DEADSOFTWARE

Update version master alpha0.7.0
authorfredboy <fredboy@protonmail.com>
Wed, 24 Apr 2024 15:49:20 +0000 (22:49 +0700)
committerfredboy <fredboy@protonmail.com>
Wed, 24 Apr 2024 15:49:20 +0000 (22:49 +0700)
190 files changed:
.github/workflows/android.yml [new file with mode: 0644]
.gitignore
COPYING
README.md
android/AndroidManifest.xml
android/assets/crafting_table.png [new file with mode: 0644]
android/assets/health.png [new file with mode: 0644]
android/assets/inventory.png [new file with mode: 0644]
android/assets/json/crafting.json [new file with mode: 0644]
android/assets/json/game_items.json
android/assets/json/menu_new_game_buttons.json
android/assets/json/texture_regions.json
android/assets/textures/blocks/grass_snowed.png [new file with mode: 0644]
android/assets/textures/blocks/leaves_birch.png [deleted file]
android/assets/textures/blocks/leaves_oak.png
android/assets/textures/blocks/leaves_spruce.png [deleted file]
android/assets/textures/items/diamond_axe.png [new file with mode: 0644]
android/assets/textures/items/diamond_hoe.png [new file with mode: 0644]
android/assets/textures/items/diamond_pickaxe.png [new file with mode: 0644]
android/assets/textures/items/gold_axe.png [new file with mode: 0644]
android/assets/textures/items/gold_hoe.png [new file with mode: 0644]
android/assets/textures/items/gold_pickaxe.png [new file with mode: 0644]
android/assets/textures/items/iron_axe.png [new file with mode: 0644]
android/assets/textures/items/iron_hoe.png [new file with mode: 0644]
android/assets/textures/items/iron_pickaxe.png [new file with mode: 0644]
android/assets/textures/items/shears.png [new file with mode: 0644]
android/assets/textures/items/stick.png [new file with mode: 0644]
android/assets/textures/items/stone_axe.png [new file with mode: 0644]
android/assets/textures/items/stone_hoe.png [new file with mode: 0644]
android/assets/textures/items/stone_pickaxe.png [new file with mode: 0644]
android/assets/textures/items/wood_axe.png [new file with mode: 0644]
android/assets/textures/items/wood_hoe.png [new file with mode: 0644]
android/assets/textures/items/wood_pickaxe.png [new file with mode: 0644]
android/build.gradle
android/src/ru/deadsoftware/cavedroid/AndroidLauncher.java
build.gradle
core/build.gradle
core/src/ru/deadsoftware/cavedroid/CaveGame.java
core/src/ru/deadsoftware/cavedroid/MainComponent.java
core/src/ru/deadsoftware/cavedroid/MainConfig.java
core/src/ru/deadsoftware/cavedroid/game/GameComponent.java
core/src/ru/deadsoftware/cavedroid/game/GameFluidsThread.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/GameInput.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/GameInputProcessor.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/GameItems.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/GameModule.java
core/src/ru/deadsoftware/cavedroid/game/GamePhysics.java
core/src/ru/deadsoftware/cavedroid/game/GameProc.java
core/src/ru/deadsoftware/cavedroid/game/GameRenderer.java
core/src/ru/deadsoftware/cavedroid/game/GameSaver.java
core/src/ru/deadsoftware/cavedroid/game/GameScreen.java
core/src/ru/deadsoftware/cavedroid/game/GameWorld.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/GameWorldGenerator.kt [deleted file]
core/src/ru/deadsoftware/cavedroid/game/actions/CommonBlockActionUtils.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/PlaceBlockActionsModule.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/UpdateBlockActionsModule.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/UseItemActionsModule.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/IPlaceBlockAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToBackgroundAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToForegroundAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceSlabAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/IUpdateBlockAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGrassAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGravelAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateRequiresBlockAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSandAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSnowedGrassAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/useitem/IUseItemAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseEmptyBucketAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseLavaBucketAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseWaterBucketAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/IGameInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/InputUtils.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/KeyboardInputHandlersModule.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/MouseInputHandlersModule.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/action/IGameInputAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/action/KeyboardInputAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/action/MouseInputAction.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/action/keys/KeyboardInputActionKey.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/action/keys/MouseInputActionKey.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/CloseGameWindowKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyDownKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyUpKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoLeftKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoRightKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/JumpKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/MoveCursorControlsModeKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenCraftingKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenInventoryKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/PauseGameKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleControlsModeKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleDebugInfoKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleGameModeKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleMinimapKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/TurnOnFlyModeKeyboardInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/AttackMouseInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CloseGameWindowMouseInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CreativeInventoryScrollMouseInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CursorMouseInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/HotbarMouseInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCraftingInventoryItemMouseInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCreativeInventoryItemMouseInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectSurvivalInventoryItemMouseInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/UseItemMouseInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/mapper/KeyboardInputActionMapper.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/mapper/MouseInputActionMapper.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/mobs/FallingGravel.java
core/src/ru/deadsoftware/cavedroid/game/mobs/FallingSand.java
core/src/ru/deadsoftware/cavedroid/game/mobs/Mob.java
core/src/ru/deadsoftware/cavedroid/game/mobs/MobsController.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/mobs/MobsController.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/mobs/Player.java
core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/block/BlockAnimationInfo.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/block/BlockDropInfo.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/block/BlockMargins.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/block/CommonBlockParams.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/craft/CraftingRecipe.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/dto/CraftingDto.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/item/CommonItemParams.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/item/InventoryItem.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/item/Item.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/world/Biome.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/world/generator/WorldGeneratorConfig.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/objects/Block.kt [deleted file]
core/src/ru/deadsoftware/cavedroid/game/objects/Drop.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/objects/Drop.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/objects/DropController.java
core/src/ru/deadsoftware/cavedroid/game/objects/Item.kt [deleted file]
core/src/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/IGameRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/RenderModule.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/windows/AbstractWindowRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/windows/CraftingWindowRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/windows/SurvivalWindowRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/windows/GameWindowsConfigs.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/windows/GameWindowsManager.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/windows/inventory/AbstractInventoryWindow.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/windows/inventory/CraftingInventoryWindow.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/windows/inventory/CreativeInventoryWindow.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/windows/inventory/SurvivalInventoryWindow.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/world/GameWorld.java [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/world/GameWorldBlocksLogicControllerTask.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.java [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/world/GameWorldMobDamageControllerTask.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/menu/MenuProc.java
core/src/ru/deadsoftware/cavedroid/menu/submenus/Menu.java
core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuMain.java
core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuNewGame.java
core/src/ru/deadsoftware/cavedroid/misc/Assets.java
core/src/ru/deadsoftware/cavedroid/misc/ControlMode.java [deleted file]
core/src/ru/deadsoftware/cavedroid/misc/Renderer.java
core/src/ru/deadsoftware/cavedroid/misc/utils/ArrayMapExtensions.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/misc/utils/ItemUtils.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteOrigin.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteUtils.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/misc/utils/mobs/MobSprites.kt [new file with mode: 0644]
desktop/build.gradle
desktop/gradle/wrapper/gradle-wrapper.properties [new file with mode: 0644]
desktop/src/ru/deadsoftware/cavedroid/desktop/DesktopLauncher.java
gen-changelog.sh [new file with mode: 0755]
gradle.properties
gradle/wrapper/gradle-wrapper.properties
gradlew
gradlew.bat
make-release.sh [new file with mode: 0755]
require-celan-work-tree.sh [new file with mode: 0644]
up-version.sh [new file with mode: 0755]

diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
new file mode 100644 (file)
index 0000000..6621b01
--- /dev/null
@@ -0,0 +1,28 @@
+name: Android CI
+
+on:
+  push:
+    branches: [ "master" ]
+  pull_request:
+    branches: [ "master" ]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: set up JDK 17
+      uses: actions/setup-java@v3
+      with:
+        java-version: '17'
+        distribution: 'temurin'
+        cache: gradle
+
+    - name: mock keystore.properties
+      run: echo "releaseKeystorePath=mock" > keystore.properties
+    - name: Grant execute permission for gradlew
+      run: chmod +x gradlew
+    - name: Build with Gradle
+      run: ./gradlew android:buildDebug
index ac624d207dbf4971a19c34d50b5b78ea187c9358..e03f5587f9ef9b42b0be788ce28a0aa394291c60 100644 (file)
@@ -116,3 +116,6 @@ Thumbs.db
 !/ios-moe/xcode/*.xcodeproj/xcshareddata
 !/ios-moe/xcode/*.xcodeproj/project.pbxproj
 /ios-moe/xcode/native/
+
+release-*/
+keystore.properties
diff --git a/COPYING b/COPYING
index 37400b601b719a895b831164d55737df9ddd37b2..6d0eb3acdafa494b776a3a16163fa4b22458a294 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -3,3 +3,5 @@ This game is distributed under MIT License (see LICENSE).
 Textures used in this game:
 Pixel Perfection by XSSheep is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.
 https://creativecommons.org/licenses/by-sa/4.0/
+
+Some scripts from stack overflow are distributed under applicable licenses
index b0f75259d51a0543afa1e874894aca6733b2c176..684c5a354316afe22a10f1134a69e5e7848ab072 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # CaveDroid
-[![Build Status](https://travis-ci.org/fredboy/cavedroid.svg?branch=master)](https://travis-ci.org/fredboy/cavedroid)
-[![Releases](https://img.shields.io/github/release/fredboy/cavedroid.svg)](https://github.com/fredboy/cavedroid/releases/latest) <br>
+[![Android CI](https://github.com/fredboy/cavedroid/actions/workflows/android.yml/badge.svg)](https://github.com/fredboy/cavedroid/actions/workflows/android.yml)
+[![GitHub Tag](https://img.shields.io/github/v/tag/fredboy/cavedroid)](https://github.com/fredboy/cavedroid/tags) <br>
 2D Minecraft clone for Android and Desktop. <br>
 Written in Java using libGDX framework. <br>
 ## Binary releases
index 47eaf7a1afc8266caabc5563a9b7a645e66a9216..9328a4edd5b08ac8f70675819a992dd7e619f620 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:tools="http://schemas.android.com/tools" package="ru.deadsoftware.cavedroid">
+          xmlns:tools="http://schemas.android.com/tools">
 
     <application
             android:allowBackup="true"
@@ -9,6 +9,7 @@
             android:theme="@style/GdxTheme" tools:ignore="GoogleAppIndexingWarning"
             android:fullBackupContent="@xml/backup_descriptor">
         <activity
+                android:exported="true"
                 android:name="ru.deadsoftware.cavedroid.AndroidLauncher"
                 android:label="@string/app_name"
                 android:screenOrientation="sensorLandscape"
diff --git a/android/assets/crafting_table.png b/android/assets/crafting_table.png
new file mode 100644 (file)
index 0000000..e019e39
Binary files /dev/null and b/android/assets/crafting_table.png differ
diff --git a/android/assets/health.png b/android/assets/health.png
new file mode 100644 (file)
index 0000000..93314e6
Binary files /dev/null and b/android/assets/health.png differ
diff --git a/android/assets/inventory.png b/android/assets/inventory.png
new file mode 100644 (file)
index 0000000..8bc6cf7
Binary files /dev/null and b/android/assets/inventory.png differ
diff --git a/android/assets/json/crafting.json b/android/assets/json/crafting.json
new file mode 100644 (file)
index 0000000..6f651bb
--- /dev/null
@@ -0,0 +1,42 @@
+{
+  "planks_oak": {
+    "input": ["log_oak", "none", "none", "none", "none", "none", "none", "none", "none"],
+    "count": 4
+  },
+  "stick": {
+    "input": ["planks_oak", "none", "none", "planks_oak", "none", "none", "none", "none", "none"],
+    "count": 4
+  },
+  "wood_pickaxe": {
+    "input": ["planks_oak", "planks_oak", "planks_oak", "none", "stick", "none", "none", "stick", "none"],
+    "count": 59
+  },
+  "wood_axe": {
+    "input": ["planks_oak", "planks_oak", "none", "planks_oak", "stick", "none", "none", "stick", "none"],
+    "count": 60
+  },
+  "wood_sword": {
+    "input": ["none", "planks_oak", "none", "none", "planks_oak", "none", "none", "stick", "none"],
+    "count": 60
+  },
+  "wood_shovel": {
+    "input": ["none", "planks_oak", "none", "none", "stick", "none", "none", "stick", "none"],
+    "count": 59
+  },
+  "stone_pickaxe": {
+    "input": ["cobblestone", "cobblestone", "cobblestone", "none", "stick", "none", "none", "stick", "none"],
+    "count": 131
+  },
+  "stone_axe": {
+    "input": ["cobblestone", "cobblestone", "none", "cobblestone", "stick", "none", "none", "stick", "none"],
+    "count": 131
+  },
+  "stone_sword": {
+    "input": ["none", "cobblestone", "none", "none", "cobblestone", "none", "none", "stick", "none"],
+    "count": 132
+  },
+  "stone_shovel": {
+    "input": ["none", "cobblestone", "none", "none", "stick", "none", "none", "stick", "none"],
+    "count": 131
+  }
+}
\ No newline at end of file
index 042f0c9bbea1a77cde66481caaf6ec8fde516cc2..d6c3b441a3fc0cff5672e08a10985e8705bfbcd0 100644 (file)
   "blocks": {
     "none": {
       "collision": false,
-      "transparent": true
+      "transparent": true,
+      "drop": "none",
+      "texture": "none",
+      "meta": "none"
     },
     "stone": {
       "hp": 450,
-      "drop": "cobblestone"
+      "drop": "cobblestone",
+      "texture": "stone",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
     },
     "grass": {
       "hp": 54,
-      "drop": "dirt"
+      "drop": "dirt",
+      "texture": "grass",
+      "tool_level": 0,
+      "tool_type": "shovel"
+    },
+    "grass_snowed": {
+      "hp": 54,
+      "drop": "dirt",
+      "texture": "grass_snowed",
+      "tool_level": 0,
+      "tool_type": "shovel"
     },
     "dirt": {
-      "hp": 45
+      "hp": 45,
+      "drop": "dirt",
+      "texture": "dirt",
+      "tool_level": 0,
+      "tool_type": "shovel"
     },
     "cobblestone": {
-      "hp": 600
+      "hp": 600,
+      "drop": "cobblestone",
+      "texture": "cobblestone",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
     },
     "planks_oak": {
-      "hp": 180
+      "hp": 180,
+      "drop": "planks_oak",
+      "texture": "planks_oak",
+      "tool_level": 0,
+      "tool_type": "axe"
     },
     "sapling_oak": {
-      "hp": 0,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "block_required": true
+      "block_required": true,
+      "drop": "sapling_oak",
+      "texture": "sapling_oak",
+      "hp": 0
     },
     "bedrock": {
-      "hp": -1
+      "drop": "bedrock",
+      "texture": "bedrock"
     },
     "water": {
-      "hp": -1,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "fluid": true,
-      "texture": "water_still",
+      "drop": "water",
       "meta": "water",
+      "texture": "water_still",
       "animated": true,
-      "frames": 16
+      "frames": 16,
+      "state": 0
     },
     "lava": {
-      "hp": -1,
       "collision": false,
-      "background": false,
-      "fluid": true,
-      "texture": "lava_still",
+      "drop": "lava",
       "meta": "lava",
+      "texture": "lava_still",
       "animated": true,
-      "frames": 16
+      "frames": 16,
+      "state": 0,
+      "damage": 4
     },
     "sand": {
-      "hp": 45
+      "hp": 45,
+      "drop": "sand",
+      "texture": "sand",
+      "tool_level": 0,
+      "tool_type": "shovel"
     },
     "gravel": {
-      "hp": 54
+      "hp": 54,
+      "drop": "gravel",
+      "texture": "gravel",
+      "tool_level": 0,
+      "tool_type": "shovel"
     },
     "gold_ore": {
-      "hp": 900
+      "hp": 900,
+      "drop": "gold_ore",
+      "texture": "gold_ore",
+      "tool_level": 3,
+      "tool_type": "pickaxe"
     },
     "iron_ore": {
-      "hp": 900
+      "hp": 900,
+      "drop": "iron_ore",
+      "texture": "iron_ore",
+      "tool_level": 2,
+      "tool_type": "pickaxe"
     },
     "coal_ore": {
       "hp": 900,
-      "drop": "coal"
+      "drop": "coal",
+      "texture": "coal_ore",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "diamond_ore": {
+      "hp": 900,
+      "drop": "diamond",
+      "texture": "diamond_ore",
+      "tool_level": 3,
+      "tool_type": "pickaxe"
+    },
+    "diamond_block": {
+      "hp": 1500,
+      "drop": "diamond_block",
+      "texture": "diamond_block",
+      "tool_level": 3,
+      "tool_type": "pickaxe"
     },
     "log_oak": {
-      "hp": 180
+      "hp": 180,
+      "drop": "log_oak",
+      "texture": "log_oak",
+      "tool_level": 0,
+      "tool_type": "axe"
     },
     "leaves_oak": {
       "hp": 21,
-      "transparent": true
+      "drop": "leaves_oak",
+      "texture": "leaves_oak",
+      "tool_level": 1,
+      "tool_type": "shears",
+      "tint": "#5AC557"
     },
     "sponge": {
-      "hp": 54
+      "hp": 54,
+      "drop": "sponge",
+      "texture": "sponge",
+      "tool_level": 0,
+      "tool_type": "hoe"
     },
     "glass": {
       "hp": 27,
+      "transparent": true,
       "drop": "none",
-      "background": false,
-      "transparent": true
+      "texture": "glass"
     },
     "lapis_ore": {
       "hp": 900,
-      "drop": "lapis"
+      "drop": "lapis",
+      "texture": "lapis_ore",
+      "tool_level": 2,
+      "tool_type": "pickaxe"
     },
     "lapis_block": {
-      "hp": 900
+      "hp": 900,
+      "drop": "lapis_block",
+      "texture": "lapis_block",
+      "tool_level": 2,
+      "tool_type": "pickaxe"
     },
     "sandstone": {
-      "hp": 240
+      "hp": 240,
+      "drop": "sandstone",
+      "texture": "sandstone",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
     },
     "noteblock": {
-      "hp": 75
+      "hp": 75,
+      "drop": "noteblock",
+      "texture": "noteblock",
+      "tool_level": 0,
+      "tool_type": "axe"
     },
     "bed_l": {
       "hp": 21,
-      "drop": "none",
       "collision": false,
       "background": true,
-      "transparent": true
+      "transparent": true,
+      "drop": "none",
+      "texture": "bed_l",
+      "tool_level": 0,
+      "tool_type": "axe"
     },
     "bed_r": {
       "hp": 21,
-      "drop": "none",
       "collision": false,
       "background": true,
-      "transparent": true
+      "transparent": true,
+      "drop": "none",
+      "texture": "bed_r",
+      "tool_level": 0,
+      "tool_type": "axe"
     },
     "web": {
       "hp": 1200,
       "collision": false,
-      "background": false,
-      "transparent": true
+      "transparent": true,
+      "drop": "web",
+      "texture": "web",
+      "tool_level": 1,
+      "tool_type": "shears"
     },
     "tallgrass": {
-      "hp": 0,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "block_required": true
+      "block_required": true,
+      "drop": "tallgrass",
+      "texture": "tallgrass",
+      "tool_level": 1,
+      "tool_type": "shears",
+      "tint": "#5AC557",
+      "hp": 0
     },
     "deadbush": {
-      "hp": 0,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "block_required": true
+      "block_required": true,
+      "drop": "deadbush",
+      "texture": "deadbush",
+      "tool_level": 1,
+      "tool_type": "shears",
+      "hp": 0
     },
     "bricks": {
-      "hp": 600
+      "hp": 600,
+      "drop": "bricks",
+      "texture": "bricks",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
     },
     "dandelion": {
-      "hp": 0,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "block_required": true
+      "block_required": true,
+      "drop": "dandelion",
+      "texture": "dandelion",
+      "hp": 0
     },
     "rose": {
-      "hp": 0,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "block_required": true
+      "block_required": true,
+      "drop": "rose",
+      "texture": "rose",
+      "hp": 0
     },
     "mushroom_brown": {
-      "hp": 0,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "block_required": true
+      "block_required": true,
+      "drop": "mushroom_brown",
+      "texture": "mushroom_brown",
+      "hp": 0
     },
     "mushroom_red": {
-      "hp": 0,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "block_required": true
+      "block_required": true,
+      "drop": "mushroom_red",
+      "texture": "mushroom_red",
+      "hp": 0
     },
     "wool_colored_white": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_white",
+      "texture": "wool_colored_white",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_orange": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_orange",
+      "texture": "wool_colored_orange",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_magenta": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_magenta",
+      "texture": "wool_colored_magenta",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_light_blue": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_light_blue",
+      "texture": "wool_colored_light_blue",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_yellow": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_yellow",
+      "texture": "wool_colored_yellow",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_lime": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_lime",
+      "texture": "wool_colored_lime",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_pink": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_pink",
+      "texture": "wool_colored_pink",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_gray": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_gray",
+      "texture": "wool_colored_gray",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_silver": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_silver",
+      "texture": "wool_colored_silver",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_cyan": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_cyan",
+      "texture": "wool_colored_cyan",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_purple": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_purple",
+      "texture": "wool_colored_purple",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_blue": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_blue",
+      "texture": "wool_colored_blue",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_brown": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_brown",
+      "texture": "wool_colored_brown",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_green": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_green",
+      "texture": "wool_colored_green",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_red": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_red",
+      "texture": "wool_colored_red",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "wool_colored_black": {
-      "hp": 75
+      "hp": 75,
+      "drop": "wool_colored_black",
+      "texture": "wool_colored_black",
+      "tool_level": 0,
+      "tool_type": "shears"
     },
     "gold_block": {
-      "hp": 900
+      "hp": 900,
+      "drop": "gold_block",
+      "texture": "gold_block",
+      "tool_level": 3,
+      "tool_type": "pickaxe"
     },
     "iron_block": {
-      "hp": 1500
+      "hp": 1500,
+      "drop": "iron_block",
+      "texture": "iron_block",
+      "tool_level": 2,
+      "tool_type": "pickaxe"
     },
-    "stone_slab": {
+    "stone_slab_bottom": {
       "top": 8,
+      "sprite_top": 8,
       "hp": 600,
       "transparent": true,
-      "sprite_top": 8
+      "drop": "stone_slab",
+      "meta": "slab",
+      "texture": "stone_slab",
+      "full_block": "double_stone_slab",
+      "other_part": "stone_slab_top",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "stone_slab_top": {
+      "hp": 600,
+      "bottom": 8,
+      "sprite_bottom": 8,
+      "transparent": true,
+      "drop": "stone_slab",
+      "meta": "slab",
+      "texture": "stone_slab",
+      "full_block": "double_stone_slab",
+      "other_part": "stone_slab_bottom",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
     },
     "double_stone_slab": {
       "hp": 600,
       "drop": "stone_slab",
-      "texture": "stone_slab"
+      "drop_count": 2,
+      "texture": "stone_slab",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
     },
-    "sandstone_slab": {
+    "sandstone_slab_bottom": {
       "top": 8,
+      "sprite_top": 8,
       "hp": 600,
       "transparent": true,
-      "texture": "sandstone"
-    },
-    "oak_slab": {
+      "drop": "sandstone_slab",
+      "meta": "slab",
+      "texture": "sandstone",
+      "full_block": "sandstone",
+      "other_part": "sandstone_slab_top",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "sandstone_slab_top": {
+      "bottom": 8,
+      "sprite_bottom": 8,
+      "hp": 600,
+      "transparent": true,
+      "drop": "sandstone_slab",
+      "meta": "slab",
+      "texture": "sandstone",
+      "full_block": "sandstone",
+      "other_part": "sandstone_slab_bottom",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "oak_slab_bottom": {
       "top": 8,
+      "sprite_top": 8,
       "hp": 180,
       "transparent": true,
-      "texture": "planks_oak"
-    },
-    "cobblestone_slab": {
+      "drop": "oak_slab",
+      "meta": "slab",
+      "texture": "planks_oak",
+      "full_block": "planks_oak",
+      "other_part": "oak_slab_top",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "oak_slab_top": {
+      "bottom": 8,
+      "sprite_bottom": 8,
+      "hp": 180,
+      "transparent": true,
+      "drop": "oak_slab",
+      "meta": "slab",
+      "texture": "planks_oak",
+      "full_block": "planks_oak",
+      "other_part": "oak_slab_bottom",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "cobblestone_slab_bottom": {
       "top": 8,
+      "sprite_top": 8,
       "hp": 600,
       "transparent": true,
-      "texture": "cobblestone"
-    },
-    "brick_slab": {
+      "meta": "slab",
+      "drop": "cobblestone_slab",
+      "texture": "cobblestone",
+      "full_block": "cobblestone",
+      "other_part": "cobblestone_slab_top",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "cobblestone_slab_top": {
+      "bottom": 8,
+      "sprite_bottom": 8,
+      "hp": 600,
+      "transparent": true,
+      "meta": "slab",
+      "drop": "cobblestone_slab",
+      "texture": "cobblestone",
+      "full_block": "cobblestone",
+      "other_part": "cobblestone_slab_bottom",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "brick_slab_bottom": {
       "top": 8,
+      "sprite_top": 8,
       "hp": 600,
       "transparent": true,
-      "texture": "bricks"
+      "drop": "brick_slab",
+      "meta": "slab",
+      "texture": "bricks",
+      "full_block": "bricks",
+      "other_part": "brick_slab_top",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "brick_slab_top": {
+      "bottom": 8,
+      "sprite_bottom": 8,
+      "hp": 600,
+      "transparent": true,
+      "drop": "brick_slab",
+      "meta": "slab",
+      "texture": "bricks",
+      "full_block": "bricks",
+      "other_part": "brick_slab_bottom",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
     },
     "stonebrick": {
-      "hp": 450
+      "hp": 450,
+      "drop": "stonebrick",
+      "texture": "stonebrick",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
     },
-    "stonebrick_slab": {
+    "stonebrick_slab_bottom": {
       "top": 8,
+      "sprite_top": 8,
       "hp": 450,
       "transparent": true,
-      "texture": "stonebrick"
+      "drop": "stonebrick_slab",
+      "meta": "slab",
+      "texture": "stonebrick",
+      "full_block": "stonebrick",
+      "other_part": "stonebrick_slab_top",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "stonebrick_slab_top": {
+      "bottom": 8,
+      "sprite_bottom": 8,
+      "hp": 450,
+      "transparent": true,
+      "drop": "stonebrick_slab",
+      "meta": "slab",
+      "texture": "stonebrick",
+      "full_block": "stonebrick",
+      "other_part": "brick_slab_bottom",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
     },
     "cactus": {
       "left": 1,
       "right": 1,
       "hp": 39,
       "transparent": true,
-      "block_required": true
+      "block_required": true,
+      "drop": "cactus",
+      "texture": "cactus",
+      "damage": 1
     },
     "water_16": {
-      "hp": -1,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "fluid": true,
-      "texture": "water_flow",
+      "drop": "water_16",
       "meta": "water",
+      "texture": "water_flow",
       "animated": true,
-      "frames": 16
+      "frames": 16,
+      "state": 1
     },
     "water_12": {
       "top": 4,
-      "hp": -1,
+      "sprite_top": 4,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "fluid": true,
-      "texture": "water_flow",
+      "drop": "water_12",
       "meta": "water",
+      "texture": "water_flow",
       "animated": true,
       "frames": 16,
-      "sprite_top": 4
+      "state": 2
     },
     "water_8": {
       "top": 8,
-      "hp": -1,
+      "sprite_top": 8,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "fluid": true,
-      "texture": "water_flow",
+      "drop": "water_8",
       "meta": "water",
+      "texture": "water_flow",
       "animated": true,
       "frames": 16,
-      "sprite_top": 8
+      "state": 3
     },
     "water_4": {
       "top": 12,
-      "hp": -1,
+      "sprite_top": 12,
       "collision": false,
-      "background": false,
       "transparent": true,
-      "fluid": true,
-      "texture": "water_flow",
+      "drop": "water_4",
       "meta": "water",
+      "texture": "water_flow",
       "animated": true,
       "frames": 16,
-      "sprite_top": 12
+      "state": 4
     },
     "lava_16": {
-      "hp": -1,
       "collision": false,
-      "background": false,
-      "fluid": true,
-      "texture": "lava_flow",
+      "drop": "lava_16",
       "meta": "lava",
+      "texture": "lava_flow",
       "animated": true,
-      "frames": 16
+      "frames": 16,
+      "state": 1,
+      "damage": 4
     },
     "lava_12": {
       "top": 4,
-      "hp": -1,
+      "sprite_top": 4,
       "collision": false,
-      "background": false,
-      "fluid": true,
-      "texture": "lava_flow",
+      "transparent": true,
+      "drop": "lava_12",
       "meta": "lava",
+      "texture": "lava_flow",
       "animated": true,
       "frames": 16,
-      "sprite_top": 4
+      "state": 2,
+      "damage": 4
     },
     "lava_8": {
       "top": 8,
-      "hp": -1,
+      "sprite_top": 8,
       "collision": false,
-      "background": false,
-      "fluid": true,
-      "texture": "lava_flow",
+      "transparent": true,
+      "drop": "lava_8",
       "meta": "lava",
+      "texture": "lava_flow",
       "animated": true,
       "frames": 16,
-      "sprite_top": 8
+      "state": 3,
+      "damage": 4
     },
     "lava_4": {
       "top": 12,
-      "hp": -1,
+      "sprite_top": 12,
       "collision": false,
-      "background": false,
-      "fluid": true,
-      "texture": "lava_flow",
+      "transparent": true,
+      "drop": "lava_4",
       "meta": "lava",
+      "texture": "lava_flow",
       "animated": true,
       "frames": 16,
-      "sprite_top": 12
+      "state": 4,
+      "damage": 4
     },
     "obsidian": {
-      "hp": 1500
+      "hp": 15000,
+      "drop": "obsidian",
+      "texture": "obsidian",
+      "tool_level": 4,
+      "tool_type": "pickaxe"
     }
   },
   "items": {
     "none": {
       "name": "",
-      "type": "block"
+      "type": "none",
+      "texture": "none"
     },
     "stone": {
       "name": "Stone",
-      "type": "block"
+      "type": "block",
+      "texture": "stone"
     },
     "grass": {
       "name": "Grass",
-      "type": "block"
+      "type": "block",
+      "texture": "grass"
     },
     "dirt": {
       "name": "Dirt",
-      "type": "block"
+      "type": "block",
+      "texture": "dirt"
     },
     "cobblestone": {
       "name": "Cobblestone",
-      "type": "block"
+      "type": "block",
+      "texture": "cobblestone"
     },
     "planks_oak": {
       "name": "Oak Planks",
-      "type": "block"
+      "type": "block",
+      "texture": "planks_oak"
     },
     "sapling_oak": {
       "name": "Oak Sapling",
-      "type": "block"
+      "type": "block",
+      "texture": "sapling_oak"
     },
     "bedrock": {
       "name": "Bedrock",
-      "type": "block"
+      "type": "block",
+      "texture": "bedrock"
     },
     "water": {
       "name": "Water",
-      "type": "block"
+      "type": "block",
+      "texture": "water"
     },
     "lava": {
       "name": "Lava",
-      "type": "block"
+      "type": "block",
+      "texture": "lava"
     },
     "sand": {
       "name": "Sand",
-      "type": "block"
+      "type": "block",
+      "texture": "sand"
     },
     "gravel": {
       "name": "Gravel",
-      "type": "block"
+      "type": "block",
+      "texture": "gravel"
     },
     "gold_ore": {
       "name": "Golden Ore",
-      "type": "block"
+      "type": "block",
+      "texture": "gold_ore"
     },
     "iron_ore": {
       "name": "Iron Ore",
-      "type": "block"
+      "type": "block",
+      "texture": "iron_ore"
     },
     "coal_ore": {
       "name": "Coal Ore",
-      "type": "block"
+      "type": "block",
+      "texture": "coal_ore"
+    },
+    "diamond_ore": {
+      "name": "Diamond Ore",
+      "type": "block",
+      "texture": "diamond_ore"
     },
     "log_oak": {
       "name": "Wood",
-      "type": "block"
+      "type": "block",
+      "texture": "log_oak"
     },
     "leaves_oak": {
       "name": "Leaves",
-      "type": "block"
+      "type": "block",
+      "texture": "leaves_oak"
     },
     "glass": {
       "name": "Glass",
-      "type": "block"
+      "type": "block",
+      "texture": "glass"
     },
     "lapis_ore": {
       "name": "Lapis Ore",
-      "type": "block"
+      "type": "block",
+      "texture": "lapis_ore"
     },
     "lapis_block": {
       "name": "Lapis Block",
-      "type": "block"
+      "type": "block",
+      "texture": "lapis_block"
     },
     "sandstone": {
       "name": "Sandstone",
-      "type": "block"
+      "type": "block",
+      "texture": "sandstone"
     },
     "web": {
       "name": "Cobweb",
-      "type": "block"
+      "type": "block",
+      "texture": "web"
     },
     "tallgrass": {
       "name": "Tall Grass",
-      "type": "block"
+      "type": "block",
+      "texture": "tallgrass",
+      "origin_x": 0.5
     },
     "deadbush": {
       "name": "Dead Bush",
-      "type": "block"
+      "type": "block",
+      "texture": "deadbush",
+      "origin_x": 0.5
     },
     "bricks": {
       "name": "Bricks",
-      "type": "block"
+      "type": "block",
+      "texture": "bricks"
     },
     "dandelion": {
       "name": "Dandelion",
-      "type": "block"
+      "type": "block",
+      "texture": "dandelion",
+      "origin_x": 0.5
     },
     "rose": {
       "name": "Rose",
-      "type": "block"
+      "type": "block",
+      "texture": "rose",
+      "origin_x": 0.5
     },
     "mushroom_brown": {
       "name": "Mushroom",
-      "type": "block"
+      "type": "block",
+      "texture": "mushroom_brown",
+      "origin_x": 0.5
     },
     "mushroom_red": {
       "name": "Mushroom",
-      "type": "block"
+      "type": "block",
+      "texture": "mushroom_red",
+      "origin_x": 0.5
     },
     "wool_colored_white": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_white"
     },
     "wool_colored_orange": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_orange"
     },
     "wool_colored_magenta": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_magenta"
     },
     "wool_colored_light_blue": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_light_blue"
     },
     "wool_colored_yellow": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_yellow"
     },
     "wool_colored_lime": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_lime"
     },
     "wool_colored_pink": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_pink"
     },
     "wool_colored_gray": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_gray"
     },
     "wool_colored_silver": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_silver"
     },
     "wool_colored_cyan": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_cyan"
     },
     "wool_colored_purple": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_purple"
     },
     "wool_colored_blue": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_blue"
     },
     "wool_colored_brown": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_brown"
     },
     "wool_colored_green": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_green"
     },
     "wool_colored_red": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_red"
     },
     "wool_colored_black": {
       "name": "Wool",
-      "type": "block"
+      "type": "block",
+      "texture": "wool_colored_black"
     },
     "gold_block": {
       "name": "Gold Block",
-      "type": "block"
+      "type": "block",
+      "texture": "gold_block"
     },
     "iron_block": {
       "name": "Iron Block",
-      "type": "block"
+      "type": "block",
+      "texture": "iron_block"
+    },
+    "diamond_block": {
+      "name": "Diamond Block",
+      "type": "block",
+      "texture": "diamond_block"
     },
     "stone_slab": {
       "name": "Stone Slab",
-      "type": "block",
-      "meta": "slab"
+      "type": "slab",
+      "texture": "stone_slab",
+      "top_slab_block": "stone_slab_top",
+      "bottom_slab_block": "stone_slab_bottom"
     },
     "sandstone_slab": {
       "name": "Sandstone Slab",
-      "type": "block",
-      "meta": "slab"
+      "type": "slab",
+      "texture": "sandstone_slab",
+      "top_slab_block": "sandstone_slab_top",
+      "bottom_slab_block": "sandstone_slab_bottom"
     },
     "oak_slab": {
       "name": "Oak Slab",
-      "type": "block",
-      "meta": "slab"
+      "type": "slab",
+      "texture": "oak_slab",
+      "top_slab_block": "oak_slab_top",
+      "bottom_slab_block": "oak_slab_bottom"
     },
     "cobblestone_slab": {
       "name": "Cobblestone Slab",
-      "type": "block",
-      "meta": "slab"
+      "type": "slab",
+      "texture": "cobblestone_slab",
+      "top_slab_block": "cobblestone_slab_top",
+      "bottom_slab_block": "cobblestone_slab_bottom"
     },
     "brick_slab": {
       "name": "Brick Slab",
-      "type": "block",
-      "meta": "slab"
+      "type": "slab",
+      "texture": "brick_slab",
+      "top_slab_block": "brick_slab_top",
+      "bottom_slab_block": "brick_slab_bottom"
     },
     "stonebrick": {
       "name": "Stone Brick",
-      "type": "block"
+      "type": "block",
+      "texture": "stonebrick"
     },
     "stonebrick_slab": {
       "name": "Stone Brick Slab",
-      "type": "block",
-      "meta": "slab"
+      "type": "slab",
+      "texture": "stonebrick_slab",
+      "top_slab_block": "stonebrick_slab_top",
+      "bottom_slab_block": "stonebrick_slab_bottom"
     },
     "cactus": {
       "name": "Cactus",
-      "type": "block"
+      "type": "block",
+      "texture": "cactus"
     },
     "obsidian": {
       "name": "Obsidian",
-      "type": "block"
+      "type": "block",
+      "texture": "obsidian"
+    },
+    "stick": {
+      "name": "Stick",
+      "texture": "stick"
     },
     "wood_sword": {
       "name": "Wooden Sword",
-      "type": "tool"
+      "type": "sword",
+      "texture": "wood_sword",
+      "origin_x": 0.125,
+      "tool_level": 1,
+      "max_stack": 60
     },
     "stone_sword": {
       "name": "Stone Sword",
-      "type": "tool"
+      "type": "sword",
+      "texture": "stone_sword",
+      "origin_x": 0.125,
+      "tool_level": 2,
+      "max_stack": 132
     },
     "iron_sword": {
       "name": "Iron Sword",
-      "type": "tool"
+      "type": "sword",
+      "texture": "iron_sword",
+      "origin_x": 0.125,
+      "tool_level": 3,
+      "max_stack": 251
     },
     "diamond_sword": {
       "name": "Diamond Sword",
-      "type": "tool"
+      "type": "sword",
+      "texture": "diamond_sword",
+      "origin_x": 0.125,
+      "tool_level": 4,
+      "max_stack": 1562
     },
     "gold_sword": {
       "name": "Golden Sword",
-      "type": "tool"
+      "type": "sword",
+      "texture": "gold_sword",
+      "origin_x": 0.125,
+      "tool_level": 1,
+      "max_stack": 33
     },
     "wood_shovel": {
       "name": "Wooden Shovel",
-      "type": "tool"
+      "type": "shovel",
+      "texture": "wood_shovel",
+      "origin_x": 0.125,
+      "tool_level" : 1,
+      "max_stack": 59
     },
     "stone_shovel": {
       "name": "Stone Shovel",
-      "type": "tool"
+      "type": "shovel",
+      "texture": "stone_shovel",
+      "origin_x": 0.125,
+      "tool_level" : 2,
+      "max_stack": 131
     },
     "iron_shovel": {
       "name": "Iron Shovel",
-      "type": "tool"
+      "type": "shovel",
+      "texture": "iron_shovel",
+      "origin_x": 0.125,
+      "tool_level" : 3,
+      "max_stack": 250
     },
     "diamond_shovel": {
       "name": "Diamond Shovel",
-      "type": "tool"
+      "type": "shovel",
+      "texture": "diamond_shovel",
+      "origin_x": 0.125,
+      "tool_level" : 4,
+      "max_stack": 1561
     },
     "gold_shovel": {
       "name": "Golden Shovel",
-      "type": "tool"
+      "type": "shovel",
+      "texture": "gold_shovel",
+      "origin_x": 0.125,
+      "tool_level" : 1,
+      "block_damage_multiplier": 6,
+      "max_stack": 32
+    },
+    "wood_pickaxe": {
+      "name": "Wooden Pickaxe",
+      "type": "pickaxe",
+      "texture": "wood_pickaxe",
+      "origin_x": 0.125,
+      "tool_level" : 1,
+      "max_stack": 59
+    },
+    "stone_pickaxe": {
+      "name": "Stone Pickaxe",
+      "type": "pickaxe",
+      "texture": "stone_pickaxe",
+      "origin_x": 0.125,
+      "tool_level" : 2,
+      "max_stack": 131
+    },
+    "iron_pickaxe": {
+      "name": "Iron Pickaxe",
+      "type": "pickaxe",
+      "texture": "iron_pickaxe",
+      "origin_x": 0.125,
+      "tool_level" : 3,
+      "max_stack": 251
+    },
+    "diamond_pickaxe": {
+      "name": "Diamond Pickaxe",
+      "type": "pickaxe",
+      "texture": "diamond_pickaxe",
+      "origin_x": 0.125,
+      "tool_level" : 4,
+      "max_stack": 1562
+    },
+    "gold_pickaxe": {
+      "name": "Golden Pickaxe",
+      "type": "pickaxe",
+      "texture": "gold_pickaxe",
+      "origin_x": 0.125,
+      "tool_level" : 1,
+      "block_damage_multiplier": 6,
+      "max_stack": 33
+    },
+    "wood_axe": {
+      "name": "Wooden Axe",
+      "type": "axe",
+      "texture": "wood_axe",
+      "origin_x": 0.125,
+      "tool_level" : 1,
+      "max_stack": 60
+    },
+    "stone_axe": {
+      "name": "Stone Axe",
+      "type": "axe",
+      "texture": "stone_axe",
+      "origin_x": 0.125,
+      "tool_level" : 2,
+      "max_stack": 131
+    },
+    "iron_axe": {
+      "name": "Iron Axe",
+      "type": "axe",
+      "texture": "iron_axe",
+      "origin_x": 0.125,
+      "tool_level" : 3,
+      "max_stack": 250
+    },
+    "diamond_axe": {
+      "name": "Diamond Axe",
+      "type": "axe",
+      "texture": "diamond_axe",
+      "origin_x": 0.125,
+      "tool_level" : 4,
+      "max_stack": 1561
+    },
+    "gold_axe": {
+      "name": "Golden Axe",
+      "type": "axe",
+      "texture": "gold_axe",
+      "origin_x": 0.125,
+      "tool_level" : 1,
+      "block_damage_multiplier": 6,
+      "max_stack": 32
+    },
+    "shears": {
+      "name": "Shears",
+      "type": "shears",
+      "texture": "shears",
+      "tool_level" : 1,
+      "origin_x": 0.125,
+      "max_stack": 238
     },
     "bucket_empty": {
       "name": "Empty Bucket",
-      "type": "tool"
+      "type": "bucket",
+      "texture": "bucket_empty",
+      "origin_x": 0.25,
+      "action_key": "use_empty_bucket",
+      "max_stack": 1
     },
     "bucket_water": {
       "name": "Water Bucket",
-      "type": "tool"
+      "type": "bucket",
+      "texture": "bucket_water",
+      "origin_x": 0.25,
+      "action_key": "use_water_bucket",
+      "max_stack": 1
     },
     "bucket_lava": {
       "name": "Lava Bucket",
-      "type": "tool"
+      "type": "bucket",
+      "texture": "bucket_lava",
+      "origin_x": 0.25,
+      "action_key": "use_lava_bucket",
+      "max_stack": 1
     }
   }
-}
\ No newline at end of file
+}
index c61adc3d36f8310355683f53d179402b6d56ddd0..ca4c4042bfa178fe69579c238ebdc8bf7f7c41f2 100644 (file)
@@ -3,8 +3,7 @@
     "label": "Creative"
   },
   "survival": {
-    "label": "Survival",
-    "type": 0
+    "label": "Survival"
   },
   "back": {
     "label": "Back"
index e1f3e48adc14ebf25d3578a590cfc545826017c8..800098081fedec8efe5483a3ba2af7803c742a71 100644 (file)
       "h": 26
     }
   },
-  "break": {
-    "break_0": {
-      "w": 16,
-      "h": 16
-    },
-    "break_1": {
-      "x": 16,
-      "w": 16,
-      "h": 16
-    },
-    "break_2": {
-      "x": 32,
-      "w": 16,
-      "h": 16
-    },
-    "break_3": {
-      "x": 48,
-      "w": 16,
-      "h": 16
-    },
-    "break_4": {
-      "x": 64,
-      "w": 16,
-      "h": 16
-    },
-    "break_5": {
-      "x": 80,
-      "w": 16,
-      "h": 16
-    },
-    "break_6": {
-      "x": 96,
-      "w": 16,
-      "h": 16
-    },
-    "break_7": {
-      "x": 112,
-      "w": 16,
-      "h": 16
-    },
-    "break_8": {
-      "x": 128,
-      "w": 16,
-      "h": 16
-    },
-    "break_9": {
-      "x": 144,
-      "w": 16,
-      "h": 16
-    }
-  },
   "allitems": {
     "creative": {
       "w": 176,
       "h": 15
     }
   },
+  "inventory": {
+    "survival": {
+      "w": 176,
+      "h": 166
+    }
+  },
+  "crafting_table": {
+    "crafting_table": {
+      "w": 176,
+      "h": 166
+    }
+  },
   "buttons": {
     "button_0": {
       "w": 200,
   },
   "shade": {},
   "gamelogo": {},
-  "background": {}
+  "background": {},
+  "health":{
+    "heart_whole": {
+      "w": 9
+    },
+    "heart_half": {
+      "x": 9,
+      "w": 9
+    }
+  }
 }
\ No newline at end of file
diff --git a/android/assets/textures/blocks/grass_snowed.png b/android/assets/textures/blocks/grass_snowed.png
new file mode 100644 (file)
index 0000000..730996d
Binary files /dev/null and b/android/assets/textures/blocks/grass_snowed.png differ
diff --git a/android/assets/textures/blocks/leaves_birch.png b/android/assets/textures/blocks/leaves_birch.png
deleted file mode 100644 (file)
index 3fa5139..0000000
Binary files a/android/assets/textures/blocks/leaves_birch.png and /dev/null differ
index c2a54ee5e827094223e85d93d5bb957793f79a8e..d9027521b78eaa34aac1ff2d3e3c46f98617c7c2 100644 (file)
Binary files a/android/assets/textures/blocks/leaves_oak.png and b/android/assets/textures/blocks/leaves_oak.png differ
diff --git a/android/assets/textures/blocks/leaves_spruce.png b/android/assets/textures/blocks/leaves_spruce.png
deleted file mode 100644 (file)
index cdcd782..0000000
Binary files a/android/assets/textures/blocks/leaves_spruce.png and /dev/null differ
diff --git a/android/assets/textures/items/diamond_axe.png b/android/assets/textures/items/diamond_axe.png
new file mode 100644 (file)
index 0000000..ac81a23
Binary files /dev/null and b/android/assets/textures/items/diamond_axe.png differ
diff --git a/android/assets/textures/items/diamond_hoe.png b/android/assets/textures/items/diamond_hoe.png
new file mode 100644 (file)
index 0000000..f0ad2c6
Binary files /dev/null and b/android/assets/textures/items/diamond_hoe.png differ
diff --git a/android/assets/textures/items/diamond_pickaxe.png b/android/assets/textures/items/diamond_pickaxe.png
new file mode 100644 (file)
index 0000000..75823f6
Binary files /dev/null and b/android/assets/textures/items/diamond_pickaxe.png differ
diff --git a/android/assets/textures/items/gold_axe.png b/android/assets/textures/items/gold_axe.png
new file mode 100644 (file)
index 0000000..d939170
Binary files /dev/null and b/android/assets/textures/items/gold_axe.png differ
diff --git a/android/assets/textures/items/gold_hoe.png b/android/assets/textures/items/gold_hoe.png
new file mode 100644 (file)
index 0000000..da32a5d
Binary files /dev/null and b/android/assets/textures/items/gold_hoe.png differ
diff --git a/android/assets/textures/items/gold_pickaxe.png b/android/assets/textures/items/gold_pickaxe.png
new file mode 100644 (file)
index 0000000..5b4e6d6
Binary files /dev/null and b/android/assets/textures/items/gold_pickaxe.png differ
diff --git a/android/assets/textures/items/iron_axe.png b/android/assets/textures/items/iron_axe.png
new file mode 100644 (file)
index 0000000..52113da
Binary files /dev/null and b/android/assets/textures/items/iron_axe.png differ
diff --git a/android/assets/textures/items/iron_hoe.png b/android/assets/textures/items/iron_hoe.png
new file mode 100644 (file)
index 0000000..d56485d
Binary files /dev/null and b/android/assets/textures/items/iron_hoe.png differ
diff --git a/android/assets/textures/items/iron_pickaxe.png b/android/assets/textures/items/iron_pickaxe.png
new file mode 100644 (file)
index 0000000..0746cc0
Binary files /dev/null and b/android/assets/textures/items/iron_pickaxe.png differ
diff --git a/android/assets/textures/items/shears.png b/android/assets/textures/items/shears.png
new file mode 100644 (file)
index 0000000..bfa815c
Binary files /dev/null and b/android/assets/textures/items/shears.png differ
diff --git a/android/assets/textures/items/stick.png b/android/assets/textures/items/stick.png
new file mode 100644 (file)
index 0000000..81c915e
Binary files /dev/null and b/android/assets/textures/items/stick.png differ
diff --git a/android/assets/textures/items/stone_axe.png b/android/assets/textures/items/stone_axe.png
new file mode 100644 (file)
index 0000000..5f42de6
Binary files /dev/null and b/android/assets/textures/items/stone_axe.png differ
diff --git a/android/assets/textures/items/stone_hoe.png b/android/assets/textures/items/stone_hoe.png
new file mode 100644 (file)
index 0000000..2118229
Binary files /dev/null and b/android/assets/textures/items/stone_hoe.png differ
diff --git a/android/assets/textures/items/stone_pickaxe.png b/android/assets/textures/items/stone_pickaxe.png
new file mode 100644 (file)
index 0000000..13bdd75
Binary files /dev/null and b/android/assets/textures/items/stone_pickaxe.png differ
diff --git a/android/assets/textures/items/wood_axe.png b/android/assets/textures/items/wood_axe.png
new file mode 100644 (file)
index 0000000..b0cbde6
Binary files /dev/null and b/android/assets/textures/items/wood_axe.png differ
diff --git a/android/assets/textures/items/wood_hoe.png b/android/assets/textures/items/wood_hoe.png
new file mode 100644 (file)
index 0000000..3744c1b
Binary files /dev/null and b/android/assets/textures/items/wood_hoe.png differ
diff --git a/android/assets/textures/items/wood_pickaxe.png b/android/assets/textures/items/wood_pickaxe.png
new file mode 100644 (file)
index 0000000..5932623
Binary files /dev/null and b/android/assets/textures/items/wood_pickaxe.png differ
index a5ed087df1b3458384d90a0ddf8b1bccd50ba73f..061b54d0649c631d3cba7795b9e5bedb648da2f8 100644 (file)
@@ -1,6 +1,14 @@
+plugins {
+    id "kotlin-android"
+}
+
+def keystorePropertiesFile = rootProject.file("keystore.properties")
+def keystoreProperties = new Properties()
+keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+
 android {
-    buildToolsVersion "28.0.3"
-    compileSdkVersion 29
+    namespace "ru.deadsoftware.cavedroid"
+    compileSdkVersion 34
     sourceSets {
         main {
             manifest.srcFile 'AndroidManifest.xml'
@@ -16,8 +24,8 @@ android {
         }
     }
     compileOptions {
-        sourceCompatibility 1.8
-        targetCompatibility 1.8
+        sourceCompatibility 17
+        targetCompatibility 17
     }
     packagingOptions {
         exclude 'META-INF/robovm/ios/robovm.xml'
@@ -25,23 +33,37 @@ android {
     defaultConfig {
         applicationId "ru.deadsoftware.cavedroid"
         minSdkVersion 24
-        targetSdkVersion 29
-        versionCode 10
-        versionName "alpha0.4"
+        targetSdkVersion 34
+        versionCode 20
+        versionName "alpha0.7.0"
     }
     applicationVariants.all { variant ->
         variant.outputs.all {
             outputFileName = "android-${versionName}.apk"
         }
     }
+
+    signingConfigs {
+        release_config {
+            storeFile file(keystoreProperties['releaseKeystorePath'])
+            storePassword keystoreProperties['releaseKeystorePassword']
+            keyAlias keystoreProperties['releaseKeyAlias']
+            keyPassword keystoreProperties['releaseKeyPassword']
+        }
+    }
+
     buildTypes {
         release {
             minifyEnabled false
+            signingConfig signingConfigs.release_config
         }
         debug {
             applicationIdSuffix ".debug"
         }
     }
+    buildFeatures {
+        buildConfig true
+    }
 
 }
 
index fcf083199e0e667bb839c4e45a2c7fdfc22330a0..e7aad50df8c3df8deab19206bf22d5fee456f3ef 100644 (file)
@@ -10,7 +10,6 @@ public class AndroidLauncher extends AndroidApplication {
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
-        config.hideStatusBar = true;
         config.useImmersiveMode = true;
         String gameFolder = "";
         try {
@@ -19,7 +18,7 @@ public class AndroidLauncher extends AndroidApplication {
             e.printStackTrace();
             exit();
         }
-        CaveGame caveGame = new CaveGame(gameFolder, true);
+        CaveGame caveGame = new CaveGame(gameFolder, true, null);
         caveGame.setDebug(BuildConfig.DEBUG);
         initialize(caveGame, config);
     }
index ed846ef9ecd276fccf4cd108d0db9ce443052a1d..39d1d37f36ac0e4f478c3cb5cd03b7215bff7646 100644 (file)
@@ -1,40 +1,39 @@
 buildscript {
 
+    ext {
+        appName = "CaveDroid"
+        gdxVersion = '1.12.0'
+        guavaVersion = '28.1'
+        daggerVersion = '2.51.1'
+        kotlinVersion = '1.9.23'
+        kotlinSerializationVersion = '1.6.3'
+    }
+
     repositories {
         mavenLocal()
         mavenCentral()
         maven { url "https://plugins.gradle.org/m2/" }
         maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
-        jcenter()
         google()
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.5.0'
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
+        classpath 'com.android.tools.build:gradle:8.2.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
     }
 }
 
 allprojects {
 
-    version = 'alpha0.4'
-    ext {
-        appName = "CaveDroid"
-        gdxVersion = '1.9.10'
-        roboVMVersion = '2.3.7'
-        box2DLightsVersion = '1.4'
-        ashleyVersion = '1.7.0'
-        aiVersion = '1.8.0'
-        guavaVersion = '28.1'
-    }
+    version = 'alpha0.7.0'
 
     repositories {
         mavenLocal()
         mavenCentral()
-        jcenter()
         google()
         maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
         maven { url "https://oss.sonatype.org/content/repositories/releases/" }
+        maven { url "https://jitpack.io" }
     }
 }
 
@@ -43,7 +42,8 @@ project(":desktop") {
 
     dependencies {
         implementation project(":core")
-        api "com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion"
+        implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion"
+        api "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion"
         api "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
     }
 }
@@ -55,24 +55,30 @@ project(":android") {
 
     dependencies {
         implementation project(":core")
+        implementation platform("org.jetbrains.kotlin:kotlin-bom:1.9.23")
         api "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion"
-        natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi"
         natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a"
         natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a"
         natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86"
         natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64"
+
+        configurations.implementation {
+            exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
+        }
     }
 }
 
 project(":core") {
     apply plugin: "java-library"
 
+
     dependencies {
         api "com.badlogicgames.gdx:gdx:$gdxVersion"
         api "com.google.guava:guava:$guavaVersion-android"
-        api 'com.google.dagger:dagger:2.27'
-        implementation 'org.jetbrains:annotations:15.0'
-        implementation "org.jetbrains.kotlin:kotlin-stdlib"
-        annotationProcessor 'com.google.dagger:dagger-compiler:2.27'
+        api "com.google.dagger:dagger:$daggerVersion"
+        implementation 'org.jetbrains:annotations:23.1.0'
+        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+        implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion"
+        annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
     }
 }
\ No newline at end of file
index f17b1029a7e1f15f5ac5a79a78cfcde1c1986525..cdf2ee7e4708ffec3ab49447cc63298a1f77caab 100644 (file)
@@ -1,12 +1,12 @@
 plugins {
     id "org.jetbrains.kotlin.jvm"
-    id "java"
+    id "kotlin"
     id "idea"
-    id "net.ltgt.apt" version "0.21"
+    id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlinVersion"
 }
 
-sourceCompatibility = 1.8
+sourceCompatibility = 17
 
-[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
+sourceSets.main.java.srcDirs = [ "src/" ]
 
-sourceSets.main.java.srcDirs = [ "src/" ]
\ No newline at end of file
+java.targetCompatibility = JavaVersion.VERSION_17
\ No newline at end of file
index 031b6135ae3fb58153427d52e76814d598898124..1d795521e22bc82367e0a9de9bf14ddc40a37091 100644 (file)
@@ -1,30 +1,40 @@
 package ru.deadsoftware.cavedroid;
 
+import com.badlogic.gdx.Application;
 import com.badlogic.gdx.Game;
 import com.badlogic.gdx.Gdx;
-import ru.deadsoftware.cavedroid.game.GameItems;
 import ru.deadsoftware.cavedroid.game.GameScreen;
 import ru.deadsoftware.cavedroid.misc.Assets;
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader;
+
+import javax.annotation.Nullable;
 
 public class CaveGame extends Game {
 
     private static final String TAG = "CaveGame";
 
-    public static final String VERSION = "alpha 0.4";
+    public static final String VERSION = "alpha 0.7.0";
 
     private final MainConfig mMainConfig;
     private final MainComponent mMainComponent;
+    private final AssetLoader mAssetLoader;
 
     private final String mGameFolder;
     private final boolean mTouch;
     private boolean mDebug;
 
-    public CaveGame(String gameFolder, boolean touch) {
+    @Nullable
+    private final String mAssetsPackPath;
+
+    public CaveGame(String gameFolder, boolean touch, @Nullable String assetsPackPath) {
         mGameFolder = gameFolder;
         mTouch = touch;
+        mAssetsPackPath = assetsPackPath;
 
         mMainComponent = DaggerMainComponent.builder().caveGame(this).build();
+
         mMainConfig = mMainComponent.getMainConfig();
+        mAssetLoader = mMainComponent.getAssetLoader();
     }
 
     public void setDebug(boolean debug) {
@@ -41,11 +51,18 @@ public class CaveGame extends Game {
         mMainConfig.setWidth(width);
         mMainConfig.setHeight(height);
         mMainConfig.setShowInfo(mDebug);
+        mMainConfig.setAssetsPackPath(mAssetsPackPath);
+
+        if (mDebug) {
+            Gdx.app.setLogLevel(Application.LOG_DEBUG);
+        } else {
+            Gdx.app.setLogLevel(Application.LOG_ERROR);
+        }
     }
 
-    public void newGame() {
+    public void newGame(int gameMode) {
         GameScreen gameScreen = mMainComponent.getGameScreen();
-        gameScreen.newGame();
+        gameScreen.newGame(gameMode);
         setScreen(gameScreen);
     }
 
@@ -56,20 +73,27 @@ public class CaveGame extends Game {
     }
 
     public void quitGame() {
+        if (screen != null) {
+            screen.dispose();
+        }
         setScreen(mMainComponent.getMenuScreen());
     }
 
     @Override
     public void create() {
-        Gdx.app.log(TAG, mGameFolder);
         Gdx.files.absolute(mGameFolder).mkdirs();
-
-        Assets.load();
-        GameItems.load();
-
         initConfig();
 
+        Gdx.app.debug(TAG, mGameFolder);
+        Assets.load(mAssetLoader);
         setScreen(mMainComponent.getMenuScreen());
     }
 
+    @Override
+    public void dispose() {
+        if (screen != null) {
+            screen.dispose();
+        }
+        Assets.dispose();
+    }
 }
index d8a05da03fbdeda134cc144a6af804f881ff48d9..c640fb6c2abae2e5b5d12bc4574622419ab2e9b1 100644 (file)
@@ -3,6 +3,7 @@ package ru.deadsoftware.cavedroid;
 import dagger.Component;
 import ru.deadsoftware.cavedroid.game.GameScreen;
 import ru.deadsoftware.cavedroid.menu.MenuScreen;
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader;
 
 import javax.inject.Singleton;
 
@@ -14,4 +15,6 @@ public interface MainComponent {
     MenuScreen getMenuScreen();
 
     MainConfig getMainConfig();
+
+    AssetLoader getAssetLoader();
 }
index 8cd0ffdac6920e2977f46ac10c156e8be4f4ac7f..6577f297254f82928b2e18d28f29911d32764750 100644 (file)
@@ -3,6 +3,7 @@ package ru.deadsoftware.cavedroid;
 import ru.deadsoftware.cavedroid.game.GameUiWindow;
 
 import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
@@ -24,6 +25,9 @@ public class MainConfig {
     private float mWidth;
     private float mHeight;
 
+    @Nullable
+    private String mAssetsPackPath = null;
+
     @Inject
     public MainConfig(CaveGame caveGame) {
         mCaveGame = caveGame;
@@ -49,10 +53,6 @@ public class MainConfig {
         return mGameUiWindow == gameUiWindow;
     }
 
-    public GameUiWindow getGameUiWindow() {
-        return mGameUiWindow;
-    }
-
     public void setGameUiWindow(GameUiWindow gameUiWindow) {
         mGameUiWindow = gameUiWindow;
     }
@@ -104,4 +104,13 @@ public class MainConfig {
     public void setShowMap(boolean showMap) {
         mShowMap = showMap;
     }
+
+    @Nullable
+    public String getAssetsPackPath() {
+        return mAssetsPackPath;
+    }
+
+    public void setAssetsPackPath(@Nullable String assetsPackPath) {
+        mAssetsPackPath = assetsPackPath;
+    }
 }
index 25779f06851e234c83c818872bf290064eccd82d..2dafcd5e0346fb00a7d2fec91d132f7bce74a664 100644 (file)
@@ -2,11 +2,25 @@ package ru.deadsoftware.cavedroid.game;
 
 import dagger.Component;
 import ru.deadsoftware.cavedroid.MainComponent;
+import ru.deadsoftware.cavedroid.game.actions.PlaceBlockActionsModule;
+import ru.deadsoftware.cavedroid.game.actions.UpdateBlockActionsModule;
+import ru.deadsoftware.cavedroid.game.actions.UseItemActionsModule;
+import ru.deadsoftware.cavedroid.game.input.KeyboardInputHandlersModule;
+import ru.deadsoftware.cavedroid.game.input.MouseInputHandlersModule;
+import ru.deadsoftware.cavedroid.game.render.RenderModule;
 
 @GameScope
-@Component(dependencies = MainComponent.class, modules = GameModule.class)
+@Component(dependencies = MainComponent.class,
+        modules = {GameModule.class,
+                UseItemActionsModule.class,
+                UpdateBlockActionsModule.class,
+                PlaceBlockActionsModule.class,
+                RenderModule.class,
+                KeyboardInputHandlersModule.class,
+                MouseInputHandlersModule.class
+        })
 public interface GameComponent {
     GameProc getGameProc();
 
-    GameInputProcessor getGameInputProcessor();
+    GameItemsHolder getGameItemsHolder();
 }
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameFluidsThread.java b/core/src/ru/deadsoftware/cavedroid/game/GameFluidsThread.java
deleted file mode 100644 (file)
index c3cdd47..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-package ru.deadsoftware.cavedroid.game;
-
-import com.badlogic.gdx.utils.TimeUtils;
-import ru.deadsoftware.cavedroid.game.mobs.MobsController;
-
-import java.util.Arrays;
-
-import static ru.deadsoftware.cavedroid.game.GameItems.*;
-
-class GameFluidsThread extends Thread {
-
-    private static final int FLUID_UPDATE_INTERVAL_MS = 100;
-    private static final int FLUID_STATES = 5;
-
-    private static final int[] WATER_IDS = {8, 60, 61, 62, 63};
-    private static final int[] LAVA_IDS = {9, 64, 65, 66, 67};
-
-    private long mFluidLastUpdateTimestamp = 0;
-
-    private final GameWorld mGameWorld;
-    private final MobsController mMobsController;
-
-    private final Thread mMainThread;
-
-    GameFluidsThread(GameWorld gameWorld,
-                     MobsController mobsController,
-                     Thread mainThread) {
-        mGameWorld = gameWorld;
-        mMobsController = mobsController;
-        mMainThread = mainThread;
-    }
-
-    private int getBlockState(int id) {
-        return isWater(id) ? Arrays.binarySearch(WATER_IDS, id) : Arrays.binarySearch(LAVA_IDS, id);
-    }
-
-    private int getNextBlockState(int id) {
-        if (!isFluid(id)) {
-            return -1;
-        }
-        int state = getBlockState(id);
-        if (state < FLUID_STATES - 1) {
-            return state + 1;
-        }
-        return -1;
-    }
-
-    private int getNextBlockStateId(int id) {
-        int nextState = getNextBlockState(id);
-        if (nextState == -1) {
-            return 0;
-        }
-        if (isWater(id)) {
-            return WATER_IDS[nextState];
-        }
-        return LAVA_IDS[nextState];
-    }
-
-    private int id(int x, int y) {
-        return mGameWorld.getForeMap(x, y);
-    }
-
-    private boolean sameFluid(int thisId, int thatId) {
-        return isFluid(thatId) && isWater(thatId) == isWater(thisId);
-    }
-
-    private boolean noFluidNearby(int x, int y) {
-        return !isFluid(id(x, y - 1)) &&
-                (!isFluid(id(x - 1, y)) || id(x - 1, y) >= id(x, y)) &&
-                (!isFluid(id(x + 1, y)) || id(x + 1, y) >= id(x, y));
-    }
-
-    private boolean drainFluid(int x, int y) {
-        if (getBlockState(id(x, y)) > 0) {
-            if (noFluidNearby(x, y)) {
-                mGameWorld.setForeMap(x, y, getNextBlockStateId(id(x, y)));
-            }
-            if (!isFluid(id(x, y))) {
-                mGameWorld.setForeMap(x, y, 0);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void flowFluidTo(int thisId, int x, int y, int nextStateId) {
-        int thatId = id(x, y);
-        if (fluidCanFlowThere(thisId, thatId)) {
-            mGameWorld.setForeMap(x, y, nextStateId);
-        } else if (isWater(thisId) && isLava(thatId)) {
-            if (getBlockState(thatId) > 0) {
-                mGameWorld.setForeMap(x, y, 4); //cobblestone
-            } else {
-                mGameWorld.setForeMap(x, y, 68); //obsidian
-            }
-        } else if (isLava(thisId) && isWater(thatId)) {
-            mGameWorld.setForeMap(x, y, 1); //stone
-        }
-    }
-
-    private void flowFluid(int x, int y) {
-        int id = id(x, y);
-        if (getBlockState(id) < FLUID_STATES - 1 && getBlock(id(x, y + 1)).hasCollision()) {
-            int nextState = getNextBlockState(id);
-            int nextStateId = getNextBlockStateId(id);
-            if (nextState == 1) {
-                nextStateId++;
-            }
-            flowFluidTo(id, x - 1, y, nextStateId);
-            flowFluidTo(id, x + 1, y, nextStateId);
-        } else {
-            flowFluidTo(id, x, y + 1, isWater(id) ? WATER_IDS[1] : LAVA_IDS[1]);
-        }
-
-    }
-
-    private void updateFluids(int x, int y) {
-        if (!isFluid(id(x, y))) {
-            return;
-        }
-        if (drainFluid(x, y)) {
-            return;
-        }
-        flowFluid(x, y);
-    }
-
-    private void fluidUpdater() {
-        int midScreen = (int) mMobsController.getPlayer().x / 16;
-        for (int y = mGameWorld.getHeight() - 1; y >= 0; y--) {
-            for (int x = 0; x <= mGameWorld.getWidth() / 2; x++) {
-                updateFluids(midScreen + x, y);
-                updateFluids(midScreen - x, y);
-            }
-        }
-    }
-
-    private boolean timeToUpdate() {
-        if (TimeUtils.timeSinceMillis(mFluidLastUpdateTimestamp) >= FLUID_UPDATE_INTERVAL_MS) {
-            mFluidLastUpdateTimestamp = TimeUtils.millis();
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void run() {
-        while (!this.isInterrupted() && mMainThread.isAlive()) {
-            if (timeToUpdate()) {
-                fluidUpdater();
-            }
-        }
-    }
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameInput.java b/core/src/ru/deadsoftware/cavedroid/game/GameInput.java
deleted file mode 100644 (file)
index ca60317..0000000
+++ /dev/null
@@ -1,467 +0,0 @@
-package ru.deadsoftware.cavedroid.game;
-
-import com.badlogic.gdx.Gdx;
-import com.badlogic.gdx.Input;
-import com.badlogic.gdx.graphics.g2d.TextureRegion;
-import com.badlogic.gdx.utils.TimeUtils;
-import com.google.common.collect.Range;
-import ru.deadsoftware.cavedroid.MainConfig;
-import ru.deadsoftware.cavedroid.game.mobs.Mob;
-import ru.deadsoftware.cavedroid.game.mobs.MobsController;
-import ru.deadsoftware.cavedroid.game.mobs.Pig;
-import ru.deadsoftware.cavedroid.game.mobs.Player;
-import ru.deadsoftware.cavedroid.game.objects.DropController;
-import ru.deadsoftware.cavedroid.misc.Assets;
-import ru.deadsoftware.cavedroid.misc.ControlMode;
-
-import javax.inject.Inject;
-
-import static ru.deadsoftware.cavedroid.game.GameItems.*;
-
-@GameScope
-public class GameInput {
-
-    private final MainConfig mMainConfig;
-    private final GameWorld mGameWorld;
-    private final DropController mDropController;
-    private final MobsController mMobsController;
-    private final Player mPlayer;
-
-    private ControlMode mControlMode;
-
-    private boolean mKeyDown;
-    private boolean mTouchedDown;
-    private boolean mDragging;
-
-    private int mKeyDownCode;
-    private int mTouchDownBtn;
-    private float mTouchDownX;
-    private float mTouchDownY;
-    private long mTouchDownTime;
-
-    private int mCurX;
-    private int mCurY;
-    private int mCreativeScroll;
-    private int mBlockDamage;
-
-    @Inject
-    public GameInput(MainConfig mainConfig,
-                     GameWorld gameWorld,
-                     DropController dropController,
-                     MobsController mobsController) {
-        mMainConfig = mainConfig;
-        mGameWorld = gameWorld;
-        mDropController = dropController;
-        mMobsController = mobsController;
-
-        mPlayer = mMobsController.getPlayer();
-
-        mControlMode = mMainConfig.isTouch() ? ControlMode.WALK : ControlMode.CURSOR;
-    }
-
-    private boolean checkSwim() {
-        return GameItems.isFluid(mGameWorld.getForeMap(mPlayer.getMapX(), mPlayer.getLowerMapY()));
-    }
-
-    private void goUpwards() {
-        if (checkSwim()) {
-            mPlayer.swim = true;
-        } else if (mPlayer.canJump()) {
-            mPlayer.getMove().add(0, -7);
-        } else if (!mPlayer.isFlyMode() && mPlayer.gameMode == 1) {
-            mPlayer.setFlyMode(true);
-            mPlayer.getMove().y = 0;
-        } else if (mPlayer.isFlyMode()) {
-            mPlayer.getMove().y = -GamePhysics.PL_SPEED;
-        }
-    }
-
-    @SuppressWarnings("IntegerDivisionInFloatingPointContext")
-    private boolean insideCreativeInv(float screenX, float screenY) {
-        TextureRegion creative = Assets.textureRegions.get("creative");
-        return (screenX > mMainConfig.getWidth() / 2 - creative.getRegionWidth() / 2 &&
-                screenX < mMainConfig.getWidth() / 2 + creative.getRegionWidth() / 2 &&
-                screenY > mMainConfig.getHeight() / 2 - creative.getRegionHeight() / 2 &&
-                screenY < mMainConfig.getHeight() / 2 + creative.getRegionHeight() / 2);
-    }
-
-    private void wasdPressed(int keycode) {
-        if (mControlMode == ControlMode.WALK || !mMainConfig.isTouch()) {
-            switch (keycode) {
-                case Input.Keys.A:
-                    mPlayer.getMove().x = -GamePhysics.PL_SPEED;
-                    mPlayer.setDir(Mob.Direction.LEFT);
-                    if (mMainConfig.isTouch() && checkSwim()) {
-                        mPlayer.swim = true;
-                    }
-                    break;
-                case Input.Keys.D:
-                    mPlayer.getMove().x = GamePhysics.PL_SPEED;
-                    mPlayer.setDir(Mob.Direction.RIGHT);
-                    if (mMainConfig.isTouch() && checkSwim()) {
-                        mPlayer.swim = true;
-                    }
-                    break;
-                case Input.Keys.W:
-                case Input.Keys.SPACE:
-                    goUpwards();
-                    break;
-                case Input.Keys.S:
-                case Input.Keys.CONTROL_LEFT:
-                    mPlayer.getMove().y = GamePhysics.PL_SPEED;
-                    break;
-            }
-        } else {
-            switch (keycode) {
-                case Input.Keys.A:
-                    mCurX--;
-                    break;
-                case Input.Keys.D:
-                    mCurX++;
-                    break;
-                case Input.Keys.W:
-                    mCurY--;
-                    break;
-                case Input.Keys.S:
-                    mCurY++;
-                    break;
-            }
-            mBlockDamage = 0;
-        }
-    }
-
-    private boolean isNotAutoselectable(int x, int y) {
-        return (!mGameWorld.hasForeAt(x, y) || !mGameWorld.getForeMapBlock(x, y).hasCollision());
-    }
-
-    private void checkCursorBounds() {
-        if (mCurY < 0) {
-            mCurY = 0;
-        } else if (mCurY >= mGameWorld.getHeight()) {
-            mCurY = mGameWorld.getHeight() - 1;
-        }
-
-        if (mControlMode == ControlMode.CURSOR) {
-            if (mCurX * 16 + 8 < mPlayer.getX() + mPlayer.getWidth() / 2) {
-                mPlayer.setDir(Mob.Direction.LEFT);
-            } else {
-                mPlayer.setDir(Mob.Direction.RIGHT);
-            }
-        }
-    }
-
-    public void moveCursor(GameRenderer gameRenderer) {
-        int pastX = mCurX;
-        int pastY = mCurY;
-
-        if (mControlMode == ControlMode.WALK && mMainConfig.isTouch()) {
-            mCurX = mPlayer.getMapX() + (mPlayer.looksLeft() ? -1 : 1);
-            mCurY = mPlayer.getUpperMapY();
-            for (int i = 0; i < 2 && isNotAutoselectable(mCurX, mCurY); i++) {
-                mCurY++;
-            }
-            if (isNotAutoselectable(mCurX, mCurY)) {
-                mCurX += mPlayer.looksLeft() ? 1 : -1;
-            }
-        } else if (!mMainConfig.isTouch()) {
-            mCurX = (int) (Gdx.input.getX() * (mMainConfig.getWidth() /
-                    Gdx.graphics.getWidth()) + gameRenderer.getCamX()) / 16;
-
-            mCurY = (int) (Gdx.input.getY() * (mMainConfig.getHeight() /
-                    Gdx.graphics.getHeight()) + gameRenderer.getCamY()) / 16;
-            if (mCurX < 0) {
-                mCurX--;
-            }
-        }
-
-        if (pastX != mCurX || pastY != mCurY) {
-            mBlockDamage = 0;
-        }
-
-        checkCursorBounds();
-    }
-
-    private void useItem(int x, int y, int id, boolean bg) {
-        String key = getItem(id).isBlock() ? getBlockKey(id) : getItemKey(id);
-        if (id > 0) {
-            if (getItem(id).isBlock()) {
-                if (!bg) {
-                    mGameWorld.placeToForeground(x, y, getBlockIdByItemId(id));
-                } else {
-                    mGameWorld.placeToBackground(x, y, getBlockIdByItemId(id));
-                }
-            } else {
-                switch (key) {
-                    case "bucket_water":
-                        mGameWorld.placeToForeground(x, y, getBlockId("water"));
-                        mPlayer.inventory[mPlayer.slot] = getItemId("bucket_empty");
-                        break;
-                    case "bucket_lava":
-                        mGameWorld.placeToForeground(x, y, getBlockId("lava"));
-                        mPlayer.inventory[mPlayer.slot] = getItemId("bucket_empty");
-                        break;
-                }
-            }
-        }
-    }
-
-    private void pressLMB() {
-        if (mMainConfig.checkGameUiWindow(GameUiWindow.NONE) &&
-                ((mGameWorld.hasForeAt(mCurX, mCurY) && mGameWorld.getForeMapBlock(mCurX, mCurY).getHp() >= 0) ||
-                        (!mGameWorld.hasForeAt(mCurX, mCurY) && mGameWorld.hasBackAt(mCurX, mCurY) &&
-                                mGameWorld.getBackMapBlock(mCurX, mCurY).getHp() >= 0))) {
-            if (mPlayer.gameMode == 0) {
-                mBlockDamage++;
-                if (mGameWorld.hasForeAt(mCurX, mCurY)) {
-                    if (mBlockDamage >= mGameWorld.getForeMapBlock(mCurX, mCurY).getHp()) {
-                        mGameWorld.destroyForeMap(mCurX, mCurY);
-                        mBlockDamage = 0;
-                    }
-                } else if (mGameWorld.hasBackAt(mCurX, mCurY)) {
-                    if (mBlockDamage >= mGameWorld.getBackMapBlock(mCurX, mCurY).getHp()) {
-                        mGameWorld.destroyBackMap(mCurX, mCurY);
-                        mBlockDamage = 0;
-                    }
-                }
-            } else {
-                if (mGameWorld.hasForeAt(mCurX, mCurY)) {
-                    mGameWorld.placeToForeground(mCurX, mCurY, 0);
-                } else if (mGameWorld.hasBackAt(mCurX, mCurY)) {
-                    mGameWorld.placeToBackground(mCurX, mCurY, 0);
-                }
-                mTouchedDown = false;
-            }
-        }
-    }
-
-    private boolean insideHotbar(float x, float y) {
-        TextureRegion hotbar = Assets.textureRegions.get("hotbar");
-        return y < hotbar.getRegionHeight() &&
-                Range.open(mMainConfig.getWidth() / 2 - (float) hotbar.getRegionWidth() / 2,
-                        mMainConfig.getWidth() / 2 + (float) hotbar.getRegionWidth() / 2).contains(x);
-    }
-
-    private void holdMB() {
-        if (mTouchDownBtn == Input.Buttons.RIGHT) {
-            useItem(mCurX, mCurY, mPlayer.inventory[mPlayer.slot], true);
-            mTouchedDown = false;
-        } else {
-            if (insideHotbar(mTouchDownX, mTouchDownY)) {
-                mMainConfig.setGameUiWindow(GameUiWindow.CREATIVE_INVENTORY);
-                mTouchedDown = false;
-            }
-        }
-    }
-
-    public void keyDown(int keycode) {
-        mKeyDown = true;
-        mKeyDownCode = keycode;
-        switch (keycode) {
-            case Input.Keys.A:
-            case Input.Keys.D:
-            case Input.Keys.W:
-            case Input.Keys.S:
-            case Input.Keys.SPACE:
-            case Input.Keys.CONTROL_LEFT:
-                wasdPressed(keycode);
-                break;
-
-            case Input.Keys.ALT_LEFT:
-                if (mMainConfig.isTouch()) {
-                    mControlMode = mControlMode == ControlMode.WALK ? ControlMode.CURSOR : ControlMode.WALK;
-                }
-                break;
-
-            case Input.Keys.E:
-                if (mMainConfig.checkGameUiWindow(GameUiWindow.NONE)) {
-                    switch (mPlayer.gameMode) {
-                        case 0:
-                            //TODO survival inv
-                            break;
-                        case 1:
-                            mMainConfig.setGameUiWindow(GameUiWindow.CREATIVE_INVENTORY);
-                            break;
-                    }
-                } else {
-                    mMainConfig.setGameUiWindow(GameUiWindow.NONE);
-                }
-                break;
-
-            case Input.Keys.G:
-                mMobsController.addMob(Pig.class, mCurX * 16, mCurY * 16);
-                break;
-
-            case Input.Keys.Q:
-                mGameWorld.placeToForeground(mCurX, mCurY, 8);
-                break;
-
-            case Input.Keys.ESCAPE:
-            case Input.Keys.BACK:
-                GameSaver.save(mMainConfig, mDropController, mMobsController, mGameWorld);
-                mMainConfig.getCaveGame().quitGame();
-                break;
-
-            case Input.Keys.F1:
-                mMainConfig.setShowInfo(!mMainConfig.isShowInfo());
-                break;
-
-            case Input.Keys.M:
-                mMainConfig.setShowMap(!mMainConfig.isShowMap());
-                break;
-        }
-    }
-
-    public void keyUp(int keycode) {
-        switch (keycode) {
-            case Input.Keys.A:
-            case Input.Keys.D:
-                mPlayer.getMove().x = 0;
-                if (mMainConfig.isTouch() && mPlayer.swim) {
-                    mPlayer.swim = false;
-                }
-                break;
-
-            case Input.Keys.W:
-            case Input.Keys.S:
-            case Input.Keys.SPACE:
-            case Input.Keys.CONTROL_LEFT:
-                if (mPlayer.isFlyMode()) {
-                    mPlayer.getMove().y = 0;
-                }
-                if (mPlayer.swim) {
-                    mPlayer.swim = false;
-                }
-                break;
-        }
-    }
-
-    public void touchDown(float touchX, float touchY, int button) {
-        mTouchDownTime = TimeUtils.millis();
-        mTouchedDown = true;
-        mTouchDownBtn = button;
-        mTouchDownX = touchX;
-        mTouchDownY = touchY;
-    }
-
-    public void touchUp(float screenX, float screenY, int button) {
-        if (mDragging) {
-            mDragging = false;
-            return;
-        }
-
-        if (mMainConfig.isTouch() && mKeyDown) {
-            keyUp(mKeyDownCode);
-            mKeyDown = false;
-        }
-        TextureRegion hotbar = Assets.textureRegions.get("hotbar");
-        TextureRegion creative = Assets.textureRegions.get("creative");
-        if (mTouchedDown) {
-            if (mMainConfig.checkGameUiWindow(GameUiWindow.CREATIVE_INVENTORY) && insideCreativeInv(screenX, screenY)) {
-                int ix = (int) (screenX - (mMainConfig.getWidth() / 2 - creative.getRegionWidth() / 2 + 8)) / 18;
-                int iy = (int) (screenY - (mMainConfig.getHeight() / 2 - creative.getRegionHeight() / 2 + 18)) / 18;
-                int item = mCreativeScroll * 8 + (ix + iy * 8);
-                if (ix >= 8 || ix < 0 || iy < 0 || iy >= 5) {
-                    item = -1;
-                }
-                if (item >= 0 && item < GameItems.getItemsSize()) {
-                    System.arraycopy(mPlayer.inventory, 0, mPlayer.inventory, 1, 8);
-                    mPlayer.inventory[0] = item;
-                }
-            } else if (mMainConfig.checkGameUiWindow(GameUiWindow.CREATIVE_INVENTORY)) {
-                mMainConfig.setGameUiWindow(GameUiWindow.NONE);
-            } else if (screenY < hotbar.getRegionHeight() &&
-                    screenX > mMainConfig.getWidth() / 2 - (float) hotbar.getRegionWidth() / 2 &&
-                    screenX < mMainConfig.getWidth() / 2 + (float) hotbar.getRegionWidth() / 2) {
-                mPlayer.slot = (int) ((screenX - (mMainConfig.getWidth() / 2 - hotbar.getRegionWidth() / 2)) / 20);
-            } else if (button == Input.Buttons.RIGHT) {
-                useItem(mCurX, mCurY,
-                        mPlayer.inventory[mPlayer.slot], false);
-            } else if (button == Input.Buttons.LEFT) {
-                mBlockDamage = 0;
-            }
-        }
-        mTouchedDown = false;
-    }
-
-    public void touchDragged(float screenX, float screenY) {
-        mDragging = true;
-        if (mMainConfig.checkGameUiWindow(GameUiWindow.CREATIVE_INVENTORY) && Math.abs(screenY - mTouchDownY) > 16) {
-            if (insideCreativeInv(screenX, screenY)) {
-                mCreativeScroll -= (screenY - mTouchDownY) / 16;
-                mTouchDownX = screenX;
-                mTouchDownY = screenY;
-                if (mCreativeScroll < 0) {
-                    mCreativeScroll = 0;
-                }
-                if (mCreativeScroll > GameProc.MAX_CREATIVE_SCROLL) {
-                    mCreativeScroll = GameProc.MAX_CREATIVE_SCROLL;
-                }
-            }
-        }
-    }
-
-    public void scrolled(int amount) {
-        switch (mMainConfig.getGameUiWindow()) {
-            case NONE:
-                mPlayer.slot += amount;
-                if (mPlayer.slot < 0) {
-                    mPlayer.slot = 8;
-                }
-                if (mPlayer.slot > 8) {
-                    mPlayer.slot = 0;
-                }
-                break;
-            case CREATIVE_INVENTORY:
-                mCreativeScroll += amount;
-                if (mCreativeScroll < 0) {
-                    mCreativeScroll = 0;
-                }
-                if (mCreativeScroll > GameProc.MAX_CREATIVE_SCROLL) {
-                    mCreativeScroll = GameProc.MAX_CREATIVE_SCROLL;
-                }
-                break;
-        }
-    }
-
-    public int getKeyDownCode() {
-        return mKeyDownCode;
-    }
-
-    public boolean isKeyDown() {
-        return mKeyDown;
-    }
-
-    int getBlockDamage() {
-        return mBlockDamage;
-    }
-
-    int getCurX() {
-        return mCurX;
-    }
-
-    int getCurY() {
-        return mCurY;
-    }
-
-    int getCreativeScroll() {
-        return mCreativeScroll;
-    }
-
-    public ControlMode getControlMode() {
-        return mControlMode;
-    }
-
-    public void setControlMode(ControlMode controlMode) {
-        mControlMode = controlMode;
-    }
-
-    void update() {
-        if (mTouchedDown && mTouchDownBtn == Input.Buttons.LEFT) {
-            pressLMB();
-        }
-        if (mTouchedDown && TimeUtils.timeSinceMillis(mTouchDownTime) > 500) {
-            holdMB();
-        }
-    }
-
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameInputProcessor.java b/core/src/ru/deadsoftware/cavedroid/game/GameInputProcessor.java
deleted file mode 100644 (file)
index 51cc897..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-package ru.deadsoftware.cavedroid.game;
-
-import com.badlogic.gdx.Gdx;
-import com.badlogic.gdx.Input;
-import com.badlogic.gdx.InputAdapter;
-import com.badlogic.gdx.math.Rectangle;
-import com.badlogic.gdx.utils.JsonValue;
-import ru.deadsoftware.cavedroid.MainConfig;
-import ru.deadsoftware.cavedroid.game.objects.TouchButton;
-import ru.deadsoftware.cavedroid.misc.Assets;
-
-import javax.inject.Inject;
-
-import static com.badlogic.gdx.utils.ObjectMap.Entry;
-
-@GameScope
-public class GameInputProcessor extends InputAdapter {
-
-    private static final TouchButton nullButton = new TouchButton(null, -1, true);
-
-    private final GameInput mGameInput;
-    private final GameRenderer mGameRenderer;
-    private final MainConfig mMainConfig;
-
-    @Inject
-    public GameInputProcessor(GameInput gameInput,
-                              GameRenderer gameRenderer,
-                              MainConfig mainConfig) {
-        mGameInput = gameInput;
-        mGameRenderer = gameRenderer;
-        mMainConfig = mainConfig;
-
-        loadTouchButtonsFromJSON();
-    }
-
-    private int getMouseKey(String name) {
-        switch (name) {
-            case "Left":
-                return Input.Buttons.LEFT;
-            case "Right":
-                return Input.Buttons.RIGHT;
-            case "Middle":
-                return Input.Buttons.MIDDLE;
-            case "Back":
-                return Input.Buttons.BACK;
-            case "Forward":
-                return Input.Buttons.FORWARD;
-            default:
-                return -1;
-        }
-    }
-
-    private void loadTouchButtonsFromJSON() {
-        JsonValue json = Assets.jsonReader.parse(Gdx.files.internal("json/touch_buttons.json"));
-        for (JsonValue key = json.child(); key != null; key = key.next()) {
-            float x = key.getFloat("x");
-            float y = key.getFloat("y");
-            float w = key.getFloat("w");
-            float h = key.getFloat("h");
-            boolean mouse = Assets.getBooleanFromJson(key, "mouse", false);
-            String name = key.getString("key");
-            int code = mouse ? getMouseKey(name) : Input.Keys.valueOf(name);
-            if (x < 0) {
-                x = mGameRenderer.getWidth() + x;
-            }
-            if (y < 0) {
-                y = mGameRenderer.getHeight() + y;
-            }
-            Assets.guiMap.put(key.name(), new TouchButton(new Rectangle(x, y, w, h), code, mouse));
-        }
-
-    }
-
-    private float transformScreenX(int screenX) {
-        return mGameRenderer.getWidth() / Gdx.graphics.getWidth() * screenX;
-    }
-
-    private float transformScreenY(int screenY) {
-        return mGameRenderer.getHeight() / Gdx.graphics.getHeight() * screenY;
-    }
-
-    private TouchButton getTouchedKey(float touchX, float touchY) {
-        for (Entry<String, TouchButton> entry : Assets.guiMap) {
-            TouchButton button = entry.value;
-            if (button.getRect().contains(touchX, touchY)) {
-                return button;
-            }
-        }
-        return nullButton;
-    }
-
-    @Override
-    public boolean keyDown(int keycode) {
-        mGameInput.keyDown(keycode);
-        return false;
-    }
-
-    @Override
-    public boolean keyUp(int keycode) {
-        mGameInput.keyUp(keycode);
-        return false;
-    }
-
-    @Override
-    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
-        float touchX = transformScreenX(screenX);
-        float touchY = transformScreenY(screenY);
-
-        if (mMainConfig.isTouch()) {
-            TouchButton touchedKey = getTouchedKey(touchX, touchY);
-            if (touchedKey.isMouse()) {
-                mGameInput.touchDown(touchX, touchY, touchedKey.getCode());
-            } else {
-                mGameInput.keyDown(touchedKey.getCode());
-            }
-        } else {
-            mGameInput.touchDown(touchX, touchY, button);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
-        float touchX = transformScreenX(screenX);
-        float touchY = transformScreenY(screenY);
-
-        if (mMainConfig.isTouch()) {
-            TouchButton touchedKey = getTouchedKey(touchX, touchY);
-            if (touchedKey.isMouse()) {
-                mGameInput.touchUp(touchX, touchY, touchedKey.getCode());
-            } else {
-                mGameInput.keyUp(mGameInput.getKeyDownCode());
-            }
-        } else {
-            mGameInput.touchUp(touchX, touchY, button);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean touchDragged(int screenX, int screenY, int pointer) {
-        float touchX = transformScreenX(screenX);
-        float touchY = transformScreenY(screenY);
-        if (mMainConfig.isTouch() && mGameInput.isKeyDown()) {
-            if (getTouchedKey(touchX, touchY).getCode() == -1) {
-                mGameInput.keyUp(mGameInput.getKeyDownCode());
-            }
-        } else {
-            mGameInput.touchDragged(touchX, touchY);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean scrolled(int amount) {
-        mGameInput.scrolled(amount);
-        return false;
-    }
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameItems.java b/core/src/ru/deadsoftware/cavedroid/game/GameItems.java
deleted file mode 100644 (file)
index 85f4b9c..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-package ru.deadsoftware.cavedroid.game;
-
-import com.badlogic.gdx.Gdx;
-import com.badlogic.gdx.graphics.Texture;
-import com.badlogic.gdx.graphics.g2d.Sprite;
-import com.badlogic.gdx.utils.ArrayMap;
-import com.badlogic.gdx.utils.GdxRuntimeException;
-import com.badlogic.gdx.utils.JsonValue;
-import ru.deadsoftware.cavedroid.game.objects.Block;
-import ru.deadsoftware.cavedroid.game.objects.Item;
-import ru.deadsoftware.cavedroid.misc.Assets;
-
-import java.util.HashMap;
-
-public class GameItems {
-
-    private static final String TAG = "GameItems";
-
-    private static final HashMap<String, Integer> blocksIds = new HashMap<>();
-    private static final HashMap<String, Integer> itemsIds = new HashMap<>();
-
-    private static final ArrayMap<String, Block> blocks = new ArrayMap<>();
-    private static final ArrayMap<String, Item> items = new ArrayMap<>();
-
-    public static boolean isFluid(int id) {
-        return getBlock(id).isFluid();
-    }
-
-    public static boolean isWater(int id) {
-        return getBlock(id).getMeta().equals("water");
-    }
-
-    public static boolean isLava(int id) {
-        return getBlock(id).getMeta().equals("lava");
-    }
-
-    public static boolean isSlab(int id) {
-        return getBlock(id).getMeta().equals("slab");
-    }
-
-    public static boolean fluidCanFlowThere(int thisId, int thatId) {
-        return thatId == 0 || (!getBlock(thatId).hasCollision() && !isFluid(thatId)) ||
-                (isWater(thisId) && isWater(thatId) && thisId < thatId) ||
-                (isLava(thisId) && isLava(thatId) && thisId < thatId);
-    }
-
-    public static Block getBlock(int id) {
-        return blocks.getValueAt(id);
-    }
-
-    public static Item getItem(int id) {
-        return items.getValueAt(id);
-    }
-
-    public static Block getBlock(String key) {
-        return blocks.getValueAt(blocksIds.get(key));
-    }
-
-    public static Item getItem(String key) {
-        return items.getValueAt(itemsIds.get(key));
-    }
-
-    public static int getBlockId(String key) {
-        return blocksIds.get(key);
-    }
-
-    public static int getItemId(String key) {
-        return itemsIds.get(key);
-    }
-
-    public static String getBlockKey(int id) {
-        return blocks.getKeyAt(id);
-    }
-
-    public static String getItemKey(int id) {
-        return items.getKeyAt(id);
-    }
-
-    public static int getBlockIdByItemId(int id) {
-        return getBlockId(items.getKeyAt(id));
-    }
-
-    public static int getBlocksSize() {
-        return blocks.size;
-    }
-
-    public static int getItemsSize() {
-        return items.size;
-    }
-
-    public static Sprite getBlockTex(int id) {
-        return getBlock(id).getTexture();
-    }
-
-    public static Sprite getItemTex(int id) {
-        return items.getValueAt(id).getType().equals("block") ? getBlockTex(id) : getItem(id).getTexture();
-    }
-
-    public static void load() {
-        JsonValue json = Assets.jsonReader.parse(Gdx.files.internal("json/game_items.json"));
-        for (JsonValue block = json.get("blocks").child(); block != null; block = block.next()) {
-            try {
-                String key = block.name();
-                int left = Assets.getIntFromJson(block, "left", 0);
-                int right = Assets.getIntFromJson(block, "right", 0);
-                int top = Assets.getIntFromJson(block, "top", 0);
-                int bottom = Assets.getIntFromJson(block, "bottom", 0);
-                int clipX = Assets.getIntFromJson(block, "sprite_left", 0);
-                int clipY = Assets.getIntFromJson(block, "sprite_top", 0);
-                int clipWidth = Assets.getIntFromJson(block, "sprite_right", 0);
-                int clipHeight = Assets.getIntFromJson(block, "sprite_bottom", 0);
-                int hp = Assets.getIntFromJson(block, "hp", -1);
-                boolean collision = Assets.getBooleanFromJson(block, "collision", true);
-                boolean background = Assets.getBooleanFromJson(block, "background", false);
-                boolean transparent = Assets.getBooleanFromJson(block, "transparent", false);
-                boolean requiresBlock = Assets.getBooleanFromJson(block, "block_required", false);
-                boolean fluid = Assets.getBooleanFromJson(block, "fluid", false);
-                String drop = Assets.getStringFromJson(block, "drop", key);
-                String meta = Assets.getStringFromJson(block, "meta", "");
-                String tex = Assets.getStringFromJson(block, "texture", key);
-                Texture texture = tex.equals("none") ? null :
-                        new Texture(Gdx.files.internal("textures/blocks/" + tex + ".png"));
-                boolean animated = Assets.getBooleanFromJson(block, "animated", false);
-                int frames = Assets.getIntFromJson(block, "frames", 0);
-
-                Block newBlock = new Block(
-                        left,
-                        top,
-                        right,
-                        bottom,
-                        hp,
-                        drop,
-                        collision,
-                        background,
-                        transparent,
-                        requiresBlock,
-                        fluid,
-                        meta,
-                        texture,
-                        animated,
-                        frames,
-                        clipX,
-                        clipY,
-                        clipWidth,
-                        clipHeight
-                );
-
-                blocksIds.put(key, blocks.size);
-                blocks.put(key, newBlock);
-            } catch (GdxRuntimeException e) {
-                Gdx.app.error(TAG, e.getMessage());
-            }
-        }
-        for (JsonValue item = json.get("items").child(); item != null; item = item.next()) {
-            try {
-                String key = item.name();
-                String name = Assets.getStringFromJson(item, "name", key);
-                String type = Assets.getStringFromJson(item, "type", "item");
-                String texture = Assets.getStringFromJson(item, "texture", key);
-                Sprite sprite = type.equals("block") ? null :
-                        new Sprite(new Texture(Gdx.files.internal("textures/items/" + texture + ".png")));
-                itemsIds.put(key, items.size);
-                items.put(key, new Item(name, type, sprite));
-            } catch (GdxRuntimeException e) {
-                Gdx.app.error(TAG, e.getMessage());
-            }
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt b/core/src/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt
new file mode 100644 (file)
index 0000000..832845e
--- /dev/null
@@ -0,0 +1,167 @@
+package ru.deadsoftware.cavedroid.game
+
+import com.badlogic.gdx.Gdx
+import kotlinx.serialization.json.Json
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.craft.CraftingRecipe
+import ru.deadsoftware.cavedroid.game.model.craft.CraftingResult
+import ru.deadsoftware.cavedroid.game.model.dto.BlockDto
+import ru.deadsoftware.cavedroid.game.model.dto.CraftingDto
+import ru.deadsoftware.cavedroid.game.model.dto.GameItemsDto
+import ru.deadsoftware.cavedroid.game.model.dto.ItemDto
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.model.mapper.BlockMapper
+import ru.deadsoftware.cavedroid.game.model.mapper.ItemMapper
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader
+import java.util.LinkedList
+import javax.inject.Inject
+
+@GameScope
+class GameItemsHolder @Inject constructor(
+    private val assetLoader: AssetLoader,
+    private val blockMapper: BlockMapper,
+    private val itemMapper: ItemMapper,
+) {
+
+    private var _initialized: Boolean = false
+
+    private val blocksMap = LinkedHashMap<String, Block>()
+    private val itemsMap = LinkedHashMap<String, Item>()
+    private val craftingRecipes = LinkedList<CraftingRecipe>()
+
+    lateinit var fallbackBlock: Block
+        private set
+    lateinit var fallbackItem: Item
+        private set
+
+    init {
+        initialize()
+    }
+
+    private fun loadBlocks(dtoMap: Map<String, BlockDto>) {
+        dtoMap.forEach { (key, dto) ->
+            blocksMap[key] = blockMapper.map(key, dto)
+                .apply(Block::initialize)
+        }
+
+        fallbackBlock = blocksMap[FALLBACK_BLOCK_KEY]
+            ?: throw IllegalArgumentException("Fallback block key '$FALLBACK_BLOCK_KEY' not found")
+    }
+
+    private fun loadItems(dtoMap: Map<String, ItemDto>) {
+        if (dtoMap.isNotEmpty() && blocksMap.isEmpty()) {
+            throw IllegalStateException("items should be loaded after blocks")
+        }
+
+        dtoMap.forEach { (key, dto) ->
+            try {
+                itemsMap[key] = itemMapper.map(
+                    key = key,
+                    dto = dto,
+                    block = blocksMap[key],
+                    slabTopBlock = blocksMap[dto.topSlabBlock] as? Block.Slab,
+                    slabBottomBlock = blocksMap[dto.bottomSlabBlock] as? Block.Slab
+                )
+            } catch (e: Exception) {
+                Gdx.app.error(TAG, "Failed to map item $key. Reason: ${e.message}")
+                e.printStackTrace()
+            }
+        }
+
+        fallbackItem = itemsMap[FALLBACK_ITEM_KEY]
+            ?: throw IllegalArgumentException("Fallback item key '$FALLBACK_ITEM_KEY' not found")
+    }
+
+    private fun loadCraftingRecipes() {
+        val jsonString = assetLoader.getAssetHandle("json/crafting.json").readString()
+        val jsonMap = JsonFormat.decodeFromString<Map<String, CraftingDto>>(jsonString)
+
+        if (jsonMap.isNotEmpty() && itemsMap.isEmpty()) {
+            throw IllegalStateException("items should be loaded before crafting")
+        }
+
+        jsonMap.forEach { (key, value) ->
+            craftingRecipes += CraftingRecipe(
+                input = value.input.map(::getItem),
+                output = CraftingResult(getItem(key), value.count)
+            )
+        }
+    }
+
+    fun initialize() {
+        if (_initialized) {
+            Gdx.app.debug(TAG, "Attempted to init when already initialized")
+            return
+        }
+
+        val jsonString = assetLoader.getAssetHandle("json/game_items.json").readString()
+        val gameItemsDto = JsonFormat.decodeFromString<GameItemsDto>(jsonString)
+
+        loadBlocks(gameItemsDto.blocks)
+        loadItems(gameItemsDto.items)
+
+        _initialized = true
+
+        loadCraftingRecipes()
+    }
+
+    private fun <T> Map<String, T>.getOrFallback(key: String, fallback: T, lazyErrorMessage: () -> String): T {
+        if (!_initialized) {
+            throw IllegalStateException("GameItemsHolder was not initialized before use")
+        }
+
+        val t = this[key] ?: run {
+            Gdx.app.error(TAG, lazyErrorMessage.invoke())
+            return fallback
+        }
+        return t
+    }
+
+    fun getBlock(key: String): Block {
+        return blocksMap.getOrFallback(key, fallbackBlock) {
+            "No block with key '$key' found. Returning $FALLBACK_BLOCK_KEY"
+        }
+    }
+
+    fun getItem(key: String): Item {
+        return itemsMap.getOrFallback(key, fallbackItem) {
+            "No item with key '$key' found. Returning $FALLBACK_BLOCK_KEY"
+        }
+    }
+
+    fun craftItem(input: List<Item>): InventoryItem? {
+        return  try {
+            craftingRecipes.first { rec -> rec.input == input}.output.toInventoryItem()
+        } catch (e: NoSuchElementException) {
+            null
+        }
+    }
+
+    fun getAllItems(): Collection<Item> {
+        return itemsMap.values
+    }
+
+    fun getItemFromCreativeInventory(position: Int): Item {
+        return if (position in itemsMap.values.indices) {
+            itemsMap.values.elementAt(position)
+        } else {
+            fallbackItem
+        }
+    }
+
+    fun getMaxCreativeScrollAmount(): Int = itemsMap.size / 8
+
+    fun <T : Block> getBlocksByType(type: Class<T>): List<T> {
+        return blocksMap.values.filterIsInstance(type)
+    }
+
+    companion object {
+        private const val TAG = "GameItemsHolder"
+
+        private val JsonFormat = Json { ignoreUnknownKeys = true }
+
+        const val FALLBACK_BLOCK_KEY = "none"
+        const val FALLBACK_ITEM_KEY = "none"
+    }
+}
\ No newline at end of file
index d7ccc2d6e44bb7744aa6fa8f84cafd4fcd439a55..29175da1968fba60e36f64a8ebb8ec746c73b630 100644 (file)
@@ -4,7 +4,9 @@ import dagger.Module;
 import dagger.Provides;
 import ru.deadsoftware.cavedroid.MainConfig;
 import ru.deadsoftware.cavedroid.game.mobs.MobsController;
+import ru.deadsoftware.cavedroid.game.model.block.Block;
 import ru.deadsoftware.cavedroid.game.objects.DropController;
+import ru.deadsoftware.cavedroid.game.world.GameWorld;
 
 import javax.annotation.CheckForNull;
 
@@ -14,8 +16,14 @@ public class GameModule {
     @CheckForNull
     private static GameSaver.Data data;
 
-    public static void load(MainConfig mainConfig) {
-        data = GameSaver.load(mainConfig);
+    public static boolean loaded = false;
+
+    private static void load(MainConfig mainConfig, GameItemsHolder gameItemsHolder) {
+        if (loaded) {
+            return;
+        }
+        data = GameSaver.load(mainConfig, gameItemsHolder);
+        loaded = true;
     }
 
     private static void makeDataNullIfEmpty() {
@@ -26,27 +34,35 @@ public class GameModule {
 
     @Provides
     @GameScope
-    public static DropController provideDropController() {
+    public static DropController provideDropController(MainConfig mainConfig, GameItemsHolder gameItemsHolder) {
+        load(mainConfig, gameItemsHolder);
         DropController controller = data != null ? data.retrieveDropController() : new DropController();
         makeDataNullIfEmpty();
+        controller.initDrops(gameItemsHolder);
         return controller;
     }
 
     @Provides
     @GameScope
-    public static MobsController provideMobsController() {
-        MobsController controller = data != null ? data.retrieveMobsController() : new MobsController();
+    public static MobsController provideMobsController(MainConfig mainConfig, GameItemsHolder gameItemsHolder) {
+        load(mainConfig, gameItemsHolder);
+        MobsController controller = data != null ? data.retrieveMobsController() : new MobsController(gameItemsHolder);
         makeDataNullIfEmpty();
+        controller.getPlayer().initInventory(gameItemsHolder);
         return controller;
     }
 
     @Provides
     @GameScope
-    public static GameWorld provideGameWorld(DropController dropController, MobsController mobsController) {
-        int[][] fm = data != null ? data.retrieveForeMap() : null;
-        int[][] bm = data != null ? data.retrieveBackMap() : null;
+    public static GameWorld provideGameWorld(MainConfig mainConfig,
+                                             DropController dropController,
+                                             MobsController mobsController,
+                                             GameItemsHolder gameItemsHolder) {
+        load(mainConfig, gameItemsHolder);
+        Block[][] fm = data != null ? data.retrieveForeMap() : null;
+        Block[][] bm = data != null ? data.retrieveBackMap() : null;
         makeDataNullIfEmpty();
-        return new GameWorld(dropController, mobsController, fm, bm);
+        return new GameWorld(dropController, mobsController, gameItemsHolder, fm, bm);
     }
 
 }
index d6b5a9aa5da8e907c07430e1670cce350a61a757..a0c8c378ef3c35e6006e99dab47b192fb669d75c 100644 (file)
@@ -8,34 +8,41 @@ import ru.deadsoftware.cavedroid.MainConfig;
 import ru.deadsoftware.cavedroid.game.mobs.Mob;
 import ru.deadsoftware.cavedroid.game.mobs.MobsController;
 import ru.deadsoftware.cavedroid.game.mobs.Player;
+import ru.deadsoftware.cavedroid.game.model.block.Block;
 import ru.deadsoftware.cavedroid.game.objects.Drop;
 import ru.deadsoftware.cavedroid.game.objects.DropController;
+import ru.deadsoftware.cavedroid.game.world.GameWorld;
 
+import javax.annotation.CheckForNull;
 import javax.inject.Inject;
 import java.util.Iterator;
 
 
 @GameScope
-class GamePhysics {
+public class GamePhysics {
 
-    static final int PL_SPEED = 2;
+    public static final float PL_JUMP_VELOCITY = -133.332f;
+    public static final float PL_TERMINAL_VELOCITY = 1254.4f;
 
-    private final Vector2 gravity = new Vector2(0, .9f);
+    private final Vector2 gravity = new Vector2(0, 444.44f);
 
     private final GameWorld mGameWorld;
     private final MainConfig mMainConfig;
     private final MobsController mMobsController;
     private final DropController mDropController;
+    private final GameItemsHolder mGameItemsHolder;
 
     @Inject
     public GamePhysics(GameWorld gameWorld,
                        MainConfig mainConfig,
                        MobsController mobsController,
-                       DropController dropController) {
+                       DropController dropController,
+                       GameItemsHolder gameItemsHolder) {
         mGameWorld = gameWorld;
         mMainConfig = mainConfig;
         mMobsController = mobsController;
         mDropController = dropController;
+        mGameItemsHolder = gameItemsHolder;
     }
 
     /**
@@ -47,14 +54,14 @@ class GamePhysics {
         int dir = mob.looksLeft() ? 0 : 1;
         int blX = (int) (mob.getX() + mob.getWidth() * dir - 8 + 16 * dir);
         int blY = (int) (mob.getY() + mob.getHeight() - 8);
-        int block = mGameWorld.getForeMap(blX / 16, blY / 16);
+        Block block = mGameWorld.getForeMap(blX / 16, blY / 16);
 
         if (checkColl(new Rectangle(blX, mob.getY() - 18, mob.getWidth(), mob.getHeight()))) {
-            block = 0;
+            return false;
         }
 
-        return (block > 0 && GameItems.getBlock(block).toJump() &&
-                (mob.getY() + mob.getHeight()) - GameItems.getBlock(block).getRectangle(blX / 16, blY / 16).y > 8);
+        return (block.toJump() &&
+                (mob.getY() + mob.getHeight()) - block.getRectangle(blX / 16, blY / 16).y > 8);
     }
 
     private boolean checkColl(Rectangle rect) {
@@ -71,12 +78,15 @@ class GamePhysics {
             maxY = mGameWorld.getHeight();
         }
 
-        int block;
+        Block block;
         for (int y = minY; y < maxY; y++) {
             for (int x = minX; x < maxX; x++) {
+                if (!mGameWorld.hasForeAt(x, y)) {
+                    continue;
+                }
                 block = mGameWorld.getForeMap(x, y);
-                if (block > 0 && GameItems.getBlock(block).hasCollision()) {
-                    if (Intersector.overlaps(rect, GameItems.getBlock(block).getRectangle(x, y))) {
+                if (block.hasCollision()) {
+                    if (Intersector.overlaps(rect, block.getRectangle(x, y))) {
                         return true;
                     }
                 }
@@ -86,36 +96,77 @@ class GamePhysics {
         return false;
     }
 
-    private int getBlock(Rectangle rect) {
+    private Block getBlock(Rectangle rect) {
         return mGameWorld.getForeMap((int) (rect.x + rect.width / 2) / 16,
                 (int) (rect.y + rect.height / 8 * 7) / 16);
     }
 
-    private void dropPhy(Drop drop) {
-        int dropToPlayer = drop.closeToPlayer(mGameWorld, mMobsController.getPlayer());
-        if (dropToPlayer > 0) {
-            drop.moveToPlayer(mGameWorld, mMobsController.getPlayer(), dropToPlayer);
+    private Rectangle getShiftedPlayerRect(float shift) {
+        final Player player = mMobsController.getPlayer();
+        return new Rectangle(player.x + shift, player.y, player.width, player.height);
+    }
+
+    /**
+     * @return Rectangle representing magneting target for this drop
+     */
+    @CheckForNull
+    private Rectangle getShiftedMagnetingPlayerRect(Drop drop) {
+        final Player player = mMobsController.getPlayer();
+
+        if (drop.canMagnetTo(player)) {
+            return getShiftedPlayerRect(0);
+        }
+
+        final Rectangle shiftedLeft = getShiftedPlayerRect(-mGameWorld.getWidthPx());
+        if (drop.canMagnetTo(shiftedLeft)) {
+            return shiftedLeft;
+        }
+
+        final Rectangle shiftedRight = getShiftedPlayerRect(mGameWorld.getWidthPx());
+        if (drop.canMagnetTo(shiftedRight)) {
+            return shiftedRight;
+        }
+
+        return null;
+    }
+
+    private void pickUpDropIfPossible(Rectangle shiftedPlayerTarget, Drop drop) {
+        final Player player = mMobsController.getPlayer();
+
+        if (Intersector.overlaps(shiftedPlayerTarget, drop)) {
+            player.pickUpDrop(drop);
+        }
+    }
+
+    private void dropPhy(Drop drop, float delta) {
+        final Rectangle playerMagnetTarget = getShiftedMagnetingPlayerRect(drop);
+        final Vector2 dropVelocity = drop.getVelocity();
+
+
+        if (playerMagnetTarget != null) {
+            final Vector2 magnetVector = new Vector2(playerMagnetTarget.x - drop.x,
+                    playerMagnetTarget.y - drop.y);
+            magnetVector.nor().scl(Drop.MAGNET_VELOCITY * delta);
+            dropVelocity.add(magnetVector);
         } else {
-            if (drop.getMove().x >= .5f) {
-                drop.getMove().x -= .5f;
-            } else if (drop.getMove().x <= -.5f) {
-                drop.getMove().x += .5f;
-            } else {
-                drop.getMove().x = 0;
-            }
-            if (drop.getMove().y < 9) {
-                drop.getMove().y += gravity.y / 4;
-            }
+            dropVelocity.y += gravity.y * delta;
         }
-        drop.move();
 
+        dropVelocity.x = MathUtils.clamp(dropVelocity.x, -Drop.MAGNET_VELOCITY, Drop.MAGNET_VELOCITY);
+        dropVelocity.y = MathUtils.clamp(dropVelocity.y, -Drop.MAGNET_VELOCITY, Drop.MAGNET_VELOCITY);
+
+        drop.x += dropVelocity.x * delta;
+        drop.y += dropVelocity.y * delta;
 
         if (checkColl(drop)) {
-            drop.getMove().set(0, -1);
+            dropVelocity.setZero();
             do {
-                drop.move();
+                drop.y--;
             } while (checkColl(drop));
-            drop.getMove().setZero();
+        }
+
+        if (playerMagnetTarget != null) {
+            pickUpDropIfPossible(playerMagnetTarget, drop);
         }
     }
 
@@ -132,9 +183,9 @@ class GamePhysics {
 
                 int d = 0;
 
-                if (mob.getMove().x < 0) {
+                if (mob.getVelocity().x < 0) {
                     d = 1;
-                } else if (mob.getMove().x > 0) {
+                } else if (mob.getVelocity().x > 0) {
                     d = -1;
                 }
 
@@ -157,13 +208,18 @@ class GamePhysics {
         if (checkColl(mob)) {
             int d = -1;
 
-            if (mob.getMove().y < 0) {
+            if (mob.getVelocity().y < 0) {
                 d = 1;
             }
 
             if (d == -1) {
                 mob.setCanJump(true);
                 mob.setFlyMode(false);
+
+                int dmg = ((int)Math.max(0f, (((mob.getVelocity().y * mob.getVelocity().y) / (2 * gravity.y)) - 48f) / 16f));
+                if (dmg > 0) {
+                    mob.damage(dmg);
+                }
             }
 
             mob.y = MathUtils.round(mob.getY());
@@ -172,10 +228,12 @@ class GamePhysics {
                 mob.y += d;
             }
 
-            mob.getMove().y = 0;
+            mob.getVelocity().y = 0;
 
         } else {
-            mob.setCanJump(false);
+            mob.y += 1;
+            mob.setCanJump(checkColl(mob));
+            mob.y -= 1;
         }
 
         if (mob.getY() > mGameWorld.getHeightPx()) {
@@ -183,105 +241,103 @@ class GamePhysics {
         }
     }
 
-    private void playerPhy(Player player) {
-        player.y += player.getMove().y;
-        mobYColl(player);
-
+    private void playerPhy(Player player, float delta) {
         if (player.isDead()) {
             return;
         }
 
-        if (GameItems.isFluid(getBlock(player))) {
-            if (mMainConfig.isTouch() && player.getMove().x != 0 && !player.swim && !player.isFlyMode()) {
+        if (getBlock(player).isFluid()) {
+            if (mMainConfig.isTouch() && player.getVelocity().x != 0 && !player.swim && !player.isFlyMode()) {
                 player.swim = true;
             }
             if (!player.swim) {
-                if (!player.isFlyMode() && player.getMove().y < 4.5f) {
-                    player.getMove().add(gravity.x / 4, gravity.y / 4);
+                if (!player.isFlyMode() && player.getVelocity().y < 32f) {
+                    player.getVelocity().y += gravity.y * delta;
                 }
-                if (!player.isFlyMode() && player.getMove().y > 4.5f) {
-                    player.getMove().add(0, -1f);
+                if (!player.isFlyMode() && player.getVelocity().y > 32f) {
+                    player.getVelocity().y -= player.getVelocity().y * 32f * delta;
                 }
             } else {
-                player.getMove().add(0, -.5f);
-                if (player.getMove().y < -3) {
-                    player.getMove().y = -3;
+                player.getVelocity().y += PL_JUMP_VELOCITY * delta;
+                if (player.getVelocity().y < -player.getSpeed()) {
+                    player.getVelocity().y = -player.getSpeed();
                 }
             }
         } else {
-            if (!player.isFlyMode() && player.getMove().y < 18) {
-                player.getMove().add(gravity);
+            if (!player.isFlyMode() && player.getVelocity().y < PL_TERMINAL_VELOCITY) {
+                player.getVelocity().y += gravity.y * delta;
             }
         }
 
-        player.x += player.getMove().x * (player.isFlyMode() ? 1.5f : 1) *
-                (GameItems.isFluid(getBlock(player)) && !player.isFlyMode() ? .8f : 1);
+        player.y += player.getVelocity().y * delta;
+        mobYColl(player);
+
+        player.x += player.getVelocity().x * (player.isFlyMode() ? 1.5f : 1) *
+                (getBlock(player).isFluid() && !player.isFlyMode() ? .8f : 1) * delta;
 
         mobXColl(player);
 
-        if (mMainConfig.isTouch() && !player.isFlyMode() && player.canJump() && player.getMove().x != 0 && checkJump(player)) {
-            player.getMove().add(0, -8);
+        if (mMainConfig.isTouch() && !player.isFlyMode() && player.canJump() && player.getVelocity().x != 0 && checkJump(player)) {
+            player.jump();
             player.setCanJump(false);
         }
     }
 
-    private void mobPhy(Mob mob) {
-        if (mob.getType() == Mob.Type.MOB && GameItems.isFluid(getBlock(mob))) {
-            if (mob.getMove().y > 9) {
-                mob.getMove().add(0, -.9f);
+    private void mobPhy(Mob mob, float delta) {
+        if (mob.getType() == Mob.Type.MOB && getBlock(mob).isFluid()) {
+            if (mob.getVelocity().y > 32f) {
+                mob.getVelocity().y -= mob.getVelocity().y * 32f * delta;
             }
 
-            mob.getMove().add(0, -.5f);
+            mob.getVelocity().y += PL_JUMP_VELOCITY * delta;
 
-            if (mob.getMove().y < -3) {
-                mob.getMove().y = -3;
+            if (mob.getVelocity().y < -mob.getSpeed()) {
+                mob.getVelocity().y = -mob.getSpeed();
             }
-        } else if (!mob.isFlyMode() && mob.getMove().y < 18) {
-            mob.getMove().add(gravity);
+        } else if (!mob.isFlyMode() && mob.getVelocity().y < PL_TERMINAL_VELOCITY) {
+            mob.getVelocity().y += gravity.y * delta;
         }
 
-        mob.y += mob.getMove().y;
+        mob.y += mob.getVelocity().y * delta;
         mobYColl(mob);
 
         if (mob.isDead()) {
             return;
         }
 
-        mob.x += mob.getMove().x;
+        mob.x += mob.getVelocity().x * delta;
         mobXColl(mob);
 
-        if (mob.canJump() && mob.getMove().x != 0 && checkJump(mob)) {
-            mob.getMove().add(0, -8);
+        if (mob.canJump() && mob.getVelocity().x != 0 && checkJump(mob)) {
+            mob.jump();
             mob.setCanJump(false);
         }
     }
 
-    void update() {
+    void update(float delta) {
         Player player = mMobsController.getPlayer();
 
         for (Iterator<Drop> it = mDropController.getIterator(); it.hasNext(); ) {
             Drop drop = it.next();
-            dropPhy(drop);
-            if (Intersector.overlaps(drop, player)) {
-                drop.pickUpDrop(player);
-            }
-            if (drop.isPickedUp()) {
+            dropPhy(drop, delta);
+            if (drop.getPickedUp()) {
                 it.remove();
             }
         }
 
-        for (Iterator<Mob> it = mMobsController.getIterator(); it.hasNext(); ) {
+        for (Iterator<Mob> it = mMobsController.getMobs().iterator(); it.hasNext(); ) {
             Mob mob = it.next();
-            mob.ai(mGameWorld);
-            mobPhy(mob);
+            mob.ai(mGameWorld, mGameItemsHolder, delta);
+            mobPhy(mob, delta);
             if (mob.isDead()) {
                 it.remove();
             }
         }
 
-        playerPhy(player);
+        playerPhy(player, delta);
+        player.ai(mGameWorld, mGameItemsHolder, delta);
         if (player.isDead()) {
-            player.respawn(mGameWorld);
+            player.respawn(mGameWorld, mGameItemsHolder);
         }
     }
 
index 44a46878cff367a12cc83ed0b8b8619be6b16c54..94cd34ba9729bac41f22b0c6df010b5b9ac0c3f9 100644 (file)
@@ -1,41 +1,73 @@
 package ru.deadsoftware.cavedroid.game;
 
+import com.badlogic.gdx.Gdx;
 import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.utils.Timer;
+import ru.deadsoftware.cavedroid.MainConfig;
+import ru.deadsoftware.cavedroid.game.mobs.MobsController;
+import ru.deadsoftware.cavedroid.game.mobs.Player;
+import ru.deadsoftware.cavedroid.game.world.GameWorldBlocksLogicControllerTask;
+import ru.deadsoftware.cavedroid.game.world.GameWorldFluidsLogicControllerTask;
+import ru.deadsoftware.cavedroid.game.world.GameWorldMobDamageControllerTask;
 
 import javax.inject.Inject;
 
 @GameScope
 public class GameProc implements Disposable {
 
-    public static final int MAX_CREATIVE_SCROLL = GameItems.getItemsSize() / 8;
-
-    private final GameWorld mGameWorld;
     private final GamePhysics mGamePhysics;
-    private final GameInput mGameInput;
     private final GameRenderer mGameRenderer;
+    private final MobsController mMobsController;
+    private final GameWorldFluidsLogicControllerTask mGameWorldFluidsLogicControllerTask;
+    private final GameWorldBlocksLogicControllerTask mGameWorldBlocksLogicControllerTask;
+    private final GameWorldMobDamageControllerTask mGameWorldMobDamageControllerTask;
+
+    private final Timer mWorldLogicTimer = new Timer();
 
     @Inject
-    public GameProc(GameWorld gameWorld,
+    public GameProc(MainConfig mainConfig,
                     GamePhysics gamePhysics,
-                    GameInput gameInput,
-                    GameRenderer gameRenderer) {
-        mGameWorld = gameWorld;
+                    GameRenderer gameRenderer,
+                    MobsController mobsController,
+                    GameWorldFluidsLogicControllerTask gameWorldFluidsLogicControllerTask,
+                    GameWorldBlocksLogicControllerTask gameWorldBlocksLogicControllerTask,
+                    GameWorldMobDamageControllerTask gameWorldMobDamageControllerTask
+    ) {
         mGamePhysics = gamePhysics;
-        mGameInput = gameInput;
         mGameRenderer = gameRenderer;
+        mMobsController = mobsController;
+        mGameWorldFluidsLogicControllerTask = gameWorldFluidsLogicControllerTask;
+        mGameWorldBlocksLogicControllerTask = gameWorldBlocksLogicControllerTask;
+        mGameWorldMobDamageControllerTask = gameWorldMobDamageControllerTask;
 
-        mGameWorld.startFluidsThread();
+        mobsController.getPlayer().controlMode = mainConfig.isTouch() ? Player.ControlMode.WALK : Player.ControlMode.CURSOR;
+
+        mWorldLogicTimer.scheduleTask(gameWorldFluidsLogicControllerTask, 0,
+                GameWorldFluidsLogicControllerTask.FLUID_UPDATE_INTERVAL_SEC);
+        mWorldLogicTimer.scheduleTask(gameWorldBlocksLogicControllerTask, 0,
+                GameWorldBlocksLogicControllerTask.WORLD_BLOCKS_LOGIC_UPDATE_INTERVAL_SEC);
+        mWorldLogicTimer.scheduleTask(gameWorldMobDamageControllerTask, 0,
+                GameWorldMobDamageControllerTask.ENVIRONMENTAL_MOB_DAMAGE_INTERVAL_SEC);
+    }
+
+    public void setPlayerGameMode(int gameMode) {
+        mMobsController.getPlayer().gameMode = gameMode;
     }
 
     public void update(float delta) {
-        mGamePhysics.update();
-        mGameInput.update();
-        mGameWorld.update();
+        mGamePhysics.update(delta);
         mGameRenderer.render(delta);
     }
 
+    public void show() {
+        Gdx.input.setInputProcessor(mGameRenderer);
+    }
+
     @Override
     public void dispose() {
-        mGameWorld.dispose();
+        mWorldLogicTimer.stop();
+        mGameWorldFluidsLogicControllerTask.cancel();
+        mGameWorldBlocksLogicControllerTask.cancel();
+        mGameWorldMobDamageControllerTask.cancel();
     }
 }
index 19f2681b0df65020285bcdb4572076bc089fb969..e96f3af48e3936fb31ccb99ecfac36a8b67d6774 100644 (file)
 package ru.deadsoftware.cavedroid.game;
 
 import com.badlogic.gdx.Gdx;
-import com.badlogic.gdx.graphics.Color;
 import com.badlogic.gdx.graphics.GL20;
-import com.badlogic.gdx.graphics.g2d.TextureRegion;
-import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
 import com.badlogic.gdx.math.Rectangle;
+import com.badlogic.gdx.utils.ObjectMap;
 import ru.deadsoftware.cavedroid.MainConfig;
-import ru.deadsoftware.cavedroid.game.mobs.Mob;
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler;
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction;
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction;
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey;
+import ru.deadsoftware.cavedroid.game.input.handler.mouse.CursorMouseInputHandler;
+import ru.deadsoftware.cavedroid.game.input.mapper.KeyboardInputActionMapper;
+import ru.deadsoftware.cavedroid.game.input.mapper.MouseInputActionMapper;
 import ru.deadsoftware.cavedroid.game.mobs.MobsController;
 import ru.deadsoftware.cavedroid.game.mobs.Player;
-import ru.deadsoftware.cavedroid.game.objects.Drop;
-import ru.deadsoftware.cavedroid.game.objects.DropController;
-import ru.deadsoftware.cavedroid.misc.ControlMode;
+import ru.deadsoftware.cavedroid.game.objects.TouchButton;
+import ru.deadsoftware.cavedroid.game.render.IGameRenderer;
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager;
+import ru.deadsoftware.cavedroid.misc.Assets;
 import ru.deadsoftware.cavedroid.misc.Renderer;
 
+import javax.annotation.CheckForNull;
 import javax.inject.Inject;
-
-import static ru.deadsoftware.cavedroid.misc.Assets.guiMap;
-import static ru.deadsoftware.cavedroid.misc.Assets.textureRegions;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
 
 @GameScope
 public class GameRenderer extends Renderer {
 
+    private static final TouchButton nullButton = new TouchButton(null, -1, true);
+
     private final MainConfig mMainConfig;
-    private final GameInput mGameInput;
-    private final GameWorld mGameWorld;
     private final MobsController mMobsController;
-    private final DropController mDropController;
+    private final List<IGameRenderer> mRenderers;
+    private final CursorMouseInputHandler mCursorMouseInputHandler;
+    private final MouseInputActionMapper mMouseInputActionMapper;
+    private final KeyboardInputActionMapper mKeyboardInputActionMapper;
+    private final Set<IGameInputHandler<MouseInputAction>> mMouseInputHandlers;
+    private final Set<IGameInputHandler<KeyboardInputAction>> mKeyboardInputHandlers;
+    private final GameWindowsManager mGameWindowsManager;
 
     @Inject
     GameRenderer(MainConfig mainConfig,
-                 GameInput gameInput,
-                 GameWorld gameWorld,
                  MobsController mobsController,
-                 DropController dropController) {
+                 Set<IGameRenderer> renderers,
+                 CursorMouseInputHandler cursorMouseInputHandler,
+                 MouseInputActionMapper mouseInputActionMapper,
+                 KeyboardInputActionMapper keyboardInputActionMapper,
+                 Set<IGameInputHandler<MouseInputAction>> mouseInputHandlers,
+                 Set<IGameInputHandler<KeyboardInputAction>> keyboardInputHandlers,
+                 GameWindowsManager gameWindowsManager) {
         super(mainConfig.getWidth(), mainConfig.getHeight());
 
         mMainConfig = mainConfig;
-        mGameInput = gameInput;
-        mGameWorld = gameWorld;
         mMobsController = mobsController;
-        mDropController = dropController;
+        mRenderers = new ArrayList<>(renderers);
+        mRenderers.sort(Comparator.comparingInt(IGameRenderer::getRenderLayer));
+        mCursorMouseInputHandler = cursorMouseInputHandler;
+        mMouseInputActionMapper = mouseInputActionMapper;
+        mKeyboardInputActionMapper = keyboardInputActionMapper;
+        mMouseInputHandlers = mouseInputHandlers;
+        mKeyboardInputHandlers = keyboardInputHandlers;
+        mGameWindowsManager = gameWindowsManager;
 
         Gdx.gl.glClearColor(0f, .6f, .6f, 1f);
     }
 
-    private float drawX(int x) {
-        return x * 16 - getCamX();
+    private float mTouchDownX, mTouchDownY;
+
+    private void updateCameraPosition() {
+        Player player = mMobsController.getPlayer();
+        setCamPos(player.getX() + player.getWidth() / 2 - getWidth() / 2,
+                player.getY() + player.getHeight() / 2 - getHeight() / 2);
     }
 
-    private float drawY(int y) {
-        return y * 16 - getCamY();
+    private float transformScreenX(int screenX) {
+        return getWidth() / Gdx.graphics.getWidth() * screenX;
     }
 
-    private void drawWreck(int bl) {
-        if (mGameInput.getBlockDamage() > 0) {
-            int index = 10 * mGameInput.getBlockDamage() / GameItems.getBlock(bl).getHp();
-            String key = "break_" + index;
-            spriter.draw(textureRegions.get(key), mGameInput.getCurX() * 16 - getCamX(),
-                    mGameInput.getCurY() * 16 - getCamY());
-        }
+    private float transformScreenY(int screenY) {
+        return getHeight() / Gdx.graphics.getHeight() * screenY;
     }
 
-    private void drawBlock(int x, int y, boolean drawBG) {
-        if (drawBG) {
-            if ((!mGameWorld.hasForeAt(x, y) || mGameWorld.getForeMapBlock(x, y).isTransparent())
-                    && mGameWorld.hasBackAt(x, y)) {
-                mGameWorld.getBackMapBlock(x, y).draw(spriter, drawX(x), drawY(y));
-                if (!mGameWorld.hasForeAt(x, y) && x == mGameInput.getCurX() && y == mGameInput.getCurY()) {
-                    drawWreck(mGameWorld.getBackMap(mGameInput.getCurX(), mGameInput.getCurY()));
-                }
-            }
-        }
-        if (mGameWorld.hasForeAt(x, y) && mGameWorld.getForeMapBlock(x, y).isBackground() == drawBG) {
-            mGameWorld.getForeMapBlock(x, y).draw(spriter, drawX(x), drawY(y));
-            if (x == mGameInput.getCurX() && y == mGameInput.getCurY()) {
-                drawWreck(mGameWorld.getForeMap(mGameInput.getCurX(), mGameInput.getCurY()));
-            }
-        }
+    private void handleMousePosition() {
+        final Rectangle viewport = getCameraViewport();
+        final MouseInputAction action = new MouseInputAction(
+                Gdx.input.getX() * (viewport.width / Gdx.graphics.getWidth()),
+                Gdx.input.getY() * (viewport.height / Gdx.graphics.getHeight()),
+                MouseInputActionKey.None.INSTANCE,
+                viewport);
+
+        mCursorMouseInputHandler.handle(action);
     }
 
-    private void drawWorld(boolean bg) {
-        int minX = (int) (getCamX() / 16) - 1;
-        int minY = (int) (getCamY() / 16) - 1;
-        int maxX = (int) ((getCamX() + getWidth()) / 16) + 1;
-        int maxY = (int) ((getCamY() + getHeight()) / 16) + 1;
-        if (minY < 0) {
-            minY = 0;
-        }
-        if (maxY > mGameWorld.getHeight()) {
-            maxY = mGameWorld.getHeight();
-        }
-        for (int y = minY; y < maxY; y++) {
-            for (int x = minX; x < maxX; x++) {
-                drawBlock(x, y, bg);
-            }
-        }
-        if (bg) {
-            spriter.end();
-            Gdx.gl.glEnable(GL20.GL_BLEND);
-            Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
-            shaper.begin(ShapeRenderer.ShapeType.Filled);
-            shaper.setColor(0f, 0f, 0f, .5f);
-            for (int y = minY; y < maxY; y++) {
-                for (int x = minX; x < maxX; x++) {
-                    if ((!mGameWorld.hasForeAt(x, y) || mGameWorld.getForeMapBlock(x, y).isTransparent())
-                            && mGameWorld.hasBackAt(x, y)) {
-                        shaper.rect(drawX(x), drawY(y), 16, 16);
-                    }
-                }
-            }
-            shaper.end();
-            Gdx.gl.glDisable(GL20.GL_BLEND);
-            spriter.begin();
+    private boolean handleMouseAction(@CheckForNull  MouseInputAction action) {
+        if (action == null) {
+            return false;
         }
-    }
 
-    private void drawMob(Mob mob) {
-        float mobDrawX = mob.getX() - getCamX();
-        float mobDrawY = mob.getY() - getCamY();
+        boolean anyProcessed = false;
 
-        if (mobDrawX + mob.getWidth() < 0 && mobDrawX + mGameWorld.getWidthPx() > 0) {
-            mobDrawX += mGameWorld.getWidthPx();
-        } else if (mobDrawX > getWidth() && mobDrawX + mob.getWidth() - mGameWorld.getWidthPx() > 0) {
-            mobDrawX -= mGameWorld.getWidthPx();
-        } else if (mobDrawX + mob.getWidth() < 0 && mobDrawX > getWidth()) {
-            return;
+        for (IGameInputHandler<MouseInputAction> handler : mMouseInputHandlers) {
+            final boolean conditions = handler.checkConditions(action);
+            if (conditions) {
+                anyProcessed = true;
+                handler.handle(action);
+                break;
+            }
+//            anyProcessed = anyProcessed || conditions;
         }
-
-        mob.draw(spriter, mobDrawX, mobDrawY);
+        return anyProcessed;
     }
 
-    private void drawDrop(Drop drop) {
+    private boolean onMouseActionEvent(int mouseX, int mouseY, int button, boolean touchUp) {
+        @CheckForNull MouseInputAction action = mMouseInputActionMapper
+                .map((float) mouseX, (float) mouseY, getCameraViewport(), button, touchUp);
+        return handleMouseAction(action);
     }
 
-    @SuppressWarnings("IntegerDivisionInFloatingPointContext")
-    private void drawCreative() {
-        TextureRegion creative = textureRegions.get("creative");
-        float x = getWidth() / 2 - (float) creative.getRegionWidth() / 2;
-        float y = getHeight() / 2 - (float) creative.getRegionHeight() / 2;
-        spriter.draw(creative, x, y);
-        spriter.draw(textureRegions.get("handle"), x + 156,
-                y + 18 + (mGameInput.getCreativeScroll() * (72f / GameProc.MAX_CREATIVE_SCROLL)));
-        for (int i = mGameInput.getCreativeScroll() * 8; i < mGameInput.getCreativeScroll() * 8 + 40; i++) {
-            if (i > 0 && i < GameItems.getItemsSize()) {
-                if (GameItems.getItem(i).isBlock()) {
-                    spriter.draw(GameItems.getBlock(GameItems.getBlockIdByItemId(i)).getTexture(),
-                            x + 8 + ((i - mGameInput.getCreativeScroll() * 8) % 8) * 18,
-                            y + 18 + ((i - mGameInput.getCreativeScroll() * 8) / 8) * 18);
-                } else {
-                    spriter.draw(GameItems.getItem(i).getTexture(),
-                            x + 8 + ((i - mGameInput.getCreativeScroll() * 8) % 8) * 18,
-                            y + 18 + ((i - mGameInput.getCreativeScroll() * 8) / 8) * 18);
-                }
-            }
-        }
-        for (int i = 0; i < 9; i++) {
-            if (mMobsController.getPlayer().inventory[i] > 0) {
-                if (GameItems.getItem(mMobsController.getPlayer().inventory[i]).isBlock()) {
-                    spriter.draw(GameItems.getBlock(GameItems.getBlockIdByItemId(mMobsController.getPlayer().inventory[i])).getTexture(),
-                            x + 8 + i * 18, y + creative.getRegionHeight() - 24);
-                } else {
-                    spriter.draw(GameItems.getItem(mMobsController.getPlayer().inventory[i]).getTexture(),
-                            x + 8 + i * 18, y + creative.getRegionHeight() - 24);
-                }
+    @Override
+    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
+        float touchX = transformScreenX(screenX);
+        float touchY = transformScreenY(screenY);
+
+        if (mMainConfig.isTouch()) {
+            TouchButton touchedKey = getTouchedKey(touchX, touchY);
+            if (touchedKey.isMouse()) {
+                return onMouseActionEvent(screenX, screenY, touchedKey.getCode(), true);
+            } else {
+                return keyUp(touchedKey.getCode());
             }
         }
 
+        return onMouseActionEvent(screenX, screenY, button, true);
     }
 
-    private void drawGUI() {
-        TextureRegion cursor = textureRegions.get("cursor");
-        TextureRegion hotbar = textureRegions.get("hotbar");
-        TextureRegion hotbarSelector = textureRegions.get("hotbar_selector");
-
-        if (mGameWorld.hasForeAt(mGameInput.getCurX(), mGameInput.getCurY()) ||
-                mGameWorld.hasBackAt(mGameInput.getCurX(), mGameInput.getCurY()) ||
-                mGameInput.getControlMode() == ControlMode.CURSOR || mMainConfig.isTouch()) {
-            spriter.draw(cursor, mGameInput.getCurX() * 16 - getCamX(), mGameInput.getCurY() * 16 - getCamY());
+    private TouchButton getTouchedKey(float touchX, float touchY) {
+        if (mGameWindowsManager.getCurrentWindowType() != GameUiWindow.NONE) {
+            return nullButton;
         }
-        spriter.draw(hotbar, getWidth() / 2 - (float) hotbar.getRegionWidth() / 2, 0);
-        for (int i = 0; i < 9; i++) {
-            if (mMobsController.getPlayer().inventory[i] > 0) {
-                if (GameItems.getItem(mMobsController.getPlayer().inventory[i]).isBlock()) {
-                    spriter.draw(GameItems.getBlock(GameItems.getBlockIdByItemId(mMobsController.getPlayer().inventory[i])).getTexture(),
-                            getWidth() / 2 - (float) hotbar.getRegionWidth() / 2 + 3 + i * 20,
-                            3);
-                } else {
-                    spriter.draw(GameItems.getItem(mMobsController.getPlayer().inventory[i]).getTexture(),
-                            getWidth() / 2 - (float) hotbar.getRegionWidth() / 2 + 3 + i * 20,
-                            3);
-                }
+        for (ObjectMap.Entry<String, TouchButton> entry : Assets.guiMap) {
+            TouchButton button = entry.value;
+            if (button.getRect().contains(touchX, touchY)) {
+                return button;
             }
         }
-        spriter.draw(hotbarSelector,
-                getWidth() / 2 - (float) hotbar.getRegionWidth() / 2 - 1 + 20 * mMobsController.getPlayer().slot,
-                -1);
+        return nullButton;
     }
 
-    private void drawTouchGui() {
-        for (int i = 0; i < guiMap.size; i++) {
-            Rectangle touchKey = guiMap.getValueAt(i).getRect();
-            spriter.draw(textureRegions.get(guiMap.getKeyAt(i)),
-                    touchKey.x, touchKey.y, touchKey.width, touchKey.height);
-        }
-        if (mGameInput.getControlMode() == ControlMode.CURSOR) {
-            spriter.draw(textureRegions.get("shade"), 83, getHeight() - 21);
+    @Override
+    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
+        float touchX = transformScreenX(screenX);
+        float touchY = transformScreenY(screenY);
+
+        mTouchDownX = touchX;
+        mTouchDownY = touchY;
+
+        if (mMainConfig.isTouch()) {
+            TouchButton touchedKey = getTouchedKey(touchX, touchY);
+            if (touchedKey.isMouse()) {
+                return onMouseActionEvent(screenX, screenY, touchedKey.getCode(), false);
+            } else {
+                return keyDown(touchedKey.getCode());
+            }
         }
+
+        return onMouseActionEvent(screenX, screenY, button, false);
     }
 
-    private void drawGamePlay() {
-        Player player = mMobsController.getPlayer();
+    @Override
+    public boolean touchDragged(int screenX, int screenY, int pointer) {
+        float touchX = transformScreenX(screenX);
+        float touchY = transformScreenY(screenY);
 
-        drawWorld(true);
-        player.draw(spriter, player.getX() - getCamX() - player.getWidth() / 2, player.getY() - getCamY());
-        mMobsController.forEach(this::drawMob);
-        mDropController.forEach(this::drawDrop);
-        drawWorld(false);
-        drawGUI();
-    }
+        if (Math.abs(touchX - mTouchDownX) < 16 && Math.abs(touchY - mTouchDownY) < 16) {
+            return false;
+        }
 
-    private void updateCameraPosition() {
-        Player player = mMobsController.getPlayer();
-        setCamPos(player.getX() + player.getWidth() / 2 - getWidth() / 2,
-                player.getY() + player.getHeight() / 2 - getHeight() / 2);
+        @CheckForNull MouseInputAction action =
+                mMouseInputActionMapper.mapDragged(screenX, screenY, getCameraViewport());
+        return handleMouseAction(action);
     }
 
     @Override
-    public void render(float delta) {
-        int fps = (int) (1 / delta);
-        updateCameraPosition();
-        mGameInput.moveCursor(this);
+    public boolean scrolled(float amountX, float amountY) {
+        @CheckForNull MouseInputAction action = mMouseInputActionMapper
+                .mapScrolled(Gdx.input.getX(), Gdx.input.getY(), amountX, amountY, getCameraViewport());
+        return handleMouseAction(action);
+    }
 
-        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
+    private boolean handleKeyboardAction(int keycode, boolean isKeyDown) {
+        @CheckForNull final KeyboardInputAction action = mKeyboardInputActionMapper
+                .map(keycode, isKeyDown);
 
-        spriter.begin();
+        if (action == null) {
+            return false;
+        }
 
-        drawGamePlay();
+        boolean anyProcessed = false;
 
-        switch (mMainConfig.getGameUiWindow()) {
-            case CREATIVE_INVENTORY:
-                drawCreative();
+        for (IGameInputHandler<KeyboardInputAction> handler : mKeyboardInputHandlers) {
+            final boolean conditions = handler.checkConditions(action);
+            if (conditions) {
+                anyProcessed = true;
+                handler.handle(action);
                 break;
-            //TODO draw other ui windows
+            }
         }
 
+        return anyProcessed;
+    }
 
-        if (mMainConfig.isTouch()) {
-            drawTouchGui();
-        }
+    @Override
+    public boolean keyDown(int keycode) {
+        return handleKeyboardAction(keycode, true);
+    }
 
-        spriter.end();
+    @Override
+    public boolean keyUp(int keycode) {
+        return handleKeyboardAction(keycode, false);
+    }
 
-        if (mMainConfig.isShowMap()) {
-            //DRAW MAP
-            shaper.begin(ShapeRenderer.ShapeType.Filled);
-            shaper.setColor(Color.LIGHT_GRAY);
-            shaper.rect(0, 0, mGameWorld.getWidth(), 128);
-            for (int y = 128; y < 256; y++) {
-                for (int x = 0; x < getWidth(); x++) {
-                    if (mGameWorld.hasForeAt(x, y) || mGameWorld.hasBackAt(x, y)) {
-                        if (GameItems.isWater(mGameWorld.getForeMap(x, y))) {
-                            shaper.setColor(Color.BLUE);
-                        } else if (GameItems.isLava(mGameWorld.getForeMap(x, y))) {
-                            shaper.setColor(Color.RED);
-                        } else {
-                            if (mGameWorld.hasForeAt(x, y)) {
-                                shaper.setColor(Color.BLACK);
-                            } else {
-                                shaper.setColor(Color.DARK_GRAY);
-                            }
-                        }
-                        shaper.rect(x, y - 128, 1, 1);
-                    }
-                }
-            }
-            shaper.setColor(Color.OLIVE);
-            shaper.rect(mMobsController.getPlayer().getMapX(), mMobsController.getPlayer().getUpperMapY() - 128, 1, 2);
-            shaper.end();
-            //=================
-        }
+    @Override
+    public void render(float delta) {
+        updateCameraPosition();
+        handleMousePosition();
+//        mGameInput.moveCursor(this);
 
-        if (mMainConfig.isShowInfo()) {
-            spriter.begin();
-            Player player = mMobsController.getPlayer();
-            drawString("FPS: " + fps, 0, 0);
-            drawString("X: " + player.getMapX(), 0, 10);
-            drawString("Y: " + player.getUpperMapY(), 0, 20);
-            drawString("CurX: " + mGameInput.getCurX(), 0, 30);
-            drawString("CurY: " + mGameInput.getCurY(), 0, 40);
-            drawString("Mobs: " + mMobsController.getSize(), 0, 50);
-            drawString("Drops: " + mDropController.getSize(), 0, 60);
-            drawString("Block: " + GameItems.getBlockKey(mGameWorld.getForeMap(mGameInput.getCurX(), mGameInput.getCurY())), 0, 70);
-            drawString("Hand: " + GameItems.getItemKey(mMobsController.getPlayer().inventory[mMobsController.getPlayer().slot]), 0, 80);
-            drawString("Game mode: " + player.gameMode, 0, 90);
-            spriter.end();
-        }
+        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
 
+        spriter.begin();
+        mRenderers.forEach(iGameRenderer -> iGameRenderer.draw(spriter, shaper, getCameraViewport(), delta));
+        spriter.end();
     }
 
 }
index 1ff2bad9783788a68d6ff1858dc9185f2a9a6fbe..cb52b3c76b126812e355c43d9ce076c7d5749140 100644 (file)
@@ -4,23 +4,29 @@ import com.badlogic.gdx.Gdx;
 import com.badlogic.gdx.files.FileHandle;
 import ru.deadsoftware.cavedroid.MainConfig;
 import ru.deadsoftware.cavedroid.game.mobs.MobsController;
+import ru.deadsoftware.cavedroid.game.model.block.Block;
 import ru.deadsoftware.cavedroid.game.objects.DropController;
+import ru.deadsoftware.cavedroid.game.world.GameWorld;
 
 import javax.annotation.CheckForNull;
 import java.io.*;
 import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
 
 public class GameSaver {
 
+    private static final String TAG = "GameSaver";
+
     public static class Data {
         @CheckForNull
         private MobsController mMobsController;
         @CheckForNull
         private DropController mDropController;
         @CheckForNull
-        private int[][] mForeMap, mBackMap;
+        private Block[][] mForeMap, mBackMap;
 
-        public Data(MobsController mobsController, DropController dropController, int[][] foreMap, int[][] backMap) {
+        public Data(MobsController mobsController, DropController dropController, Block[][] foreMap, Block[][] backMap) {
             mMobsController = mobsController;
             mDropController = dropController;
             mForeMap = foreMap;
@@ -41,16 +47,16 @@ public class GameSaver {
             return dropController;
         }
 
-        public int[][] retrieveForeMap() {
+        public Block[][] retrieveForeMap() {
             assert mForeMap != null;
-            int[][] foreMap = mForeMap;
+            Block[][] foreMap = mForeMap;
             mForeMap = null;
             return foreMap;
         }
 
-        public int[][] retrieveBackMap() {
+        public Block[][] retrieveBackMap() {
             assert mBackMap != null;
-            int[][] backMap = mBackMap;
+            Block[][] backMap = mBackMap;
             mBackMap = null;
             return backMap;
         }
@@ -66,56 +72,96 @@ public class GameSaver {
         return ByteBuffer.allocate(4).putInt(i).array();
     }
 
-    private static void saveMap(FileHandle file, int[][] map) throws IOException {
+    private static Map<String, Integer> buildBlocksDictionary(Block[][] foreMap, Block[][] backMap) {
+        final HashMap<String, Integer> dict = new HashMap<>();
+
+        int id = 0;
+        for (int i = 0; i < foreMap.length; i++) {
+            for (int j = 0; j < foreMap[i].length; j++) {
+                for (int k = 0; k < 2; k++) {
+                    final Block block = k == 0 ? foreMap[i][j] : backMap[i][j];
+                    final String key = block.getParams().getKey();
+                    if (!dict.containsKey(key)) {
+                        dict.put(key, id++);
+                    }
+                }
+            }
+        }
+
+        return dict;
+    }
+
+    private static void saveDict(FileHandle file, Map<String, Integer> dict) {
+        final String[] arr = new String[dict.size()];
+
+        for (Map.Entry<String, Integer> entry : dict.entrySet()) {
+            arr[entry.getValue()] = entry.getKey();
+        }
+
+        final StringBuilder builder = new StringBuilder();
+        for (String key : arr) {
+            builder.append(key);
+            builder.append('\n');
+        }
+
+        file.writeString(builder.toString(), false);
+    }
+
+    private static String[] loadDict(FileHandle file) {
+        return file.readString().split("\n");
+    }
+
+    private static void saveMap(FileHandle file, Block[][] map, Map<String, Integer> dict) throws IOException {
         int run, block;
         int width = map.length;
         int height = map[0].length;
 
         BufferedOutputStream out = new BufferedOutputStream(file.write(false));
 
-        out.write(intToBytes(SAVE_VERSION));
+        out.write(SAVE_VERSION);
         out.write(intToBytes(width));
         out.write(intToBytes(height));
 
         for (int y = 0; y < height; y++) {
-            block = map[0][y];
+            block = dict.get(map[0][y].getParams().getKey());
             run = 0;
-            for (int[] ints : map) {
-                if (ints[y] != block) {
-                    out.write(intToBytes(run));
-                    out.write(intToBytes(block));
+            for (Block[] blocks : map) {
+                int newValue = dict.get(blocks[y].getParams().getKey());
+                if (run >= 0xFF || newValue != block) {
+                    out.write(run);
+                    out.write(block);
                     run = 0;
-                    block = ints[y];
+                    block = dict.get(blocks[y].getParams().getKey());
                 }
                 run++;
             }
-            out.write(intToBytes(run));
-            out.write(intToBytes(block));
+            out.write(run);
+            out.write(block);
         }
 
         out.flush();
         out.close();
     }
 
-    private static int[][] loadMap(FileHandle file) throws Exception {
-        int[][] map;
+    private static Block[][] loadMap(GameItemsHolder gameItemsHolder, FileHandle file, String[] dict) throws Exception {
+        Block[][] map;
         int version, width, height;
         int run, block;
 
         DataInputStream in = new DataInputStream(file.read());
 
-        version = in.readInt();
+        version = in.readByte();
 
         if (SAVE_VERSION == version) {
             width = in.readInt();
             height = in.readInt();
-            map = new int[width][height];
+            map = new Block[width][height];
             for (int y = 0; y < height; y++) {
                 for (int x = 0; x < width; x += run) {
-                    run = in.readInt();
-                    block = in.readInt();
+                    run = in.readUnsignedByte();
+                    block = in.readUnsignedByte();
                     for (int i = x; i < x + run; i++) {
-                        map[i][y] = block;
+                        map[i][y] = gameItemsHolder.getBlock(dict[block]);
                     }
                 }
             }
@@ -128,7 +174,7 @@ public class GameSaver {
     }
 
     @CheckForNull
-    public static Data load(MainConfig mainConfig) {
+    public static Data load(MainConfig mainConfig, GameItemsHolder gameItemsHolder) {
         String folder = mainConfig.getGameFolder();
         FileHandle file = Gdx.files.absolute(folder + "/saves/game.sav");
 
@@ -147,8 +193,9 @@ public class GameSaver {
 
             in.close();
 
-            int[][] foreMap = loadMap(Gdx.files.absolute(mainConfig.getGameFolder() + "/saves/foremap.sav"));
-            int[][] backMap = loadMap(Gdx.files.absolute(mainConfig.getGameFolder() + "/saves/backmap.sav"));
+            final String[] dict = loadDict(Gdx.files.absolute(mainConfig.getGameFolder() + "/saves/dict"));
+            Block[][] foreMap = loadMap(gameItemsHolder, Gdx.files.absolute(mainConfig.getGameFolder() + "/saves/foremap.sav"), dict);
+            Block[][] backMap = loadMap(gameItemsHolder, Gdx.files.absolute(mainConfig.getGameFolder() + "/saves/backmap.sav"), dict);
 
             if (dropController == null || mobsController == null) {
                 throw new Exception("couldn't load");
@@ -166,20 +213,27 @@ public class GameSaver {
                             DropController dropController,
                             MobsController mobsController,
                             GameWorld gameWorld) {
-
         String folder = mainConfig.getGameFolder();
         FileHandle file = Gdx.files.absolute(folder + "/saves/");
         file.mkdirs();
         file = Gdx.files.absolute(folder + "/saves/game.sav");
 
+        final Block[][] foreMap, backMap;
+        foreMap = gameWorld.getFullForeMap();
+        backMap = gameWorld.getFullBackMap();
+
+        final Map<String, Integer> dict = buildBlocksDictionary(foreMap, backMap);
+
         try {
             ObjectOutputStream out = new ObjectOutputStream(file.write(false));
             out.writeInt(SAVE_VERSION);
             out.writeObject(dropController);
             out.writeObject(mobsController);
             out.close();
-            saveMap(Gdx.files.absolute(folder + "/saves/foremap.sav"), gameWorld.getFullForeMap());
-            saveMap(Gdx.files.absolute(folder + "/saves/backmap.sav"), gameWorld.getFullBackMap());
+
+            saveDict(Gdx.files.absolute(folder + "/saves/dict"), dict);
+            saveMap(Gdx.files.absolute(folder + "/saves/foremap.sav"), gameWorld.getFullForeMap(), dict);
+            saveMap(Gdx.files.absolute(folder + "/saves/backmap.sav"), gameWorld.getFullBackMap(), dict);
         } catch (Exception e) {
             e.printStackTrace();
         }
index 370d8bd79066baf1db5c385f4d89556b53c6f740..749d7d4055beeee9964e8f52cb7466188b093a1e 100644 (file)
@@ -16,25 +16,26 @@ public class GameScreen implements Screen {
     @CheckForNull
     private GameProc mGameProc;
     @CheckForNull
-    private GameInputProcessor mGameInputProcessor;
+    private GameItemsHolder mGameItemsHolder;
 
     @Inject
     public GameScreen(MainConfig mainConfig) {
         mMainConfig = mainConfig;
     }
 
-    public void newGame() {
+    public void newGame(int gameMode) {
         if (mGameProc != null) {
             mGameProc.dispose();
         }
 
+        GameModule.loaded = true;
+
         GameComponent gameComponent = DaggerGameComponent.builder()
                 .mainComponent(mMainConfig.getMainComponent()).build();
 
         mGameProc = gameComponent.getGameProc();
-        mGameInputProcessor = gameComponent.getGameInputProcessor();
 
-        Gdx.input.setInputProcessor(gameComponent.getGameInputProcessor());
+        mGameProc.setPlayerGameMode(gameMode);
     }
 
     public void loadGame() {
@@ -42,15 +43,12 @@ public class GameScreen implements Screen {
             mGameProc.dispose();
         }
 
-        GameModule.load(mMainConfig);
+        GameModule.loaded = false;
 
         GameComponent gameComponent = DaggerGameComponent.builder()
                 .mainComponent(mMainConfig.getMainComponent()).build();
 
         mGameProc = gameComponent.getGameProc();
-        mGameInputProcessor = gameComponent.getGameInputProcessor();
-
-        Gdx.input.setInputProcessor(gameComponent.getGameInputProcessor());
     }
 
     @Override
@@ -60,7 +58,8 @@ public class GameScreen implements Screen {
 
     @Override
     public void show() {
-        Gdx.input.setInputProcessor(mGameInputProcessor);
+//        Gdx.input.setInputProcessor(mGameInputProcessor);
+        mGameProc.show();
     }
 
     @Override
@@ -85,6 +84,9 @@ public class GameScreen implements Screen {
 
     @Override
     public void dispose() {
+        if (mGameProc != null) {
+            mGameProc.dispose();
+        }
     }
 
 }
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameWorld.java b/core/src/ru/deadsoftware/cavedroid/game/GameWorld.java
deleted file mode 100644 (file)
index 3bf4981..0000000
+++ /dev/null
@@ -1,260 +0,0 @@
-package ru.deadsoftware.cavedroid.game;
-
-import com.badlogic.gdx.utils.Disposable;
-import com.badlogic.gdx.utils.TimeUtils;
-import kotlin.Pair;
-import ru.deadsoftware.cavedroid.game.mobs.FallingGravel;
-import ru.deadsoftware.cavedroid.game.mobs.FallingSand;
-import ru.deadsoftware.cavedroid.game.mobs.MobsController;
-import ru.deadsoftware.cavedroid.game.objects.Block;
-import ru.deadsoftware.cavedroid.game.objects.DropController;
-
-import javax.annotation.CheckForNull;
-import javax.inject.Inject;
-
-@GameScope
-public class GameWorld implements Disposable {
-
-    private static final int DEFAULT_WIDTH = 1024;
-    private static final int DEFAULT_HEIGHT = 256;
-    private static final int UPDATE_RANGE = 16;
-
-    private final DropController mDropController;
-    private final MobsController mMobsController;
-    private final GameFluidsThread mGameFluidsThread;
-
-    private final int mWidth;
-    private final int mHeight;
-    private final int[][] mForeMap;
-    private final int[][] mBackMap;
-
-    private boolean mShouldUpdate;
-    private int mUpdateX;
-    private int mUpdateY;
-
-    @Inject
-    public GameWorld(DropController dropController,
-                     MobsController mobsController,
-                     @CheckForNull int[][] foreMap,
-                     @CheckForNull int[][] backMap) {
-        mDropController = dropController;
-        mMobsController = mobsController;
-
-        boolean isNewGame = foreMap == null || backMap == null;
-
-        if (isNewGame) {
-            mWidth = DEFAULT_WIDTH;
-            mHeight = DEFAULT_HEIGHT;
-            Pair<int[][], int[][]> maps = GameWorldGeneratorKt.generate(mWidth, mHeight, TimeUtils.millis());
-            mForeMap = maps.getFirst();
-            mBackMap = maps.getSecond();
-            mMobsController.getPlayer().respawn(this);
-        } else {
-            mForeMap = foreMap;
-            mBackMap = backMap;
-            mWidth = mForeMap.length;
-            mHeight = mForeMap[0].length;
-        }
-
-        mGameFluidsThread = new GameFluidsThread(this, mMobsController, Thread.currentThread());
-    }
-
-    public int getWidth() {
-        return mWidth;
-    }
-
-    public int getHeight() {
-        return mHeight;
-    }
-
-    public float getWidthPx() {
-        return mWidth * 16f;
-    }
-
-    public float getHeightPx() {
-        return mHeight * 16f;
-    }
-
-    int[][] getFullForeMap() {
-        return mForeMap;
-    }
-
-    int[][] getFullBackMap() {
-        return mBackMap;
-    }
-
-    private int transformX(int x) {
-        x = x % getWidth();
-        if (x < 0) {
-            x = getWidth() - Math.abs(x);
-        }
-        return x;
-    }
-
-    private int getMap(int x, int y, int layer) {
-        int map = 0;
-        try {
-            x = transformX(x);
-            map = (layer == 0) ? mForeMap[x][y] : mBackMap[x][y];
-        } catch (ArrayIndexOutOfBoundsException ignored) {
-        }
-        return map;
-    }
-
-    private void setMap(int x, int y, int layer, int value) {
-        try {
-            x = transformX(x);
-            if (layer == 0) {
-                mForeMap[x][y] = value;
-            } else {
-                mBackMap[x][y] = value;
-            }
-        } catch (ArrayIndexOutOfBoundsException ignored) {
-        }
-    }
-
-    public boolean hasForeAt(int x, int y) {
-        return getMap(x, y, 0) != 0;
-    }
-
-    public boolean hasBackAt(int x, int y) {
-        return getMap(x, y, 1) != 0;
-    }
-
-    public int getForeMap(int x, int y) {
-        return getMap(x, y, 0);
-    }
-
-    public Block getForeMapBlock(int x, int y) {
-        return GameItems.getBlock(getMap(x, y, 0));
-    }
-
-    public void setForeMap(int x, int y, int id) {
-        setMap(x, y, 0, id);
-    }
-
-    public int getBackMap(int x, int y) {
-        return getMap(x, y, 1);
-    }
-
-    public Block getBackMapBlock(int x, int y) {
-        return GameItems.getBlock(getMap(x, y, 1));
-    }
-
-    public void setBackMap(int x, int y, int id) {
-        setMap(x, y, 1, id);
-    }
-
-    private void placeSlab(int x, int y, int value) {
-        switch (value) {
-            case 51:
-                setForeMap(x, y, 52);
-                break;
-            case 53:
-                setForeMap(x, y, 21);
-                break;
-            case 54:
-                setForeMap(x, y, 5);
-                break;
-            case 55:
-                setForeMap(x, y, 4);
-                break;
-            case 56:
-                setForeMap(x, y, 28);
-                break;
-            case 58:
-                setForeMap(x, y, 57);
-                break;
-        }
-    }
-
-    public void placeToForeground(int x, int y, int value) {
-        if (!hasForeAt(x, y) || value == 0 || !GameItems.getBlock(getForeMap(x, y)).hasCollision()) {
-            setForeMap(x, y, value);
-        } else if (GameItems.isSlab(value) && getForeMap(x, y) == value) {
-            placeSlab(x, y, value);
-        }
-        mUpdateX = x - 8;
-        mUpdateY = y - 8;
-        mShouldUpdate = true;
-    }
-
-    public void placeToBackground(int x, int y, int value) {
-        if (value == 0 || (getBackMap(x, y) == 0 && GameItems.getBlock(value).hasCollision()) &&
-                (!GameItems.getBlock(value).isTransparent() || value == 18)) {
-            setBackMap(x, y, value);
-        }
-    }
-
-    public void destroyForeMap(int x, int y) {
-        Block block = GameItems.getBlock(getForeMap(x, y));
-        if (block.hasDrop()) {
-            mDropController.addDrop(transformX(x) * 16 + 4, y * 16 + 4, GameItems.getItemId(block.getDrop()));
-        }
-        placeToForeground(x, y, 0);
-    }
-
-    public void destroyBackMap(int x, int y) {
-        Block block = GameItems.getBlock(getBackMap(x, y));
-        if (block.hasDrop()) {
-            mDropController.addDrop(transformX(x) * 16 + 4, y * 16 + 4, GameItems.getItemId(block.getDrop()));
-        }
-        placeToBackground(x, y, 0);
-    }
-
-    private void updateBlock(int x, int y) {
-        if (getForeMap(x, y) == 10) {
-            if (!hasForeAt(x, y + 1) || !getForeMapBlock(x, y + 1).hasCollision()) {
-                setForeMap(x, y, 0);
-                mMobsController.addMob(FallingSand.class, x * 16, y * 16);
-                updateBlock(x, y - 1);
-            }   
-        }
-
-        if (getForeMap(x, y) == 11) {
-            if (!hasForeAt(x, y + 1) || !getForeMapBlock(x, y + 1).hasCollision()) {
-                setForeMap(x, y, 0);
-                mMobsController.addMob(FallingGravel.class, x * 16, y * 16);
-                updateBlock(x, y - 1);
-            }
-        }
-
-        if (hasForeAt(x, y) && getForeMapBlock(x, y).requiresBlock()) {
-            if (!hasForeAt(x, y + 1) || !getForeMapBlock(x, y + 1).hasCollision()) {
-                destroyForeMap(x, y);
-                updateBlock(x, y - 1);
-            }
-        }
-
-        if (getForeMap(x, y) == 2) {
-            if (hasForeAt(x, y - 1) && (getForeMapBlock(x, y - 1).hasCollision() ||
-                    GameItems.isFluid(getForeMap(x, y - 1)))) {
-                setForeMap(x, y, 3);
-            }
-        }
-    }
-
-    public void update() {
-        if (mShouldUpdate) {
-            for (int y = mUpdateY; y < mUpdateY + UPDATE_RANGE; y++) {
-                for (int x = mUpdateX; x < mUpdateX + UPDATE_RANGE; x++) {
-                    updateBlock(x, y);
-                }
-            }
-            mShouldUpdate = false;
-        }
-
-        if (!mGameFluidsThread.isAlive()) {
-            mGameFluidsThread.start();
-        }
-    }
-
-    public void startFluidsThread() {
-        mGameFluidsThread.start();
-    }
-
-    @Override
-    public void dispose() {
-        mGameFluidsThread.interrupt();
-    }
-}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameWorldGenerator.kt b/core/src/ru/deadsoftware/cavedroid/game/GameWorldGenerator.kt
deleted file mode 100644 (file)
index c157a4e..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-package ru.deadsoftware.cavedroid.game
-
-import com.badlogic.gdx.utils.TimeUtils
-import kotlin.math.abs
-import kotlin.random.Random
-
-private fun generateHeights(width: Int, min: Int, max: Int, random: Random) = IntArray(width).apply {
-    set(0, (min + max) / 2)
-    for (x in 1 until width) {
-        val previous = get(x - 1)
-        var d = random.nextInt(-5, 6).let { if (it !in -4..4) it / abs(it) else 0 }
-
-        if (previous + d !in min..max) { d = -d }
-        if (lastIndex - x < abs(get(0) - previous) * 3) {
-            d = get(0).compareTo(previous).let { if (it != 0) it / abs(it) else 0 }
-        }
-
-        set(x, get(x - 1) + d)
-    }
-}
-
-/**
- * Generates world of given width and height with given seed
- * @param width world width
- * @param height world height
- * @param seed seed for random number generator
- * @return pair of foreground and background layers
- */
-fun generate(width: Int, height: Int, seed: Long = TimeUtils.millis()): Pair<Array<IntArray>, Array<IntArray>> {
-    val random = Random(seed)
-    val foreMap = Array(width) { IntArray(height) }
-    val backMap = Array(width) { IntArray(width) }
-    val heightsMap = generateHeights(width, height / 2, height * 3 / 4, random)
-
-    for (x in 0 until width) {
-        val xHeight = heightsMap[x]
-
-        foreMap[x][xHeight] = GameItems.getBlockId("grass")
-        foreMap[x][height - 1] = GameItems.getBlockId("bedrock")
-        backMap[x][xHeight] = GameItems.getBlockId("grass")
-        backMap[x][height - 1] = GameItems.getBlockId("bedrock")
-
-        for (y in xHeight + 1 until height - 1) {
-            foreMap[x][y] = when {
-                y < xHeight + random.nextInt(5, 8) -> GameItems.getBlockId("dirt")
-                else -> GameItems.getBlockId("stone")
-            }
-            backMap[x][y] = foreMap[x][y]
-        }
-    }
-    return Pair(foreMap, backMap)
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/CommonBlockActionUtils.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/CommonBlockActionUtils.kt
new file mode 100644 (file)
index 0000000..2803ef0
--- /dev/null
@@ -0,0 +1,25 @@
+package ru.deadsoftware.cavedroid.game.actions
+
+import com.badlogic.gdx.Gdx
+import ru.deadsoftware.cavedroid.game.actions.placeblock.IPlaceBlockAction
+import ru.deadsoftware.cavedroid.game.actions.updateblock.IUpdateBlockAction
+import ru.deadsoftware.cavedroid.game.actions.updateblock.UpdateRequiresBlockAction
+import ru.deadsoftware.cavedroid.game.actions.placeblock.PlaceBlockItemToBackgroundAction
+import ru.deadsoftware.cavedroid.game.actions.placeblock.PlaceBlockItemToForegroundAction
+import ru.deadsoftware.cavedroid.game.model.item.Item
+
+private const val TAG = "PlaceBlockActionUtils"
+
+fun Map<String, IPlaceBlockAction>.placeToForegroundAction(item: Item.Placeable, x: Int, y: Int) {
+    get(PlaceBlockItemToForegroundAction.ACTION_KEY)?.place(item, x, y)
+        ?: Gdx.app.error(TAG, "action place_foreground_block not found")
+}
+
+fun Map<String, IPlaceBlockAction>.placeToBackgroundAction(item: Item.Placeable, x: Int, y: Int) {
+    get(PlaceBlockItemToBackgroundAction.ACTION_KEY)?.place(item, x, y)
+        ?: Gdx.app.error(TAG, "action place_background_block not found")
+}
+
+fun Map<String, IUpdateBlockAction>.getRequiresBlockAction(): IUpdateBlockAction {
+    return requireNotNull(get(UpdateRequiresBlockAction.ACTION_KEY)) { "action requires_block not found" }
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/PlaceBlockActionsModule.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/PlaceBlockActionsModule.kt
new file mode 100644 (file)
index 0000000..f2c5a23
--- /dev/null
@@ -0,0 +1,40 @@
+package ru.deadsoftware.cavedroid.game.actions
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.actions.placeblock.IPlaceBlockAction
+import ru.deadsoftware.cavedroid.game.actions.placeblock.PlaceBlockItemToBackgroundAction
+import ru.deadsoftware.cavedroid.game.actions.placeblock.PlaceBlockItemToForegroundAction
+import ru.deadsoftware.cavedroid.game.actions.placeblock.PlaceSlabAction
+
+@Module
+class PlaceBlockActionsModule {
+
+    @Binds
+    @IntoMap
+    @StringKey(PlaceBlockItemToForegroundAction.ACTION_KEY)
+    @GameScope
+    fun bindPlaceBlockItemToForegroundAction(action: PlaceBlockItemToForegroundAction): IPlaceBlockAction {
+        return action
+    }
+
+    @Binds
+    @IntoMap
+    @StringKey(PlaceBlockItemToBackgroundAction.ACTION_KEY)
+    @GameScope
+    fun bindPlaceBlockItemToBackgroundAction(action: PlaceBlockItemToBackgroundAction): IPlaceBlockAction {
+        return action
+    }
+
+    @Binds
+    @IntoMap
+    @StringKey(PlaceSlabAction.ACTION_KEY)
+    @GameScope
+    fun bindPlaceSlabAction(action: PlaceSlabAction): IPlaceBlockAction {
+        return action
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/UpdateBlockActionsModule.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/UpdateBlockActionsModule.kt
new file mode 100644 (file)
index 0000000..5dcfe0e
--- /dev/null
@@ -0,0 +1,52 @@
+package ru.deadsoftware.cavedroid.game.actions
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.actions.updateblock.*
+
+@Module
+class UpdateBlockActionsModule {
+
+    @Binds
+    @IntoMap
+    @StringKey(UpdateSandAction.BLOCK_KEY)
+    @GameScope
+    fun bindUpdateSandAction(action: UpdateSandAction): IUpdateBlockAction {
+        return action;
+    }
+
+    @Binds
+    @IntoMap
+    @StringKey(UpdateGravelAction.BLOCK_KEY)
+    @GameScope
+    fun bindUpdateGravelAction(action: UpdateGravelAction): IUpdateBlockAction {
+        return action;
+    }
+
+    @Binds
+    @IntoMap
+    @StringKey(UpdateRequiresBlockAction.ACTION_KEY)
+    @GameScope
+    fun bindUpdateRequiresBlockAction(action: UpdateRequiresBlockAction): IUpdateBlockAction {
+        return action;
+    }
+
+    @Binds
+    @IntoMap
+    @StringKey(UpdateGrassAction.BLOCK_KEY)
+    @GameScope
+    fun bindUpdateGrassAction(action: UpdateGrassAction): IUpdateBlockAction {
+        return action;
+    }
+
+    @Binds
+    @IntoMap
+    @StringKey(UpdateSnowedGrassAction.BLOCK_KEY)
+    @GameScope
+    fun bindUpdateSnowedGrassAction(action: UpdateSnowedGrassAction): IUpdateBlockAction {
+        return action;
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/UseItemActionsModule.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/UseItemActionsModule.kt
new file mode 100644 (file)
index 0000000..0206d5f
--- /dev/null
@@ -0,0 +1,37 @@
+package ru.deadsoftware.cavedroid.game.actions
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.actions.useitem.*
+
+@Module
+class UseItemActionsModule {
+
+    @Binds
+    @IntoMap
+    @StringKey(UseWaterBucketAction.ACTION_KEY)
+    @GameScope
+    fun bindUseWaterBucketAction(action: UseWaterBucketAction): IUseItemAction {
+        return action
+    }
+
+    @Binds
+    @IntoMap
+    @StringKey(UseLavaBucketAction.ACTION_KEY)
+    @GameScope
+    fun bindUseLavaBucketAction(action: UseLavaBucketAction): IUseItemAction {
+        return action
+    }
+
+    @Binds
+    @IntoMap
+    @StringKey(UseEmptyBucketAction.ACTION_KEY)
+    @GameScope
+    fun bindUseEmptyBucketAction(action: UseEmptyBucketAction): IUseItemAction {
+        return action
+    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/IPlaceBlockAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/IPlaceBlockAction.kt
new file mode 100644 (file)
index 0000000..06c70c4
--- /dev/null
@@ -0,0 +1,9 @@
+package ru.deadsoftware.cavedroid.game.actions.placeblock
+
+import ru.deadsoftware.cavedroid.game.model.item.Item
+
+interface IPlaceBlockAction {
+
+    fun place(placeable: Item.Placeable, x: Int, y: Int)
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToBackgroundAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToBackgroundAction.kt
new file mode 100644 (file)
index 0000000..fdfb61a
--- /dev/null
@@ -0,0 +1,27 @@
+package ru.deadsoftware.cavedroid.game.actions.placeblock
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class PlaceBlockItemToBackgroundAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val gameItemsHolder: GameItemsHolder,
+    private val mobsController: MobsController,
+) : IPlaceBlockAction {
+
+    override fun place(placeable: Item.Placeable, x: Int, y: Int) {
+        if (gameWorld.placeToBackground(x, y, placeable.block)) {
+            mobsController.player.decreaseCurrentItemCount(gameItemsHolder)
+        }
+    }
+
+    companion object {
+        const val ACTION_KEY = "place_background_block"
+    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToForegroundAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToForegroundAction.kt
new file mode 100644 (file)
index 0000000..22aafdd
--- /dev/null
@@ -0,0 +1,32 @@
+package ru.deadsoftware.cavedroid.game.actions.placeblock
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class PlaceBlockItemToForegroundAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val placeSlabAction: PlaceSlabAction,
+    private val gameItemsHolder: GameItemsHolder,
+    private val mobsController: MobsController,
+) : IPlaceBlockAction {
+
+    override fun place(placeable: Item.Placeable, x: Int, y: Int) {
+        if (placeable.isSlab()) {
+            placeSlabAction.place(placeable, x, y)
+        } else {
+            if (gameWorld.placeToForeground(x, y, placeable.block)) {
+                mobsController.player.decreaseCurrentItemCount(gameItemsHolder)
+            }
+        }
+    }
+
+    companion object {
+        const val ACTION_KEY = "place_foreground_block"
+    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceSlabAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceSlabAction.kt
new file mode 100644 (file)
index 0000000..6ac94c9
--- /dev/null
@@ -0,0 +1,42 @@
+package ru.deadsoftware.cavedroid.game.actions.placeblock
+
+import com.badlogic.gdx.Gdx
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class PlaceSlabAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : IPlaceBlockAction {
+
+    override fun place(placeable: Item.Placeable, x: Int, y: Int) {
+        if (placeable !is Item.Slab) {
+            Gdx.app.debug(TAG, "Place slab action called on ${placeable.params.key} which is not a slab")
+            return
+        }
+
+        val slabPart = if ((gameWorld.hasForeAt(x, y - 1)
+                    || gameWorld.getForeMap(x - 1, y) == placeable.topPartBlock
+                    || gameWorld.getForeMap(x + 1, y) == placeable.topPartBlock)
+            && !gameWorld.hasForeAt(x, y + 1)) {
+            placeable.topPartBlock
+        } else {
+            placeable.bottomPartBlock
+        }
+
+        if (gameWorld.placeToForeground(x, y, slabPart)) {
+            mobsController.player.decreaseCurrentItemCount(gameItemsHolder)
+        }
+    }
+
+    companion object {
+        private const val TAG = "PlaceSlabAction"
+        const val ACTION_KEY = "place_slab"
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/IUpdateBlockAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/IUpdateBlockAction.kt
new file mode 100644 (file)
index 0000000..9cc4aea
--- /dev/null
@@ -0,0 +1,7 @@
+package ru.deadsoftware.cavedroid.game.actions.updateblock
+
+interface IUpdateBlockAction {
+
+    fun update(x: Int, y: Int)
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGrassAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGrassAction.kt
new file mode 100644 (file)
index 0000000..27005b8
--- /dev/null
@@ -0,0 +1,24 @@
+package ru.deadsoftware.cavedroid.game.actions.updateblock
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class UpdateGrassAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mGameItemsHolder: GameItemsHolder,
+) : IUpdateBlockAction {
+
+    override fun update(x: Int, y: Int) {
+        val blockOnTop = gameWorld.getForeMap(x, y - 1)
+        if (blockOnTop.collision || blockOnTop.isFluid()) {
+            gameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("dirt"))
+        }
+    }
+
+    companion object {
+        const val BLOCK_KEY = "grass"
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGravelAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGravelAction.kt
new file mode 100644 (file)
index 0000000..6cc026f
--- /dev/null
@@ -0,0 +1,28 @@
+package ru.deadsoftware.cavedroid.game.actions.updateblock
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.FallingGravel
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class UpdateGravelAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+) : IUpdateBlockAction {
+
+    override fun update(x: Int, y: Int) {
+        val shouldFall = gameWorld.getForeMap(x, y + 1).params.hasCollision.not()
+
+        if (shouldFall) {
+            gameWorld.resetForeMap(x, y)
+            FallingGravel(x * 16f, y * 16f)
+                .apply { attachToController(mobsController) }
+        }
+    }
+
+    companion object {
+        const val BLOCK_KEY = "gravel"
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateRequiresBlockAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateRequiresBlockAction.kt
new file mode 100644 (file)
index 0000000..57172a5
--- /dev/null
@@ -0,0 +1,21 @@
+package ru.deadsoftware.cavedroid.game.actions.updateblock
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class UpdateRequiresBlockAction @Inject constructor(
+    private val gameWorld: GameWorld,
+) : IUpdateBlockAction {
+
+    override fun update(x: Int, y: Int) {
+        if (gameWorld.getForeMap(x, y + 1).params.hasCollision.not()) {
+            gameWorld.destroyForeMap(x, y)
+        }
+    }
+
+    companion object {
+        const val ACTION_KEY = "requires_block"
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSandAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSandAction.kt
new file mode 100644 (file)
index 0000000..4a2b58e
--- /dev/null
@@ -0,0 +1,28 @@
+package ru.deadsoftware.cavedroid.game.actions.updateblock
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.FallingSand
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class UpdateSandAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+) : IUpdateBlockAction {
+
+    override fun update(x: Int, y: Int) {
+        val shouldFall = gameWorld.getForeMap(x, y + 1).params.hasCollision.not()
+
+        if (shouldFall) {
+            gameWorld.resetForeMap(x, y)
+            FallingSand(x * 16f, y * 16f)
+                .apply { attachToController(mobsController) }
+        }
+    }
+
+    companion object {
+        const val BLOCK_KEY = "sand"
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSnowedGrassAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSnowedGrassAction.kt
new file mode 100644 (file)
index 0000000..8b7f0e3
--- /dev/null
@@ -0,0 +1,24 @@
+package ru.deadsoftware.cavedroid.game.actions.updateblock
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class UpdateSnowedGrassAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mGameItemsHolder: GameItemsHolder,
+) : IUpdateBlockAction {
+
+    override fun update(x: Int, y: Int) {
+        val blockOnTop = gameWorld.getForeMap(x, y - 1)
+        if (blockOnTop.collision || blockOnTop.isFluid()) {
+            gameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("dirt"))
+        }
+    }
+
+    companion object {
+        const val BLOCK_KEY = "grass_snowed"
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/IUseItemAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/IUseItemAction.kt
new file mode 100644 (file)
index 0000000..d963aee
--- /dev/null
@@ -0,0 +1,9 @@
+package ru.deadsoftware.cavedroid.game.actions.useitem
+
+import ru.deadsoftware.cavedroid.game.model.item.Item
+
+interface IUseItemAction {
+
+    fun perform(item: Item.Usable, x: Int, y: Int)
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseEmptyBucketAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseEmptyBucketAction.kt
new file mode 100644 (file)
index 0000000..9f7c9f4
--- /dev/null
@@ -0,0 +1,37 @@
+package ru.deadsoftware.cavedroid.game.actions.useitem
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class UseEmptyBucketAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : IUseItemAction {
+
+    override fun perform(item: Item.Usable, x: Int, y: Int) {
+        val foregroundBlock = gameWorld.getForeMap(x, y)
+        if (!foregroundBlock.isFluid()) {
+            return
+        }
+        gameWorld.resetForeMap(x, y)
+
+        val filled = when (foregroundBlock) {
+            is Block.Lava -> gameItemsHolder.getItem("bucket_lava")
+            is Block.Water -> gameItemsHolder.getItem("bucket_water")
+            else -> throw IllegalStateException("unknown fluid")
+        }
+
+        mobsController.player.setCurrentInventorySlotItem(filled)
+    }
+
+    companion object {
+        const val ACTION_KEY = "use_empty_bucket"
+    }
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseLavaBucketAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseLavaBucketAction.kt
new file mode 100644 (file)
index 0000000..9659617
--- /dev/null
@@ -0,0 +1,28 @@
+package ru.deadsoftware.cavedroid.game.actions.useitem
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class UseLavaBucketAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : IUseItemAction {
+
+    override fun perform(item: Item.Usable, x: Int, y: Int) {
+        gameWorld.placeToForeground(x, y, gameItemsHolder.getBlock("lava"))
+
+        if (mobsController.player.gameMode != 1) {
+            mobsController.player.setCurrentInventorySlotItem(gameItemsHolder.getItem("bucket_empty"))
+        }
+    }
+
+    companion object {
+        const val ACTION_KEY = "use_lava_bucket"
+    }
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseWaterBucketAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseWaterBucketAction.kt
new file mode 100644 (file)
index 0000000..15e08fc
--- /dev/null
@@ -0,0 +1,28 @@
+package ru.deadsoftware.cavedroid.game.actions.useitem
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class UseWaterBucketAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : IUseItemAction {
+
+    override fun perform(item: Item.Usable, x: Int, y: Int) {
+        gameWorld.placeToForeground(x, y, gameItemsHolder.getBlock("water"))
+        if (mobsController.player.gameMode != 1) {
+            mobsController.player.setCurrentInventorySlotItem(gameItemsHolder.getItem("bucket_empty"))
+        }
+    }
+
+    companion object {
+        const val ACTION_KEY = "use_water_bucket"
+    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt b/core/src/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt
new file mode 100644 (file)
index 0000000..c8147d1
--- /dev/null
@@ -0,0 +1,36 @@
+package ru.deadsoftware.cavedroid.game.debug
+
+import com.badlogic.gdx.Gdx
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.objects.DropController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class DebugInfoStringsProvider @Inject constructor(
+    private val mobsController: MobsController,
+    private val dropController: DropController,
+    private val gameWorld: GameWorld
+) {
+
+    fun getDebugStrings(): List<String> {
+        val player = mobsController.player
+
+        return listOf(
+            "FPS: ${Gdx.graphics.framesPerSecond}",
+            "X: ${player.mapX}",
+            "Y: ${gameWorld.height - player.upperMapY}",
+            "CurX: ${player.cursorX}",
+            "CurY: ${player.cursorY}",
+            "Velocity: ${player.velocity}",
+            "Swim: ${player.swim}",
+            "Mobs: ${mobsController.mobs.size}",
+            "Drops: ${dropController.size}",
+            "Block: ${gameWorld.getForeMap(player.cursorX, player.cursorY).params.key}",
+            "Hand: ${player.inventory[player.slot].item.params.key}",
+            "Game mode: ${player.gameMode}",
+            "Block damage: ${player.blockDamage}"
+        )
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/IGameInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/IGameInputHandler.kt
new file mode 100644 (file)
index 0000000..a8eaad1
--- /dev/null
@@ -0,0 +1,19 @@
+package ru.deadsoftware.cavedroid.game.input
+
+import ru.deadsoftware.cavedroid.game.input.action.IGameInputAction
+
+interface IGameInputHandler<A : IGameInputAction> {
+
+    /**
+     * Implementation should check if conditions for handling an input are satisfied
+     * For example - inventory input handler should return false if inventory is closed
+     */
+    fun checkConditions(action: A): Boolean
+
+    /**
+     * Handle given input action.
+     * This will not be called if [checkConditions] returned false
+     */
+    fun handle(action: A)
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/InputUtils.kt b/core/src/ru/deadsoftware/cavedroid/game/input/InputUtils.kt
new file mode 100644 (file)
index 0000000..425822a
--- /dev/null
@@ -0,0 +1,21 @@
+package ru.deadsoftware.cavedroid.game.input
+
+import com.badlogic.gdx.graphics.g2d.TextureRegion
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.misc.Assets
+
+fun isInsideHotbar(action: MouseInputAction): Boolean {
+    val hotbar = requireNotNull(Assets.textureRegions["hotbar"])
+
+    return action.screenY <= hotbar.regionHeight &&
+            action.screenX >= action.cameraViewport.width / 2 - hotbar.regionWidth / 2 &&
+            action.screenX <= action.cameraViewport.width / 2 + hotbar.regionWidth / 2
+}
+
+fun isInsideWindow(action: MouseInputAction, windowTexture: TextureRegion): Boolean {
+    return action.screenY > action.cameraViewport.height / 2 - windowTexture.regionHeight / 2 &&
+            action.screenY < action.cameraViewport.height / 2 + windowTexture.regionHeight / 2 &&
+            action.screenX > action.cameraViewport.width / 2 - windowTexture.regionWidth / 2 &&
+            action.screenX < action.cameraViewport.width / 2 + windowTexture.regionWidth / 2
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/KeyboardInputHandlersModule.kt b/core/src/ru/deadsoftware/cavedroid/game/input/KeyboardInputHandlersModule.kt
new file mode 100644 (file)
index 0000000..f590398
--- /dev/null
@@ -0,0 +1,118 @@
+package ru.deadsoftware.cavedroid.game.input
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.handler.keyboard.*
+
+@Module
+object KeyboardInputHandlersModule {
+    
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindGoLeftKeyboardInputHandler(handler: GoLeftKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindGoRightKeyboardInputHandler(handler: GoRightKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindJumpKeyboardActionHandler(handler: JumpKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindFlyUpKeyboardActionHandler(handler: FlyUpKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindTurnOnFlyModeKeyboardActionHandler(handler: TurnOnFlyModeKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindFlyDownKeyboardActionHandler(handler: FlyDownKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindOpenInventoryKeyboardInputHandler(handler: OpenInventoryKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindCloseGameWindowKeyboardInputHandler(handler: CloseGameWindowKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindToggleDebugInfoKeyboardInputHandler(handler: ToggleDebugInfoKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindToggleMinimapKeyboardInputHandler(handler: ToggleMinimapKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindToggleGameModeKeyboardInputHandler(handler: ToggleGameModeKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindPauseGameKeyboardInputHandler(handler: PauseGameKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindToggleControlsModeKeyboardInputHandler(handler: ToggleControlsModeKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindMoveCursorControlsModeKeyboardInputHandler(handler: MoveCursorControlsModeKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindOpenCraftingKeyboardInputHandler(handler: OpenCraftingKeyboardInputHandler): IGameInputHandler<KeyboardInputAction> {
+        return handler
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/MouseInputHandlersModule.kt b/core/src/ru/deadsoftware/cavedroid/game/input/MouseInputHandlersModule.kt
new file mode 100644 (file)
index 0000000..937b3e6
--- /dev/null
@@ -0,0 +1,75 @@
+package ru.deadsoftware.cavedroid.game.input
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.handler.mouse.*
+
+@Module
+object MouseInputHandlersModule {
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindCursorMouseInputHandler(handler: CursorMouseInputHandler): IGameInputHandler<MouseInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindHoldHotbarMouseInputHandler(handler: HotbarMouseInputHandler): IGameInputHandler<MouseInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindCloseGameWindowMouseActionHandler(handler: CloseGameWindowMouseInputHandler): IGameInputHandler<MouseInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindCreativeInventoryScrollMouseInputHandler(handler: CreativeInventoryScrollMouseInputHandler): IGameInputHandler<MouseInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindSelectCreativeInventoryItemMouseActionHandler(handler: SelectCreativeInventoryItemMouseInputHandler): IGameInputHandler<MouseInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindAttackMouseInputHandler(handler: AttackMouseInputHandler): IGameInputHandler<MouseInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindUseItemMouseInputActionHandler(handler: UseItemMouseInputHandler): IGameInputHandler<MouseInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindSelectSurvivalInventoryItemMouseInputHandler(handler: SelectSurvivalInventoryItemMouseInputHandler): IGameInputHandler<MouseInputAction> {
+        return handler
+    }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindSelectCraftingInventoryItemMouseInputHandler(handler: SelectCraftingInventoryItemMouseInputHandler): IGameInputHandler<MouseInputAction> {
+        return handler
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/action/IGameInputAction.kt b/core/src/ru/deadsoftware/cavedroid/game/input/action/IGameInputAction.kt
new file mode 100644 (file)
index 0000000..b982a92
--- /dev/null
@@ -0,0 +1,3 @@
+package ru.deadsoftware.cavedroid.game.input.action
+
+interface IGameInputAction
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/action/KeyboardInputAction.kt b/core/src/ru/deadsoftware/cavedroid/game/input/action/KeyboardInputAction.kt
new file mode 100644 (file)
index 0000000..416d2e0
--- /dev/null
@@ -0,0 +1,8 @@
+package ru.deadsoftware.cavedroid.game.input.action
+
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+
+data class KeyboardInputAction(
+    val actionKey: KeyboardInputActionKey,
+    val isKeyDown: Boolean,
+) : IGameInputAction
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/action/MouseInputAction.kt b/core/src/ru/deadsoftware/cavedroid/game/input/action/MouseInputAction.kt
new file mode 100644 (file)
index 0000000..122ffda
--- /dev/null
@@ -0,0 +1,11 @@
+package ru.deadsoftware.cavedroid.game.input.action
+
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+
+data class MouseInputAction(
+    val screenX: Float,
+    val screenY: Float,
+    val actionKey: MouseInputActionKey,
+    val cameraViewport: Rectangle
+) : IGameInputAction
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/action/keys/KeyboardInputActionKey.kt b/core/src/ru/deadsoftware/cavedroid/game/input/action/keys/KeyboardInputActionKey.kt
new file mode 100644 (file)
index 0000000..fb59efb
--- /dev/null
@@ -0,0 +1,24 @@
+package ru.deadsoftware.cavedroid.game.input.action.keys
+
+sealed interface KeyboardInputActionKey {
+
+    data object Left : KeyboardInputActionKey
+    data object Right : KeyboardInputActionKey
+    data object Down : KeyboardInputActionKey
+
+    data object Jump : KeyboardInputActionKey
+
+    data object Crouch : KeyboardInputActionKey
+
+    data object SwitchControlsMode : KeyboardInputActionKey
+
+    data object OpenInventory : KeyboardInputActionKey
+
+    data object Pause : KeyboardInputActionKey
+
+    data object ShowDebug : KeyboardInputActionKey
+    data object SpawnPig : KeyboardInputActionKey
+    data object SwitchGameMode : KeyboardInputActionKey
+    data object ShowMap : KeyboardInputActionKey
+    data object OpenCraft : KeyboardInputActionKey
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/action/keys/MouseInputActionKey.kt b/core/src/ru/deadsoftware/cavedroid/game/input/action/keys/MouseInputActionKey.kt
new file mode 100644 (file)
index 0000000..5822760
--- /dev/null
@@ -0,0 +1,41 @@
+package ru.deadsoftware.cavedroid.game.input.action.keys
+
+sealed interface MouseInputActionKey {
+
+    val touchUp: Boolean
+
+    data object None : MouseInputActionKey {
+        override val touchUp: Boolean
+            get() = throw IllegalAccessException("not applicable for mouse move action")
+    }
+
+    data object Dragged : MouseInputActionKey {
+        override val touchUp: Boolean
+            get() = throw IllegalAccessException("not applicable for mouse dragged action")
+    }
+
+    data class Left(
+        override val touchUp: Boolean
+    ) : MouseInputActionKey
+
+    data class Right(
+        override val touchUp: Boolean
+    ) : MouseInputActionKey
+
+    data class Middle(
+        override val touchUp: Boolean
+    ) : MouseInputActionKey
+
+    data class Touch(
+        override val touchUp: Boolean
+    ) : MouseInputActionKey
+
+    data class Scroll(
+        val amountX: Float,
+        val amountY: Float
+    ) : MouseInputActionKey {
+        override val touchUp: Boolean
+            get() = throw IllegalAccessException("not applicable for mouse scroll action")
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/CloseGameWindowKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/CloseGameWindowKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..705f9aa
--- /dev/null
@@ -0,0 +1,39 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.objects.DropController
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import javax.inject.Inject
+
+@GameScope
+class CloseGameWindowKeyboardInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val dropController: DropController,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.OpenInventory &&
+                action.isKeyDown && gameWindowsManager.getCurrentWindow() != GameUiWindow.NONE
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        val selectedItem = gameWindowsManager.currentWindow?.selectedItem
+        if (selectedItem != null) {
+            for (i in 1 .. selectedItem.amount) {
+                dropController.addDrop(
+                    /* x = */ mobsController.player.x + (32f * mobsController.player.direction.basis),
+                    /* y = */ mobsController.player.y,
+                    /* item = */ selectedItem.item
+                )
+            }
+            gameWindowsManager.currentWindow?.selectedItem = null
+        }
+        gameWindowsManager.closeWindow()
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyDownKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyDownKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..90347c4
--- /dev/null
@@ -0,0 +1,32 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.Player
+import javax.inject.Inject
+
+@GameScope
+class FlyDownKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.Down &&
+                mobsController.player.isFlyMode &&
+                (mobsController.player.controlMode == Player.ControlMode.WALK || !mainConfig.isTouch)
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        if (action.isKeyDown) {
+            mobsController.player.velocity.y = mobsController.player.speed
+        } else {
+            mobsController.player.velocity.y = 0f
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyUpKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyUpKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..2c6efc9
--- /dev/null
@@ -0,0 +1,32 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.Player
+import javax.inject.Inject
+
+@GameScope
+class FlyUpKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.Jump &&
+                mobsController.player.isFlyMode &&
+                (mobsController.player.controlMode == Player.ControlMode.WALK || !mainConfig.isTouch)
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        if (action.isKeyDown) {
+            mobsController.player.velocity.y = -mobsController.player.speed
+        } else {
+            mobsController.player.velocity.y = 0f
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoLeftKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoLeftKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..07ddb52
--- /dev/null
@@ -0,0 +1,33 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.Mob
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.Player
+import javax.inject.Inject
+
+@GameScope
+class GoLeftKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.Left &&
+                (mobsController.player.controlMode == Player.ControlMode.WALK || !mainConfig.isTouch) &&
+                (mobsController.player.controlMode == Player.ControlMode.WALK || !mainConfig.isTouch)
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        if (action.isKeyDown) {
+            mobsController.player.velocity.x = -mobsController.player.speed
+            mobsController.player.setDir(Mob.Direction.LEFT)
+        } else {
+            mobsController.player.velocity.x = 0f
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoRightKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoRightKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..8f8f038
--- /dev/null
@@ -0,0 +1,32 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.Mob
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.Player
+import javax.inject.Inject
+
+@GameScope
+class GoRightKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.Right &&
+                (mobsController.player.controlMode == Player.ControlMode.WALK || !mainConfig.isTouch)
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        if (action.isKeyDown) {
+            mobsController.player.velocity.x = mobsController.player.speed
+            mobsController.player.setDir(Mob.Direction.RIGHT)
+        } else {
+            mobsController.player.velocity.x = 0f
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/JumpKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/JumpKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..1fbc9fb
--- /dev/null
@@ -0,0 +1,29 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.Player
+import javax.inject.Inject
+
+@GameScope
+class JumpKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.Jump &&
+                mobsController.player.canJump() && !mobsController.player.isFlyMode &&
+                action.isKeyDown &&
+                (mobsController.player.controlMode == Player.ControlMode.WALK || !mainConfig.isTouch)
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        mobsController.player.jump()
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/MoveCursorControlsModeKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/MoveCursorControlsModeKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..ac82cb8
--- /dev/null
@@ -0,0 +1,62 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import com.badlogic.gdx.math.MathUtils
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.Player
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class MoveCursorControlsModeKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return mainConfig.isTouch &&
+                mobsController.player.controlMode == Player.ControlMode.CURSOR && action.isKeyDown &&
+                (action.actionKey is KeyboardInputActionKey.Left ||
+                action.actionKey is KeyboardInputActionKey.Right ||
+                        action.actionKey is KeyboardInputActionKey.Jump ||
+                        action.actionKey is KeyboardInputActionKey.Down)
+    }
+
+    private fun checkCursorBounds() {
+        val player = mobsController.player
+        if (player.gameMode == 0) {
+            val minCursorX = player.mapX - SURVIVAL_CURSOR_RANGE
+            val maxCursorX = player.mapX + SURVIVAL_CURSOR_RANGE
+            val minCursorY = player.middleMapY - SURVIVAL_CURSOR_RANGE
+            val maxCursorY = player.middleMapY + SURVIVAL_CURSOR_RANGE
+
+            player.cursorX = MathUtils.clamp(player.cursorX, minCursorX, maxCursorX)
+            player.cursorY = MathUtils.clamp(player.cursorY, minCursorY, maxCursorY)
+        }
+
+        player.cursorY = MathUtils.clamp(player.cursorY, 0, gameWorld.height - 1)
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        val player = mobsController.player
+
+        when (action.actionKey) {
+            KeyboardInputActionKey.Left -> player.cursorX--
+            KeyboardInputActionKey.Right -> player.cursorX++
+            KeyboardInputActionKey.Jump -> player.cursorY--
+            KeyboardInputActionKey.Down -> player.cursorY++
+            else -> return
+        }
+
+        checkCursorBounds()
+    }
+
+    companion object {
+        private const val SURVIVAL_CURSOR_RANGE = 4
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenCraftingKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenCraftingKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..d2e333c
--- /dev/null
@@ -0,0 +1,26 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.objects.DropController
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import javax.inject.Inject
+
+@GameScope
+class OpenCraftingKeyboardInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.OpenCraft &&
+                action.isKeyDown && gameWindowsManager.getCurrentWindow() == GameUiWindow.NONE
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        gameWindowsManager.openCrafting()
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenInventoryKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenInventoryKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..0e2c11b
--- /dev/null
@@ -0,0 +1,26 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.objects.DropController
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import javax.inject.Inject
+
+@GameScope
+class OpenInventoryKeyboardInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.OpenInventory &&
+                action.isKeyDown && gameWindowsManager.getCurrentWindow() == GameUiWindow.NONE
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        gameWindowsManager.openInventory()
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/PauseGameKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/PauseGameKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..60a9edf
--- /dev/null
@@ -0,0 +1,30 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameSaver
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.objects.DropController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class PauseGameKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val dropController: DropController,
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.Pause && action.isKeyDown
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        GameSaver.save(mainConfig, dropController, mobsController, gameWorld)
+        mainConfig.caveGame.quitGame()
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleControlsModeKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleControlsModeKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..3deacbd
--- /dev/null
@@ -0,0 +1,31 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.Player
+import javax.inject.Inject
+
+@GameScope
+class ToggleControlsModeKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.SwitchControlsMode && action.isKeyDown
+                && mainConfig.isTouch
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        if (mobsController.player.controlMode == Player.ControlMode.WALK) {
+            mobsController.player.controlMode = Player.ControlMode.CURSOR
+        } else {
+            mobsController.player.controlMode = Player.ControlMode.WALK
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleDebugInfoKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleDebugInfoKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..96bc21d
--- /dev/null
@@ -0,0 +1,22 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import javax.inject.Inject
+
+@GameScope
+class ToggleDebugInfoKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.ShowDebug && action.isKeyDown
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        mainConfig.isShowInfo = !mainConfig.isShowInfo
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleGameModeKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleGameModeKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..ef4e696
--- /dev/null
@@ -0,0 +1,29 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import javax.inject.Inject
+
+@GameScope
+class ToggleGameModeKeyboardInputHandler @Inject constructor(
+    private val mobsController: MobsController
+) : IGameInputHandler<KeyboardInputAction> {
+
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.SwitchGameMode && action.isKeyDown
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        if (mobsController.player.gameMode == 1) {
+            mobsController.player.gameMode = 0
+        } else if (mobsController.player.gameMode == 0) {
+            mobsController.player.gameMode = 1
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleMinimapKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleMinimapKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..d39dfdb
--- /dev/null
@@ -0,0 +1,22 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import javax.inject.Inject
+
+@GameScope
+class ToggleMinimapKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.ShowMap && action.isKeyDown
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        mainConfig.isShowMap = !mainConfig.isShowMap
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/TurnOnFlyModeKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/TurnOnFlyModeKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..6a73a9b
--- /dev/null
@@ -0,0 +1,29 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.Player
+import javax.inject.Inject
+
+@GameScope
+class TurnOnFlyModeKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IGameInputHandler<KeyboardInputAction> {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return mobsController.player.gameMode == 1 && action.actionKey is KeyboardInputActionKey.Jump &&
+                !mobsController.player.isFlyMode && !mobsController.player.canJump() && action.isKeyDown &&
+                (mobsController.player.controlMode == Player.ControlMode.WALK || !mainConfig.isTouch)
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        mobsController.player.isFlyMode = true
+        mobsController.player.velocity.y = 0f
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/AttackMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/AttackMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..a0bfa4c
--- /dev/null
@@ -0,0 +1,35 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import ru.deadsoftware.cavedroid.game.input.isInsideHotbar
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class AttackMouseInputHandler @Inject constructor(
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+    private val gameWindowsManager: GameWindowsManager
+) : IGameInputHandler<MouseInputAction> {
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return gameWindowsManager.getCurrentWindow() == GameUiWindow.NONE &&
+                !isInsideHotbar(action) &&
+                action.actionKey is MouseInputActionKey.Left
+
+    }
+
+    override fun handle(action: MouseInputAction) {
+        if (action.actionKey.touchUp) {
+            mobsController.player.stopHitting()
+        } else {
+            mobsController.player.startHitting()
+        };
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CloseGameWindowMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CloseGameWindowMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..e80ff47
--- /dev/null
@@ -0,0 +1,58 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import com.badlogic.gdx.graphics.g2d.TextureRegion
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import ru.deadsoftware.cavedroid.game.input.isInsideWindow
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.objects.DropController
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+class CloseGameWindowMouseInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val dropController: DropController,
+) : IGameInputHandler<MouseInputAction> {
+
+    private val creativeInventoryTexture get() = requireNotNull(Assets.textureRegions["creative"])
+    private val survivalInventoryTexture get() = requireNotNull(Assets.textureRegions["survival"])
+    private val craftingInventoryTexture get() = requireNotNull(Assets.textureRegions["crafting_table"])
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return gameWindowsManager.getCurrentWindow() != GameUiWindow.NONE &&
+                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) &&
+                !action.actionKey.touchUp &&
+                !isInsideWindow(action, getCurrentWindowTexture())
+    }
+
+    private fun getCurrentWindowTexture(): TextureRegion {
+        return when (val window = gameWindowsManager.getCurrentWindow()) {
+            GameUiWindow.CREATIVE_INVENTORY -> creativeInventoryTexture
+            GameUiWindow.SURVIVAL_INVENTORY -> survivalInventoryTexture
+            GameUiWindow.CRAFTING_TABLE -> craftingInventoryTexture
+            else -> throw UnsupportedOperationException("Cant close window ${window.name}")
+        }
+    }
+
+    override fun handle(action: MouseInputAction) {
+        val selectedItem = gameWindowsManager.currentWindow?.selectedItem
+        if (selectedItem != null) {
+            for (i in 1 .. selectedItem.amount) {
+                dropController.addDrop(
+                    /* x = */ mobsController.player.x + (32f * mobsController.player.direction.basis),
+                    /* y = */ mobsController.player.y,
+                    /* item = */ selectedItem.item
+                )
+            }
+            gameWindowsManager.currentWindow?.selectedItem = null
+        }
+        gameWindowsManager.closeWindow()
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CreativeInventoryScrollMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CreativeInventoryScrollMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..01cc4e2
--- /dev/null
@@ -0,0 +1,88 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import com.badlogic.gdx.math.MathUtils
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import ru.deadsoftware.cavedroid.game.input.isInsideWindow
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+import kotlin.math.abs
+
+@GameScope
+class CreativeInventoryScrollMouseInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val gameWindowsManager: GameWindowsManager,
+    private val gameItemsHolder: GameItemsHolder,
+) : IGameInputHandler<MouseInputAction> {
+
+    private val creativeInventoryTexture get() = requireNotNull(Assets.textureRegions["creative"])
+
+    private var dragStartY = 0f
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return gameWindowsManager.getCurrentWindow() == GameUiWindow.CREATIVE_INVENTORY &&
+                (gameWindowsManager.isDragging || isInsideWindow(action, creativeInventoryTexture)) &&
+                (checkStartDragConditions(action) || checkEndDragConditions(action) ||
+                        checkDragConditions(action) || action.actionKey is MouseInputActionKey.Scroll)
+
+    }
+
+    private fun checkStartDragConditions(action: MouseInputAction): Boolean {
+        return (action.actionKey is MouseInputActionKey.Touch) &&
+                !action.actionKey.touchUp && !gameWindowsManager.isDragging
+    }
+
+    private fun checkEndDragConditions(action: MouseInputAction): Boolean {
+        return action.actionKey is MouseInputActionKey.Touch &&
+                action.actionKey.touchUp && gameWindowsManager.isDragging
+    }
+
+    private fun checkDragConditions(action: MouseInputAction): Boolean {
+        return mainConfig.isTouch && action.actionKey is MouseInputActionKey.Dragged &&
+                abs(action.screenY - dragStartY) >= DRAG_SENSITIVITY
+    }
+
+    private fun clampScrollAmount() {
+        gameWindowsManager.creativeScrollAmount =
+            MathUtils.clamp(gameWindowsManager.creativeScrollAmount, 0, gameItemsHolder.getMaxCreativeScrollAmount())
+    }
+
+    private fun handleStartOrEndDrag(action: MouseInputAction) {
+        if (gameWindowsManager.isDragging) {
+            gameWindowsManager.isDragging = false
+        } else {
+            dragStartY = action.screenY
+        }
+    }
+
+    private fun handleDrag(action: MouseInputAction) {
+        gameWindowsManager.isDragging = true
+        gameWindowsManager.creativeScrollAmount += ((dragStartY - action.screenY) / DRAG_SENSITIVITY).toInt()
+        clampScrollAmount()
+        dragStartY = action.screenY
+    }
+
+    private fun handleScroll(action: MouseInputAction) {
+        gameWindowsManager.creativeScrollAmount += (action.actionKey as MouseInputActionKey.Scroll).amountY.toInt()
+        clampScrollAmount()
+    }
+
+    override fun handle(action: MouseInputAction) {
+        when (action.actionKey) {
+            is MouseInputActionKey.Touch -> handleStartOrEndDrag(action)
+            is MouseInputActionKey.Dragged -> handleDrag(action)
+            is MouseInputActionKey.Scroll -> handleScroll(action)
+            else -> return
+        }
+    }
+
+    companion object {
+        private const val DRAG_SENSITIVITY = 16f
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CursorMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CursorMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..54aacf2
--- /dev/null
@@ -0,0 +1,120 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import com.badlogic.gdx.math.MathUtils
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import ru.deadsoftware.cavedroid.game.mobs.Mob
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.Player
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.utils.bl
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+class CursorMouseInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+) : IGameInputHandler<MouseInputAction> {
+
+    private val player get() = mobsController.player
+
+    private val Block.isAutoselectable
+        get() = !isNone() && params.hasCollision
+
+    private fun GameWorld.isCurrentBlockAutoselectable() =
+        getForeMap(player.cursorX, player.cursorY).isAutoselectable
+
+    private fun checkCursorBounds() {
+        if (player.gameMode == 0) {
+            val minCursorX = player.mapX - SURVIVAL_CURSOR_RANGE
+            val maxCursorX = player.mapX + SURVIVAL_CURSOR_RANGE
+            val minCursorY = player.middleMapY - SURVIVAL_CURSOR_RANGE
+            val maxCursorY = player.middleMapY + SURVIVAL_CURSOR_RANGE
+
+            player.cursorX = MathUtils.clamp(player.cursorX, minCursorX, maxCursorX)
+            player.cursorY = MathUtils.clamp(player.cursorY, minCursorY, maxCursorY)
+        }
+
+        player.cursorY = MathUtils.clamp(player.cursorY, 0, gameWorld.height - 1)
+    }
+
+    private fun setPlayerDirectionToCursor() {
+        if (player.controlMode != Player.ControlMode.CURSOR) {
+            return
+        }
+
+        if (player.cursorX.px + 8 < player.x + player.width / 2) {
+            player.setDir(Mob.Direction.LEFT)
+        } else {
+            player.setDir(Mob.Direction.RIGHT)
+        }
+    }
+
+    private fun handleWalkTouch() {
+        player.cursorX = player.mapX + player.direction.basis
+        player.cursorY = player.upperMapY
+
+        for (i in 1..2) {
+            if (gameWorld.isCurrentBlockAutoselectable()) {
+                break
+            }
+            player.cursorY++
+        }
+
+        if (!gameWorld.isCurrentBlockAutoselectable()) {
+            player.cursorX -= player.direction.basis
+        }
+    }
+
+    private fun getPlayerHeadRotation(mouseWorldX: Float, mouseWorldY: Float): Float {
+        val h = mouseWorldX - player.x
+        val v = mouseWorldY - player.y
+
+        return MathUtils.atan(v / h) * MathUtils.radDeg
+    }
+
+    private fun handleMouse(action: MouseInputAction) {
+        val worldX = action.screenX + action.cameraViewport.x
+        val worldY = action.screenY + action.cameraViewport.y
+
+        // when worldX < 0, need to subtract 1 to avoid negative zero
+//        val fixCycledWorld = if (worldX < 0) 1 else 0
+
+        player.cursorX = worldX.bl - 0
+        player.cursorY = worldY.bl
+
+        player.headRotation = getPlayerHeadRotation(worldX, worldY)
+    }
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return action.actionKey is MouseInputActionKey.None
+    }
+
+    override fun handle(action: MouseInputAction) {
+        val pastCursorX = player.cursorX
+        val pastCursorY = player.cursorY
+
+        when {
+            player.controlMode == Player.ControlMode.WALK && mainConfig.isTouch -> handleWalkTouch()
+            !mainConfig.isTouch -> handleMouse(action)
+        }
+
+        checkCursorBounds()
+        setPlayerDirectionToCursor()
+
+        if (player.cursorX != pastCursorX || player.cursorY != pastCursorY) {
+            player.blockDamage = 0f
+        }
+    }
+
+    companion object {
+        private const val SURVIVAL_CURSOR_RANGE = 4
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/HotbarMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/HotbarMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..31827d2
--- /dev/null
@@ -0,0 +1,97 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import com.badlogic.gdx.utils.Timer
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import ru.deadsoftware.cavedroid.game.input.isInsideHotbar
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+class HotbarMouseInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+) : IGameInputHandler<MouseInputAction> {
+
+    private val hotbarTexture get() = requireNotNull(Assets.textureRegions["hotbar"])
+
+    private var buttonHoldTask: Timer.Task? = null
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return buttonHoldTask?.isScheduled == true ||
+                ((action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch)
+                        && isInsideHotbar(action)
+                        || action.actionKey is MouseInputActionKey.Scroll) &&
+                gameWindowsManager.getCurrentWindow() == GameUiWindow.NONE
+    }
+
+    private fun cancelHold() {
+        buttonHoldTask?.cancel()
+        buttonHoldTask = null
+    }
+
+    private fun handleHold() {
+        buttonHoldTask = null
+        gameWindowsManager.openInventory()
+    }
+
+    private fun handleDown(action: MouseInputAction) {
+        buttonHoldTask = object : Timer.Task() {
+            override fun run() {
+                handleHold()
+            }
+        }
+
+        Timer.schedule(buttonHoldTask, TOUCH_HOLD_TIME_SEC)
+    }
+
+    private fun handleUp(action: MouseInputAction) {
+        mobsController.player.slot =
+            ((action.screenX -
+                    (action.cameraViewport.width / 2 - hotbarTexture.regionWidth / 2))
+                    / HOTBAR_CELL_WIDTH).toInt()
+    }
+
+    private fun handleScroll(action: MouseInputAction) {
+        if (action.actionKey !is MouseInputActionKey.Scroll) {
+            return
+        }
+        mobsController.player.slot += action.actionKey.amountY.toInt()
+        if (mobsController.player.slot < 0) {
+            mobsController.player.slot = HOTBAR_ITEMS - 1
+        } else if (mobsController.player.slot >= HOTBAR_ITEMS){
+            mobsController.player.slot = 0
+        }
+    }
+
+    override fun handle(action: MouseInputAction) {
+        if (buttonHoldTask != null && buttonHoldTask?.isScheduled == true) {
+            cancelHold()
+        }
+
+        if (action.actionKey !is MouseInputActionKey.Left && action.actionKey !is MouseInputActionKey.Touch ) {
+            if (action.actionKey is MouseInputActionKey.Scroll) {
+                handleScroll(action)
+            }
+            return
+        }
+
+        if (action.actionKey.touchUp) {
+            handleUp(action)
+        } else {
+            handleDown(action)
+        }
+    }
+
+    companion object {
+        private const val TOUCH_HOLD_TIME_SEC = 0.5f
+        private const val HOTBAR_CELL_WIDTH = 20
+        private const val HOTBAR_ITEMS = 9
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCraftingInventoryItemMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCraftingInventoryItemMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..7005a67
--- /dev/null
@@ -0,0 +1,161 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import com.badlogic.gdx.Gdx
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import ru.deadsoftware.cavedroid.game.input.isInsideWindow
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.windows.inventory.CraftingInventoryWindow
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+class SelectCraftingInventoryItemMouseInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : IGameInputHandler<MouseInputAction> {
+
+    private val survivalWindowTexture get() = requireNotNull(Assets.textureRegions["survival"])
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return gameWindowsManager.getCurrentWindow() == GameUiWindow.CRAFTING_TABLE &&
+                isInsideWindow(action, survivalWindowTexture) &&
+                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Right || action.actionKey is MouseInputActionKey.Touch)
+                && action.actionKey.touchUp
+    }
+
+    private fun onLeftCLick(items: MutableList<InventoryItem?>, window: CraftingInventoryWindow, index: Int) {
+        val selectedItem = window.selectedItem
+        val clickedItem = items[index]
+
+        if (clickedItem != null && selectedItem != null && items[index]!!.item == selectedItem.item &&
+            items[index]!!.amount + selectedItem.amount <= selectedItem.item.params.maxStack) {
+            items[index]!!.amount += selectedItem.amount
+            window.selectedItem = null
+            return
+        }
+
+        val item = items[index]
+        items[index] = selectedItem ?: gameItemsHolder.fallbackItem.toInventoryItem()
+        window.selectedItem = item
+    }
+
+    private fun onRightClick(items: MutableList<InventoryItem?>, window: CraftingInventoryWindow, index: Int) {
+        val clickedItem = items[index]
+        val selectedItem = window.selectedItem
+            ?.takeIf { clickedItem == null || clickedItem.item.isNone() || it.item == items[index]!!.item && items[index]!!.amount + 1 < it.item.params.maxStack }
+            ?: return
+
+        val newItem = selectedItem.item.toInventoryItem((clickedItem?.takeIf { !it.item.isNone() }?.amount ?: 0) + 1)
+        items[index] = newItem
+        selectedItem.amount --
+
+        if (selectedItem.amount <= 0) {
+            window.selectedItem = null
+        }
+    }
+
+    private fun handleInsideInventoryGrid(action: MouseInputAction, xOnGrid: Int, yOnGrid: Int) {
+        val window = gameWindowsManager.currentWindow as CraftingInventoryWindow
+
+        var itemIndex = ((xOnGrid.toInt() + yOnGrid.toInt() * GameWindowsConfigs.Crafting.itemsInRow))
+        itemIndex += GameWindowsConfigs.Crafting.hotbarCells
+
+        if (itemIndex >= 36) {
+            itemIndex -= 36
+        }
+
+        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) {
+            onLeftCLick(mobsController.player.inventory, window, itemIndex)
+        } else {
+            onRightClick(mobsController.player.inventory, window, itemIndex)
+        }
+
+        Gdx.app.debug(
+            TAG,
+            "selected item: ${window.selectedItem?.item?.params?.key ?: "null"}; index $itemIndex, grid ($xOnGrid;$yOnGrid)"
+        )
+    }
+
+    private fun handleInsideCraft(action: MouseInputAction, xOnCraft: Int, yOnCraft: Int) {
+        val window = gameWindowsManager.currentWindow as CraftingInventoryWindow
+        val index = xOnCraft + yOnCraft * GameWindowsConfigs.Crafting.craftGridSize
+
+        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) {
+            onLeftCLick(window.craftingItems, window, index)
+        } else {
+            onRightClick(window.craftingItems, window, index)
+        }
+
+        window.craftResult =
+            gameItemsHolder.craftItem(window.craftingItems.map { it?.item ?: gameItemsHolder.fallbackItem })
+    }
+
+    override fun handle(action: MouseInputAction) {
+        val survivalTexture = survivalWindowTexture
+        val window = gameWindowsManager.currentWindow as CraftingInventoryWindow
+
+        val xOnWindow = action.screenX - (action.cameraViewport.width / 2 - survivalTexture.regionWidth / 2)
+        val yOnWindow = action.screenY - (action.cameraViewport.height / 2 - survivalTexture.regionHeight / 2)
+
+        val xOnGrid = (xOnWindow - GameWindowsConfigs.Crafting.itemsGridMarginLeft) /
+                GameWindowsConfigs.Crafting.itemsGridColWidth
+        val yOnGrid = (yOnWindow - GameWindowsConfigs.Crafting.itemsGridMarginTop) /
+                GameWindowsConfigs.Crafting.itemsGridRowHeight
+
+        val xOnCraft = (xOnWindow - GameWindowsConfigs.Crafting.craftOffsetX) /
+                GameWindowsConfigs.Crafting.itemsGridColWidth
+        val yOnCraft = (yOnWindow - GameWindowsConfigs.Crafting.craftOffsetY) /
+                GameWindowsConfigs.Crafting.itemsGridRowHeight
+
+        val isInsideInventoryGrid = xOnGrid >= 0 && xOnGrid < GameWindowsConfigs.Crafting.itemsInRow &&
+                yOnGrid >= 0 && yOnGrid < GameWindowsConfigs.Crafting.itemsInCol
+
+        val isInsideCraftGrid = xOnCraft >= 0 && xOnCraft < GameWindowsConfigs.Crafting.craftGridSize &&
+                yOnCraft >= 0 && yOnCraft < GameWindowsConfigs.Crafting.craftGridSize
+
+        val isInsideCraftResult = xOnWindow > GameWindowsConfigs.Crafting.craftResultOffsetX &&
+                xOnWindow < GameWindowsConfigs.Crafting.craftResultOffsetX + GameWindowsConfigs.Crafting.itemsGridColWidth &&
+                yOnWindow > GameWindowsConfigs.Crafting.craftResultOffsetY &&
+                yOnWindow < GameWindowsConfigs.Crafting.craftResultOffsetY + GameWindowsConfigs.Crafting.itemsGridRowHeight
+
+        if (isInsideInventoryGrid) {
+            handleInsideInventoryGrid(action, xOnGrid.toInt(), yOnGrid.toInt())
+        } else if (isInsideCraftGrid) {
+            handleInsideCraft(action, xOnCraft.toInt(), yOnCraft.toInt())
+        } else if (isInsideCraftResult) {
+            val selectedItem = window.selectedItem
+            if (selectedItem == null || selectedItem.item.isNone() ||
+                (selectedItem.item == window.craftResult?.item && selectedItem.amount + (window.craftResult?.amount ?: 0) <= selectedItem.item.params.maxStack)) {
+                for (i in window.craftingItems.indices) {
+                    if ((window.craftingItems[i]?.amount ?: 0) > 1) {
+                        window.craftingItems[i]?.amount = window.craftingItems[i]?.amount!! - 1
+                    } else {
+                        window.craftingItems[i] = null
+                    }
+                }
+                if (selectedItem != null && !selectedItem.item.isNone()) {
+                    selectedItem.amount += (window.craftResult?.amount ?: 0)
+                } else {
+                    window.selectedItem = window.craftResult
+                }
+                window.craftResult = gameItemsHolder.craftItem(window.craftingItems
+                    .map { it?.item ?: gameItemsHolder.fallbackItem })
+            }
+        }
+
+    }
+
+    companion object {
+        private const val TAG = "SelectCraftingInventoryItemMouseInputHandler"
+
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCreativeInventoryItemMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCreativeInventoryItemMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..7c587b6
--- /dev/null
@@ -0,0 +1,58 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import ru.deadsoftware.cavedroid.game.input.isInsideWindow
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+class SelectCreativeInventoryItemMouseInputHandler @Inject constructor(
+    private val gameItemsHolder: GameItemsHolder,
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+) : IGameInputHandler<MouseInputAction> {
+
+    private val creativeInventoryTexture get() = requireNotNull(Assets.textureRegions["creative"])
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return gameWindowsManager.getCurrentWindow() == GameUiWindow.CREATIVE_INVENTORY &&
+                !gameWindowsManager.isDragging &&
+                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) &&
+                action.actionKey.touchUp && isInsideWindow(action, creativeInventoryTexture)
+    }
+
+    override fun handle(action: MouseInputAction) {
+        val creativeTexture = creativeInventoryTexture
+        val xOnGrid = (action.screenX - (action.cameraViewport.width / 2 - creativeTexture.regionWidth / 2 +
+                GameWindowsConfigs.Creative.itemsGridMarginLeft)) /
+                GameWindowsConfigs.Creative.itemsGridColWidth
+        val yOnGrid = (action.screenY - (action.cameraViewport.height / 2 - creativeTexture.regionHeight / 2 +
+                GameWindowsConfigs.Creative.itemsGridMarginTop)) /
+                GameWindowsConfigs.Creative.itemsGridRowHeight
+
+        if (xOnGrid < 0 || xOnGrid >= GameWindowsConfigs.Creative.itemsInRow ||
+            yOnGrid < 0 || yOnGrid >= GameWindowsConfigs.Creative.itemsInCol) {
+            return
+        }
+
+        val itemIndex = (gameWindowsManager.creativeScrollAmount * GameWindowsConfigs.Creative.itemsInRow +
+                (xOnGrid.toInt() + yOnGrid.toInt() * GameWindowsConfigs.Creative.itemsInRow))
+        val item = gameItemsHolder.getItemFromCreativeInventory(itemIndex)
+        mobsController.player.inventory.reverse()
+        mobsController.player.inventory.add(item.toInventoryItem(amount = item.params.maxStack))
+        mobsController.player.inventory.reverse()
+
+        if (mobsController.player.inventory.size > 36) {
+            mobsController.player.inventory.dropLast(mobsController.player.inventory.size - 36)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectSurvivalInventoryItemMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectSurvivalInventoryItemMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..61e1b18
--- /dev/null
@@ -0,0 +1,161 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import com.badlogic.gdx.Gdx
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import ru.deadsoftware.cavedroid.game.input.isInsideWindow
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.windows.inventory.SurvivalInventoryWindow
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+class SelectSurvivalInventoryItemMouseInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : IGameInputHandler<MouseInputAction> {
+
+    private val survivalWindowTexture get() = requireNotNull(Assets.textureRegions["survival"])
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return gameWindowsManager.getCurrentWindow() == GameUiWindow.SURVIVAL_INVENTORY &&
+                isInsideWindow(action, survivalWindowTexture) &&
+                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Right || action.actionKey is MouseInputActionKey.Touch)
+                && action.actionKey.touchUp
+    }
+
+    private fun onLeftCLick(items: MutableList<InventoryItem?>, window: SurvivalInventoryWindow, index: Int) {
+        val selectedItem = window.selectedItem
+        val clickedItem = items[index]
+
+        if (clickedItem != null && selectedItem != null && items[index]!!.item == selectedItem.item &&
+            items[index]!!.amount + selectedItem.amount <= selectedItem.item.params.maxStack) {
+            items[index]!!.amount += selectedItem.amount
+            window.selectedItem = null
+            return
+        }
+
+        val item = items[index]
+        items[index] = selectedItem ?: gameItemsHolder.fallbackItem.toInventoryItem()
+        window.selectedItem = item
+    }
+
+    private fun onRightClick(items: MutableList<InventoryItem?>, window: SurvivalInventoryWindow, index: Int) {
+        val clickedItem = items[index]
+        val selectedItem = window.selectedItem
+            ?.takeIf { clickedItem == null || clickedItem.item.isNone() || it.item == items[index]!!.item && items[index]!!.amount + 1 < it.item.params.maxStack }
+            ?: return
+
+        val newItem = selectedItem.item.toInventoryItem((clickedItem?.takeIf { !it.item.isNone() }?.amount ?: 0) + 1)
+        items[index] = newItem
+        selectedItem.amount --
+
+        if (selectedItem.amount <= 0) {
+            window.selectedItem = null
+        }
+    }
+
+    private fun handleInsideInventoryGrid(action: MouseInputAction, xOnGrid: Int, yOnGrid: Int) {
+        val window = gameWindowsManager.currentWindow as SurvivalInventoryWindow
+
+        var itemIndex = ((xOnGrid.toInt() + yOnGrid.toInt() * GameWindowsConfigs.Survival.itemsInRow))
+        itemIndex += GameWindowsConfigs.Survival.hotbarCells
+
+        if (itemIndex >= 36) {
+            itemIndex -= 36
+        }
+
+        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) {
+            onLeftCLick(mobsController.player.inventory, window, itemIndex)
+        } else {
+            onRightClick(mobsController.player.inventory, window, itemIndex)
+        }
+
+        Gdx.app.debug(
+            TAG,
+            "selected item: ${window.selectedItem?.item?.params?.key ?: "null"}; index $itemIndex, grid ($xOnGrid;$yOnGrid)"
+        )
+    }
+
+    private fun handleInsideCraft(action: MouseInputAction, xOnCraft: Int, yOnCraft: Int) {
+        val window = gameWindowsManager.currentWindow as SurvivalInventoryWindow
+        val index = xOnCraft + yOnCraft * GameWindowsConfigs.Crafting.craftGridSize // this is crafting on purpose!!
+
+        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) {
+            onLeftCLick(window.craftingItems, window, index)
+        } else {
+            onRightClick(window.craftingItems, window, index)
+        }
+
+        window.craftResult =
+            gameItemsHolder.craftItem(window.craftingItems.map { it?.item ?: gameItemsHolder.fallbackItem })
+    }
+
+    override fun handle(action: MouseInputAction) {
+        val survivalTexture = survivalWindowTexture
+        val window = gameWindowsManager.currentWindow as SurvivalInventoryWindow
+
+        val xOnWindow = action.screenX - (action.cameraViewport.width / 2 - survivalTexture.regionWidth / 2)
+        val yOnWindow = action.screenY - (action.cameraViewport.height / 2 - survivalTexture.regionHeight / 2)
+
+        val xOnGrid = (xOnWindow - GameWindowsConfigs.Survival.itemsGridMarginLeft) /
+                GameWindowsConfigs.Survival.itemsGridColWidth
+        val yOnGrid = (yOnWindow - GameWindowsConfigs.Survival.itemsGridMarginTop) /
+                GameWindowsConfigs.Survival.itemsGridRowHeight
+
+        val xOnCraft = (xOnWindow - GameWindowsConfigs.Survival.craftOffsetX) /
+                GameWindowsConfigs.Survival.itemsGridColWidth
+        val yOnCraft = (yOnWindow - GameWindowsConfigs.Survival.craftOffsetY) /
+                GameWindowsConfigs.Survival.itemsGridRowHeight
+
+        val isInsideInventoryGrid = xOnGrid >= 0 && xOnGrid < GameWindowsConfigs.Survival.itemsInRow &&
+                yOnGrid >= 0 && yOnGrid < GameWindowsConfigs.Survival.itemsInCol
+
+        val isInsideCraftGrid = xOnCraft >= 0 && xOnCraft < GameWindowsConfigs.Survival.craftGridSize &&
+                yOnCraft >= 0 && yOnCraft < GameWindowsConfigs.Survival.craftGridSize
+
+        val isInsideCraftResult = xOnWindow > GameWindowsConfigs.Survival.craftResultOffsetX &&
+                xOnWindow < GameWindowsConfigs.Survival.craftResultOffsetX + GameWindowsConfigs.Survival.itemsGridColWidth &&
+                yOnWindow > GameWindowsConfigs.Survival.craftResultOffsetY &&
+                yOnWindow < GameWindowsConfigs.Survival.craftResultOffsetY + GameWindowsConfigs.Survival.itemsGridRowHeight
+
+        if (isInsideInventoryGrid) {
+            handleInsideInventoryGrid(action, xOnGrid.toInt(), yOnGrid.toInt())
+        } else if (isInsideCraftGrid) {
+            handleInsideCraft(action, xOnCraft.toInt(), yOnCraft.toInt())
+        } else if (isInsideCraftResult) {
+            val selectedItem = window.selectedItem
+            if (selectedItem == null || selectedItem.item.isNone() ||
+                (selectedItem.item == window.craftResult?.item && selectedItem.amount + (window.craftResult?.amount ?: 0) <= selectedItem.item.params.maxStack)) {
+                for (i in window.craftingItems.indices) {
+                    if ((window.craftingItems[i]?.amount ?: 0) > 1) {
+                        window.craftingItems[i]?.amount = window.craftingItems[i]?.amount!! - 1
+                    } else {
+                        window.craftingItems[i] = null
+                    }
+                }
+                if (selectedItem != null && !selectedItem.item.isNone()) {
+                    selectedItem.amount += (window.craftResult?.amount ?: 0)
+                } else {
+                    window.selectedItem = window.craftResult
+                }
+                window.craftResult = gameItemsHolder.craftItem(window.craftingItems
+                    .map { it?.item ?: gameItemsHolder.fallbackItem })
+            }
+        }
+
+    }
+
+    companion object {
+        private const val TAG = "SelectSurvivalInventoryItemMouseInputHandler"
+
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/UseItemMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/UseItemMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..e2d3f1d
--- /dev/null
@@ -0,0 +1,109 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.utils.Timer
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.actions.placeToBackgroundAction
+import ru.deadsoftware.cavedroid.game.actions.placeToForegroundAction
+import ru.deadsoftware.cavedroid.game.actions.placeblock.IPlaceBlockAction
+import ru.deadsoftware.cavedroid.game.actions.useitem.IUseItemAction
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import ru.deadsoftware.cavedroid.game.input.isInsideHotbar
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import javax.inject.Inject
+
+@GameScope
+class UseItemMouseInputHandler @Inject constructor(
+    private val mobsController: MobsController,
+    private val useItemActionMap: Map<String, @JvmSuppressWildcards IUseItemAction>,
+    private val placeBlockActionMap: Map<String, @JvmSuppressWildcards IPlaceBlockAction>,
+    private val gameWindowsManager: GameWindowsManager,
+) : IGameInputHandler<MouseInputAction> {
+
+    private var buttonHoldTask: Timer.Task? = null
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return buttonHoldTask?.isScheduled == true ||
+                !isInsideHotbar(action) &&
+                gameWindowsManager.getCurrentWindow() == GameUiWindow.NONE &&
+                action.actionKey is MouseInputActionKey.Right
+    }
+
+    private fun cancelHold() {
+        buttonHoldTask?.cancel()
+        buttonHoldTask = null
+    }
+
+    private fun handleHold(action: MouseInputAction) {
+        cancelHold()
+
+        val player = mobsController.player
+        val item = player.currentItem.item
+        player.startHitting(false)
+        player.stopHitting()
+
+        if (item is Item.Placeable) {
+            placeBlockActionMap.placeToBackgroundAction(
+                item = player.currentItem.item as Item.Placeable,
+                x = player.cursorX,
+                y = player.cursorY
+            )
+        }
+    }
+
+    private fun handleDown(action: MouseInputAction) {
+        cancelHold()
+        buttonHoldTask = object : Timer.Task() {
+            override fun run() {
+                handleHold(action)
+            }
+
+        }
+        Timer.schedule(buttonHoldTask, TOUCH_HOLD_TIME_SEC)
+    }
+
+    private fun handleUp(action: MouseInputAction) {
+        val player = mobsController.player
+        val item = player.currentItem.item
+        cancelHold()
+
+        player.startHitting(false)
+        player.stopHitting()
+
+        if (item is Item.Placeable) {
+            placeBlockActionMap.placeToForegroundAction(
+                item = item,
+                x = player.cursorX,
+                y = player.cursorY
+            )
+        } else if (item is Item.Usable) {
+            useItemActionMap[item.useActionKey]?.perform(item, player.cursorX, player.cursorY)
+                ?: Gdx.app.error(TAG, "use item action ${item.useActionKey} not found");
+        }
+    }
+
+    override fun handle(action: MouseInputAction) {
+        if (action.actionKey !is MouseInputActionKey.Right) {
+            if (buttonHoldTask?.isScheduled == true) {
+                cancelHold()
+            }
+            return
+        }
+
+        if (action.actionKey.touchUp && buttonHoldTask?.isScheduled == true) {
+            handleUp(action)
+        } else if (!action.actionKey.touchUp) {
+            handleDown(action)
+        }
+    }
+
+    companion object {
+        private const val TAG = "UseItemMouseInputActionHandler"
+        private const val TOUCH_HOLD_TIME_SEC = 0.5f
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/mapper/KeyboardInputActionMapper.kt b/core/src/ru/deadsoftware/cavedroid/game/input/mapper/KeyboardInputActionMapper.kt
new file mode 100644 (file)
index 0000000..ca91f48
--- /dev/null
@@ -0,0 +1,36 @@
+package ru.deadsoftware.cavedroid.game.input.mapper
+
+import com.badlogic.gdx.Input
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import javax.inject.Inject
+
+@GameScope
+class KeyboardInputActionMapper @Inject constructor() {
+
+    fun map(key: Int, isKeyDown: Boolean): KeyboardInputAction? {
+        val actionKey = when (key) {
+            Input.Keys.A, Input.Keys.LEFT -> KeyboardInputActionKey.Left
+            Input.Keys.D, Input.Keys.RIGHT -> KeyboardInputActionKey.Right
+            Input.Keys.W, Input.Keys.SPACE -> KeyboardInputActionKey.Jump
+            Input.Keys.S -> KeyboardInputActionKey.Down
+
+            Input.Keys.E -> KeyboardInputActionKey.OpenInventory
+            Input.Keys.ALT_LEFT -> KeyboardInputActionKey.SwitchControlsMode
+
+            Input.Keys.ESCAPE, Input.Keys.BACK -> KeyboardInputActionKey.Pause
+
+            Input.Keys.F1 -> KeyboardInputActionKey.ShowDebug
+            Input.Keys.GRAVE -> KeyboardInputActionKey.SwitchGameMode
+            Input.Keys.M -> KeyboardInputActionKey.ShowMap
+
+            Input.Keys.T -> KeyboardInputActionKey.OpenCraft
+
+            else -> null
+        }
+
+        return actionKey?.let { KeyboardInputAction(it, isKeyDown) }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/mapper/MouseInputActionMapper.kt b/core/src/ru/deadsoftware/cavedroid/game/input/mapper/MouseInputActionMapper.kt
new file mode 100644 (file)
index 0000000..254cee2
--- /dev/null
@@ -0,0 +1,80 @@
+package ru.deadsoftware.cavedroid.game.input.mapper
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.Input
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import javax.inject.Inject
+
+@GameScope
+class MouseInputActionMapper @Inject constructor(
+    val mainConfig: MainConfig,
+) {
+
+    fun map(
+        mouseX: Float,
+        mouseY: Float,
+        cameraViewport: Rectangle,
+        button: Int,
+        touchUp: Boolean
+    ): MouseInputAction? {
+        val actionKey = mapActionKey(button, touchUp) ?: return null
+
+        return MouseInputAction(
+            screenX = getScreenX(mouseX),
+            screenY = getScreenY(mouseY),
+            actionKey = actionKey,
+            cameraViewport = cameraViewport,
+        )
+    }
+
+    fun mapDragged(
+        mouseX: Float,
+        mouseY: Float,
+        cameraViewport: Rectangle,
+    ): MouseInputAction {
+        return MouseInputAction(
+            screenX = getScreenX(mouseX),
+            screenY = getScreenY(mouseY),
+            actionKey = MouseInputActionKey.Dragged,
+            cameraViewport = cameraViewport,
+        )
+    }
+
+    fun mapScrolled(
+        mouseX: Float,
+        mouseY: Float,
+        amountX: Float,
+        amountY: Float,
+        cameraViewport: Rectangle,
+    ): MouseInputAction {
+        return MouseInputAction(
+            screenX = getScreenX(mouseX),
+            screenY = getScreenY(mouseY),
+            actionKey = MouseInputActionKey.Scroll(amountX, amountY),
+            cameraViewport = cameraViewport,
+        )
+    }
+
+    private fun mapActionKey(button: Int, touchUp: Boolean): MouseInputActionKey? {
+        return when (button) {
+            Input.Buttons.LEFT -> MouseInputActionKey.Left(touchUp)
+            Input.Buttons.RIGHT -> MouseInputActionKey.Right(touchUp)
+            Input.Buttons.MIDDLE -> MouseInputActionKey.Middle(touchUp)
+            -1 -> MouseInputActionKey.Touch(touchUp)
+            else -> null
+        }
+    }
+
+    private fun getScreenX(mouseX: Float): Float {
+        return mouseX * (mainConfig.width / Gdx.graphics.width)
+    }
+
+    private fun getScreenY(mouseY: Float): Float {
+        return mouseY * (mainConfig.height / Gdx.graphics.height)
+    }
+
+}
\ No newline at end of file
index 23cf1dcc836de3b434aa7e9deea52ca53b691568..641ffe554ec84255bbe0b1099b3f67a9aded0464 100644 (file)
@@ -1,9 +1,14 @@
 package ru.deadsoftware.cavedroid.game.mobs;
 
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.Texture;
 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 import com.badlogic.gdx.math.Vector2;
-import ru.deadsoftware.cavedroid.game.GameItems;
-import ru.deadsoftware.cavedroid.game.GameWorld;
+import ru.deadsoftware.cavedroid.game.GameItemsHolder;
+import ru.deadsoftware.cavedroid.game.world.GameWorld;
+import ru.deadsoftware.cavedroid.misc.Assets;
+
+import javax.annotation.CheckForNull;
 
 /**
  * Falling gravel is actually a mob, that spawns in place of gravel when there is no block under it,
@@ -11,6 +16,8 @@ import ru.deadsoftware.cavedroid.game.GameWorld;
  */
 public class FallingGravel extends Mob {
 
+    private static final String TAG = "FallingGravel";
+
     /**
      * Creates a FallingGravel mob at coordinates
      *
@@ -18,14 +25,24 @@ public class FallingGravel extends Mob {
      * @param y Y in pixels
      */
     public FallingGravel(float x, float y) {
-        super(x, y, 16, 16, Direction.LEFT, Type.GRAVEL);
-        mMove = new Vector2(0, 1);
+        super(x, y, 16, 16, Direction.LEFT, Type.GRAVEL, Integer.MAX_VALUE);
+        mVelocity = new Vector2(0, 1);
+    }
+
+    @Override
+    public float getSpeed() {
+        return 0;
+    }
+
+    @Override
+    public void jump() {
+        // no-op
     }
 
     @Override
-    public void ai(GameWorld gameWorld) {
-        if (mMove.isZero()) {
-            gameWorld.setForeMap(getMapX(), getMiddleMapY(), 11);
+    public void ai(GameWorld gameWorld, GameItemsHolder gameItemsHolder, float delta) {
+        if (mVelocity.isZero()) {
+            gameWorld.setForeMap(getMapX(), getUpperMapY(), gameItemsHolder.getBlock("gravel"));
             kill();
         }
     }
@@ -35,8 +52,16 @@ public class FallingGravel extends Mob {
     }
 
     @Override
-    public void draw(SpriteBatch spriteBatch, float x, float y) {
-        spriteBatch.draw(GameItems.getBlockTex(11), x, y);
+    public void draw(SpriteBatch spriteBatch, float x, float y, float delta) {
+        @CheckForNull final Texture texture = Assets.blockTextures.get("gravel");
+
+        if (texture == null) {
+            Gdx.app.error(TAG, "Couldn't draw: texture not found");
+            kill();
+            return;
+        }
+
+        spriteBatch.draw(texture, x, y);
     }
 
 }
index 9a383a21315cce839145870231c0e6f9bbb8cb35..f41da96872c12aa7fc32ce6dffc885822d13935d 100644 (file)
@@ -1,9 +1,14 @@
 package ru.deadsoftware.cavedroid.game.mobs;
 
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.Texture;
 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 import com.badlogic.gdx.math.Vector2;
-import ru.deadsoftware.cavedroid.game.GameItems;
-import ru.deadsoftware.cavedroid.game.GameWorld;
+import ru.deadsoftware.cavedroid.game.GameItemsHolder;
+import ru.deadsoftware.cavedroid.game.world.GameWorld;
+import ru.deadsoftware.cavedroid.misc.Assets;
+
+import javax.annotation.CheckForNull;
 
 
 /**
@@ -12,6 +17,8 @@ import ru.deadsoftware.cavedroid.game.GameWorld;
  */
 public class FallingSand extends Mob {
 
+    private static final String TAG = "FallingSand";
+
     /**
      * Creates a FallingSand mob at coordinates
      *
@@ -19,14 +26,24 @@ public class FallingSand extends Mob {
      * @param y Y in pixels
      */
     public FallingSand(float x, float y) {
-        super(x, y, 16, 16, Direction.LEFT, Type.SAND);
-        mMove = new Vector2(0, 1);
+        super(x, y, 16, 16, Direction.LEFT, Type.SAND, Integer.MAX_VALUE);
+        mVelocity = new Vector2(0, 1);
+    }
+
+    @Override
+    public float getSpeed() {
+        return 0;
+    }
+
+    @Override
+    public void jump() {
+        // no-op
     }
 
     @Override
-    public void ai(GameWorld gameWorld) {
-        if (mMove.isZero()) {
-            gameWorld.setForeMap(getMapX(), getMiddleMapY(), 10);
+    public void ai(GameWorld gameWorld, GameItemsHolder gameItemsHolder, float delta) {
+        if (mVelocity.isZero()) {
+            gameWorld.setForeMap(getMapX(), getUpperMapY(), gameItemsHolder.getBlock("sand"));
             kill();
         }
     }
@@ -36,8 +53,16 @@ public class FallingSand extends Mob {
     }
 
     @Override
-    public void draw(SpriteBatch spriteBatch, float x, float y) {
-        spriteBatch.draw(GameItems.getBlockTex(10), x, y);
+    public void draw(SpriteBatch spriteBatch, float x, float y, float delta) {
+        @CheckForNull final Texture texture = Assets.blockTextures.get("sand");
+
+        if (texture == null) {
+            Gdx.app.error(TAG, "Couldn't draw: texture not found");
+            kill();
+            return;
+        }
+
+        spriteBatch.draw(texture, x, y);
     }
 
 }
index 0f765925bc1679bed83830f29011a5f0bd88def8..415ef30376503a491a3eefd9eb8c127d53089b77 100644 (file)
@@ -1,10 +1,12 @@
 package ru.deadsoftware.cavedroid.game.mobs;
 
+import com.badlogic.gdx.Gdx;
 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 import com.badlogic.gdx.math.MathUtils;
 import com.badlogic.gdx.math.Rectangle;
 import com.badlogic.gdx.math.Vector2;
-import ru.deadsoftware.cavedroid.game.GameWorld;
+import ru.deadsoftware.cavedroid.game.GameItemsHolder;
+import ru.deadsoftware.cavedroid.game.world.GameWorld;
 
 import java.io.Serializable;
 
@@ -13,6 +15,8 @@ import java.io.Serializable;
  */
 public abstract class Mob extends Rectangle implements Serializable {
 
+    protected static int ANIMATION_SPEED = 360;
+
     public enum Type {
         MOB,
         SAND,
@@ -20,20 +24,46 @@ public abstract class Mob extends Rectangle implements Serializable {
     }
 
     public enum Direction {
-        LEFT,
-        RIGHT
+
+        LEFT(0, -1),
+        RIGHT(1, 1);
+
+        private final int index;
+        private final int basis;
+
+        /**
+         * Index for this direction (left = 0, right = 1)
+         */
+        public final int getIndex() {
+            return index;
+        }
+
+        /**
+         * Basis for this direction (left = -1, right = 1)
+         */
+        public final int getBasis() {
+            return basis;
+        }
+
+        Direction(int index, int basis) {
+            this.index = index;
+            this.basis = basis;
+        }
     }
 
-    protected Vector2 mMove;
+    protected Vector2 mVelocity;
     protected Type mType;
-    protected int mAnimDelta = 6;
-    protected int mAnim;
+    protected int mAnimDelta = ANIMATION_SPEED;
+    protected float mAnim;
 
     private Direction mDirection;
-    private boolean mDead;
+    protected boolean mDead;
     private boolean mCanJump;
     private boolean mFlyMode;
 
+    private final int mMaxHealth;
+    private int mHealth;
+
     /**
      * @param x          in pixels
      * @param y          in pixels
@@ -41,19 +71,53 @@ public abstract class Mob extends Rectangle implements Serializable {
      * @param height     in pixels
      * @param mDirection Direction in which mob is looking
      */
-    protected Mob(float x, float y, float width, float height, Direction mDirection, Type type) {
+    protected Mob(float x, float y, float width, float height, Direction mDirection, Type type, int maxHealth) {
         super(x, y, width, height);
-        mMove = new Vector2(0, 0);
+        mVelocity = new Vector2(0, 0);
         mCanJump = false;
         mDead = false;
         this.mDirection = mDirection;
         this.mType = type;
+        this.mMaxHealth = maxHealth;
+        this.mHealth = mMaxHealth;
     }
 
     protected static Direction randomDir() {
         return MathUtils.randomBoolean(.5f) ? Direction.LEFT : Direction.RIGHT;
     }
 
+    private boolean isAnimationIncreasing() {
+        return mAnim > 0 && mAnimDelta > 0 || mAnim < 0 && mAnimDelta < 0;
+    }
+
+    private void checkHealth() {
+        mHealth = MathUtils.clamp(mHealth, 0, mMaxHealth);
+
+        if (mHealth <= 0) {
+            kill();
+        }
+    }
+
+    protected final void updateAnimation(float delta) {
+        if (mVelocity.x != 0f || Math.abs(mAnim) > mAnimDelta * delta) {
+            mAnim += mAnimDelta * delta;
+        } else {
+            mAnim = 0;
+        }
+
+        if (mAnim > 60f) {
+            mAnim = 60f;
+            mAnimDelta = -ANIMATION_SPEED;
+        } else if (mAnim < -60f) {
+            mAnim = -60f;
+            mAnimDelta = ANIMATION_SPEED;
+        }
+
+        if (mVelocity.x == 0f && isAnimationIncreasing()) {
+            mAnimDelta = -mAnimDelta;
+        }
+    }
+
     /**
      * @return The X coordinate of a mob in blocks
      */
@@ -112,15 +176,11 @@ public abstract class Mob extends Rectangle implements Serializable {
         mDirection = looksLeft() ? Direction.RIGHT : Direction.LEFT;
     }
 
-    protected final int dirMultiplier() {
-        return looksLeft() ? 0 : 1;
-    }
-
     public final boolean isDead() {
         return mDead;
     }
 
-    public final int getAnim() {
+    public final float getAnim() {
         return mAnim;
     }
 
@@ -131,13 +191,17 @@ public abstract class Mob extends Rectangle implements Serializable {
         mDead = true;
     }
 
-    public final void move() {
-        x += mMove.x;
-        y += mMove.y;
+    public final void move(float delta) {
+        x += mVelocity.x * delta;
+        y += mVelocity.y * delta;
+    }
+
+    public final Vector2 getVelocity() {
+        return mVelocity;
     }
 
-    public final Vector2 getMove() {
-        return mMove;
+    protected final void setVelocity(Vector2 velocity) {
+        mVelocity = velocity;
     }
 
     public final boolean canJump() {
@@ -160,7 +224,7 @@ public abstract class Mob extends Rectangle implements Serializable {
         return mType;
     }
 
-    public void checkWorldBounds(GameWorld gameWorld) {
+    public final void checkWorldBounds(GameWorld gameWorld) {
         if (x + width / 2 < 0) {
             x += gameWorld.getWidthPx();
         }
@@ -169,9 +233,53 @@ public abstract class Mob extends Rectangle implements Serializable {
         }
     }
 
-    public abstract void draw(SpriteBatch spriteBatch, float x, float y);
+    public final int getHealth() {
+        return mHealth;
+    }
+
+    public final void attachToController(MobsController controller) {
+        controller.addMob(this);
+    }
+
+    public void damage(int damage) {
+        if (damage == 0) {
+            return;
+        }
+
+        if (damage < 0) {
+            Gdx.app.error(this.getClass().getSimpleName(), "Damage cant be negative!");
+            return;
+        }
 
-    public abstract void ai(GameWorld gameWorld);
+        if (mHealth <= Integer.MIN_VALUE + damage) {
+            mHealth = Integer.MIN_VALUE + damage;
+        }
+
+        mHealth -= damage;
+        checkHealth();
+    }
+
+    public void heal(int heal) {
+        if (heal < 0) {
+            Gdx.app.error(this.getClass().getSimpleName(), "Heal cant be negative!");
+            return;
+        }
+
+        if (mHealth >= Integer.MAX_VALUE - heal) {
+            mHealth = Integer.MAX_VALUE - heal;
+        }
+
+        mHealth += heal;
+        checkHealth();
+    }
+
+    public abstract void draw(SpriteBatch spriteBatch, float x, float y, float delta);
+
+    public abstract void ai(GameWorld gameWorld, GameItemsHolder gameItemsHolder, float delta);
 
     public abstract void changeDir();
+
+    public abstract float getSpeed();
+
+    public abstract void jump();
 }
diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/MobsController.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/MobsController.java
deleted file mode 100644 (file)
index cda54ee..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-package ru.deadsoftware.cavedroid.game.mobs;
-
-import com.badlogic.gdx.Gdx;
-import ru.deadsoftware.cavedroid.game.GameScope;
-
-import javax.inject.Inject;
-import java.io.Serializable;
-import java.util.Iterator;
-import java.util.LinkedList;
-
-@GameScope
-public class MobsController implements Serializable {
-
-    public interface Callback {
-        void run(Mob mob);
-    }
-
-    private static final String TAG = "MobsController";
-
-    private final Player mPlayer;
-    private final LinkedList<Mob> mMobs = new LinkedList<>();
-
-    @Inject
-    public MobsController() {
-        mPlayer = new Player();
-    }
-
-    public Player getPlayer() {
-        return mPlayer;
-    }
-
-    public void addMob(Class<? extends Mob> mobClass, float x, float y) {
-        try {
-            mMobs.add(mobClass.getConstructor(float.class, float.class).newInstance(x, y));
-        } catch (Exception e) {
-            Gdx.app.error(TAG, e.getMessage());
-        }
-    }
-
-    public int getSize() {
-        return mMobs.size();
-    }
-
-    public void forEach(Callback callback) {
-        mMobs.forEach(callback::run);
-    }
-
-    public Iterator<Mob> getIterator() {
-        return mMobs.iterator();
-    }
-
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/MobsController.kt b/core/src/ru/deadsoftware/cavedroid/game/mobs/MobsController.kt
new file mode 100644 (file)
index 0000000..0ec111b
--- /dev/null
@@ -0,0 +1,28 @@
+package ru.deadsoftware.cavedroid.game.mobs
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import java.io.Serializable
+import java.util.*
+import javax.inject.Inject
+
+@GameScope
+class MobsController @Inject constructor(
+    gameItemsHolder: GameItemsHolder
+) : Serializable {
+
+    private val _mobs = LinkedList<Mob>()
+
+    val player: Player = Player(gameItemsHolder)
+
+    val mobs: List<Mob>
+        get() = _mobs
+
+    fun addMob(mob: Mob) {
+        _mobs.add(mob)
+    }
+
+    companion object {
+        private const val TAG = "MobsController"
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.java
deleted file mode 100644 (file)
index b362da7..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package ru.deadsoftware.cavedroid.game.mobs;
-
-import com.badlogic.gdx.graphics.g2d.SpriteBatch;
-import com.badlogic.gdx.math.MathUtils;
-import com.badlogic.gdx.math.Vector2;
-import ru.deadsoftware.cavedroid.game.GameWorld;
-import ru.deadsoftware.cavedroid.misc.Assets;
-
-import static ru.deadsoftware.cavedroid.misc.Assets.pigSprite;
-
-public class Pig extends Mob {
-
-    public Pig(float x, float y) {
-        super(x, y, 25, 18, randomDir(), Type.MOB);
-        mMove = new Vector2(looksLeft() ? -1 : 1, 0);
-    }
-
-    @Override
-    public void changeDir() {
-        switchDir();
-        mMove.x = -1 + 2 * dirMultiplier();
-    }
-
-    @Override
-    public void ai(GameWorld gameWorld) {
-        if (MathUtils.randomBoolean(.0025f)) {
-            if (mMove.x != 0f) {
-                mMove.x = 0;
-            } else {
-                changeDir();
-            }
-        }
-
-        if (mMove.x != 0f) {
-            mAnim += mAnimDelta;
-        } else {
-            mAnim = 0;
-        }
-
-        if (mAnim >= 60 || mAnim <= -60) {
-            mAnimDelta = -mAnimDelta;
-        }
-    }
-
-    @Override
-    public void draw(SpriteBatch spriteBatch, float x, float y) {
-        pigSprite[0][1].setRotation(getAnim());
-        pigSprite[1][1].setRotation(-getAnim());
-        //back legs
-        pigSprite[1][1].setPosition(x + (9 - dirMultiplier() * 9), y + 12);
-        pigSprite[1][1].draw(spriteBatch);
-        pigSprite[1][1].setPosition(x + 21 - (9 * dirMultiplier()), y + 12);
-        pigSprite[1][1].draw(spriteBatch);
-        //head & body
-        spriteBatch.draw(Assets.pigSprite[dirMultiplier()][0], x, y);
-        //front legs
-        pigSprite[0][1].setPosition(x + (9 - dirMultiplier() * 9), y + 12);
-        pigSprite[0][1].draw(spriteBatch);
-        pigSprite[0][1].setPosition(x + 21 - (9 * dirMultiplier()), y + 12);
-        pigSprite[0][1].draw(spriteBatch);
-    }
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.kt b/core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.kt
new file mode 100644 (file)
index 0000000..b55f429
--- /dev/null
@@ -0,0 +1,63 @@
+package ru.deadsoftware.cavedroid.game.mobs
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.math.MathUtils
+import com.badlogic.gdx.math.Vector2
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.utils.drawSprite
+import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getBackgroundLeg
+import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getBody
+import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getForegroundLeg
+import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getLeftLegRelativeX
+import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getLegsRelativeY
+import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getRightLegRelativeX
+
+class Pig(x: Float, y: Float) : Mob(x, y, WIDTH, HEIGHT, randomDir(), Type.MOB, MAX_HEALTH) {
+
+    override fun getSpeed(): Float {
+        return SPEED
+    }
+    
+    override fun changeDir() {
+        switchDir()
+        velocity = Vector2(direction.basis * speed, 0f)
+    }
+
+    override fun jump() {
+        velocity.y = JUMP_VELOCITY
+    }
+    
+    override fun ai(world: GameWorld, gameItemsHolder: GameItemsHolder, delta: Float) {
+        if (MathUtils.randomBoolean(delta)) {
+            if (velocity.x != 0f) {
+                velocity.x = 0f
+            } else {
+                changeDir()
+            }
+        }
+    }
+
+    override fun draw(spriteBatch: SpriteBatch, x: Float, y: Float, delta: Float) {
+        updateAnimation(delta)
+
+        val leftLegX = x + getLeftLegRelativeX(direction)
+        val rightLegX = x + getRightLegRelativeX(direction)
+        val legY = y + getLegsRelativeY()
+
+        spriteBatch.drawSprite(getBackgroundLeg(), leftLegX, legY, -anim)
+        spriteBatch.drawSprite(getBackgroundLeg(), rightLegX, legY, -anim)
+        spriteBatch.drawSprite(getBody(direction), x, y)
+        spriteBatch.drawSprite(getForegroundLeg(), leftLegX, legY, anim)
+        spriteBatch.drawSprite(getForegroundLeg(), rightLegX, legY, anim)
+    }
+    
+    
+    private companion object {
+        private const val WIDTH = 25f
+        private const val HEIGHT = 18f
+        private const val SPEED =  69.072f
+        private const val JUMP_VELOCITY = -133.332f
+        private const val MAX_HEALTH = 10;
+    }
+}
\ No newline at end of file
index 0b7ab8bc8148c48a24b02a3d7d266ec43e7cd3de..0e8af6e0eed0407b1cc3a4be057ae2dc344f5b87 100644 (file)
 package ru.deadsoftware.cavedroid.game.mobs;
 
+import com.badlogic.gdx.graphics.g2d.Sprite;
 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
+import com.badlogic.gdx.math.MathUtils;
 import com.badlogic.gdx.math.Vector2;
-import ru.deadsoftware.cavedroid.game.GameWorld;
+import ru.deadsoftware.cavedroid.game.GameItemsHolder;
+import ru.deadsoftware.cavedroid.game.model.block.Block;
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem;
+import ru.deadsoftware.cavedroid.game.model.item.Item;
+import ru.deadsoftware.cavedroid.game.objects.Drop;
+import ru.deadsoftware.cavedroid.game.world.GameWorld;
 import ru.deadsoftware.cavedroid.misc.Assets;
+import ru.deadsoftware.cavedroid.misc.utils.SpriteOrigin;
+import ru.deadsoftware.cavedroid.misc.utils.SpriteUtilsKt;
+
+import javax.annotation.CheckForNull;
+import java.util.ArrayList;
 
 public class Player extends Mob {
 
-    public final int[] inventory;
+    private static final float SPEED = 69.072f;
+    private static final float JUMP_VELOCITY = -133.332f;
+    private static final int MAX_HEALTH = 20;
+
+    private boolean hitting = false, hittingWithDamage = false;
+    private float hitAnim = 0f;
+    private float hitAnimDelta = ANIMATION_SPEED;
+
+    public final ArrayList<InventoryItem> inventory;
     public int slot;
-    public final int gameMode;
+    public int gameMode;
     public boolean swim;
+    public float headRotation = 0f;
+
+    public float blockDamage = 0f;
+    public int cursorX = 0;
+    public int cursorY = 0;
+
+    @CheckForNull
+    private Vector2 spawnPoint = null;
+
+    public ControlMode controlMode = ControlMode.WALK;
+
+    public enum ControlMode {
+        WALK,
+        CURSOR
+    }
 
-    public Player() {
-        super(0, 0, 4, 30, randomDir(), Type.MOB);
-        this.gameMode = 1;
-        inventory = new int[9];
+    public Player(GameItemsHolder gameItemsHolder) {
+        super(0, 0, 4, 30, randomDir(), Type.MOB, MAX_HEALTH);
+        inventory = new ArrayList<>(36);
+        for (int i = 0; i < 36; i++) {
+            inventory.add(gameItemsHolder.getFallbackItem().toInventoryItem());
+        }
         swim = false;
     }
 
-    public void respawn(GameWorld gameWorld) {
-        Vector2 pos = getSpawnPoint(gameWorld);
+    public void initInventory(GameItemsHolder gameItemsHolder) {
+        for (InventoryItem invItem : inventory) {
+            invItem.init(gameItemsHolder);
+        }
+    }
+
+    @CheckForNull
+    public Item inventory(int i) {
+        return inventory.get(i).getItem();
+    }
+    
+    public void respawn(GameWorld gameWorld, GameItemsHolder itemsHolder) {
+        Vector2 pos = getSpawnPoint(gameWorld, itemsHolder);
         this.x = pos.x;
         this.y = pos.y;
-        mMove.setZero();
+        mVelocity.setZero();
+        mDead = false;
+        heal(MAX_HEALTH);
+    }
+
+    public void decreaseCurrentItemCount(GameItemsHolder gameItemsHolder) {
+        if (gameMode == 1) {
+            return;
+        }
+        getCurrentItem().setAmount(getCurrentItem().getAmount() - 1);
+        if (getCurrentItem().getAmount() <= 0) {
+            setCurrentInventorySlotItem(gameItemsHolder.getFallbackItem());
+        }
+    }
+
+    public InventoryItem getCurrentItem() {
+        return inventory.get(slot);
     }
 
-    private Vector2 getSpawnPoint(GameWorld gameWorld) {
-        int y;
-        for (y = 0; y < gameWorld.getHeight(); y++) {
-            if (y == gameWorld.getHeight() - 1) {
-                y = 60;
-                gameWorld.setForeMap(0, y, 1);
+    public void pickUpDrop(Drop drop) {
+        for (InventoryItem invItem : inventory) {
+            if (!invItem.getItem().isTool()
+                    && invItem.getItem() == drop.getItem()
+                    && invItem.getAmount() < invItem.getItem().getParams().getMaxStack()) {
+                invItem.setAmount(invItem.getAmount() + 1);
+                drop.setPickedUp(true);
+                return;
+            }
+        }
+
+        for (int i = 0; i < inventory.size(); i++) {
+            if (inventory(i) == null || inventory(i).getParams().getKey().equals(GameItemsHolder.FALLBACK_ITEM_KEY)) {
+                inventory.set(i, drop.getItem().toInventoryItem());
+                drop.setPickedUp(true);
                 break;
             }
-            if (gameWorld.hasForeAt(0, y) && gameWorld.getForeMapBlock(0, y).hasCollision()) {
+        }
+    }
+
+    private Vector2 getSpawnPoint(GameWorld gameWorld, GameItemsHolder itemsHolder) {
+        if (spawnPoint != null) {
+            return spawnPoint;
+        }
+
+        int y, x = gameWorld.getWidth() / 2;
+        for (y = 0; y <= gameWorld.getWorldConfig().getSeaLevel(); y++) {
+            if (y == gameWorld.getWorldConfig().getSeaLevel()) {
+                for (x = 0; x < gameWorld.getWidth(); x++) {
+                    if (gameWorld.getForeMap(x, y).getParams().getHasCollision()) {
+                        break;
+                    }
+                    if (x == gameWorld.getWidth() - 1) {
+                        gameWorld.setForeMap(x, y, itemsHolder.getBlock("grass"));
+                        break;
+                    }
+                }
+                break;
+            }
+            if (gameWorld.hasForeAt(x, y) && gameWorld.getForeMap(x, y).hasCollision()) {
                 break;
             }
         }
-        return new Vector2(8 - getWidth() / 2, (float) y * 16 - getHeight());
+        spawnPoint = new Vector2(x * 16 + 8 - getWidth() / 2, (float) y * 16 - getHeight());
+        return spawnPoint;
     }
 
     public void setDir(Direction dir) {
@@ -47,8 +143,89 @@ public class Player extends Mob {
         }
     }
 
+    public void setCurrentInventorySlotItem(Item item) {
+        inventory.set(slot, item.toInventoryItem());
+    }
+
     @Override
-    public void ai(GameWorld gameWorld) {
+    public float getSpeed() {
+        return SPEED;
+    }
+
+    @Override
+    public void jump() {
+        mVelocity.y = JUMP_VELOCITY;
+    }
+
+    private void hitBlock(GameWorld gameWorld, GameItemsHolder gameItemsHolder) {
+        if (!hitting || !hittingWithDamage) {
+            return;
+        }
+
+        final Block foregroundBlock = gameWorld.getForeMap(cursorX, cursorY);
+        final Block backgroundBlock = gameWorld.getBackMap(cursorX, cursorY);
+
+        if ((!foregroundBlock.isNone() && foregroundBlock.getParams().getHitPoints() >= 0) ||
+                (foregroundBlock.isNone() && !backgroundBlock.isNone() && backgroundBlock.getParams().getHitPoints() >= 0)) {
+            if (gameMode == 0) {
+                if (!foregroundBlock.isNone() && blockDamage >= foregroundBlock.getParams().getHitPoints()) {
+                    gameWorld.destroyForeMap(cursorX, cursorY);
+                    blockDamage = 0;
+                } else if (!backgroundBlock.isNone() && blockDamage >= backgroundBlock.getParams().getHitPoints()) {
+                    gameWorld.destroyBackMap(cursorX, cursorY);
+                    blockDamage = 0;
+                }
+            } else {
+                if (!foregroundBlock.isNone()) {
+                    gameWorld.placeToForeground(cursorX, cursorY, gameItemsHolder.getFallbackBlock());
+                } else if (!backgroundBlock.isNone()) {
+                    gameWorld.placeToBackground(cursorX, cursorY, gameItemsHolder.getFallbackBlock());
+                }
+                stopHitting();
+            }
+        } else {
+            stopHitting();
+        }
+    }
+
+    @Override
+    public void ai(GameWorld gameWorld, GameItemsHolder gameItemsHolder, float delta) {
+        updateAnimation(delta);
+        hitBlock(gameWorld, gameItemsHolder);
+
+        if (gameMode == 1) {
+            return;
+        }
+
+        final Block foregroundBlock = gameWorld.getForeMap(cursorX, cursorY);
+        final Block backgroundBlock = gameWorld.getBackMap(cursorX, cursorY);
+        @CheckForNull final Block target;
+
+        if (!foregroundBlock.isNone() && foregroundBlock.getParams().getHitPoints() >= 0) {
+            target = foregroundBlock;
+        } else if (!backgroundBlock.isNone() && backgroundBlock.getParams().getHitPoints() >= 0) {
+            target = backgroundBlock;
+        } else {
+            target = null;
+        }
+
+        final boolean canHitBlock = target != null;
+
+        float multiplier = 1f;
+        final Item currentItem = inventory.get(slot).getItem();
+        if (currentItem instanceof Item.Tool && canHitBlock) {
+            if (target.getParams().getToolType() == currentItem.getClass()
+                    && ((Item.Tool)currentItem).getLevel() >= target.getParams().getToolLevel()) {
+                multiplier = 2f * ((Item.Tool)currentItem).getLevel();
+            }
+            multiplier *= ((Item.Tool)currentItem).getBlockDamageMultiplier();
+        }
+
+        if (hitting && hittingWithDamage && canHitBlock) {
+            blockDamage += 60f * delta * multiplier;
+        } else {
+            blockDamage = 0f;
+        }
     }
 
     @Override
@@ -56,38 +233,161 @@ public class Player extends Mob {
     }
 
     @Override
-    public void draw(SpriteBatch spriteBatch, float x, float y) {
-        if (mMove.x != 0 || Assets.playerSprite[0][2].getRotation() != 0) {
-            Assets.playerSprite[0][2].rotate(mAnimDelta);
-            Assets.playerSprite[1][2].rotate(-mAnimDelta);
-            Assets.playerSprite[0][3].rotate(-mAnimDelta);
-            Assets.playerSprite[1][3].rotate(mAnimDelta);
+    public void damage(int damage) {
+        if (gameMode == 1) {
+            return;
+        }
+
+        if (damage > 0) {
+            getVelocity().y += JUMP_VELOCITY / 3f;
+        }
+
+        super.damage(damage);
+    }
+
+    @Override
+    public void heal(int heal) {
+        if (gameMode == 1) {
+            return;
+        }
+        super.heal(heal);
+    }
+
+    private void drawItem(SpriteBatch spriteBatch, float x, float y, float anim) {
+        final Item item = inventory(slot);
+
+        if (item == null || item.isNone()) {
+            return;
+        }
+
+        final Sprite sprite = item.getSprite();
+        final boolean smallSprite = !item.isTool() || item.isShears();
+
+        final float originalWidth = sprite.getWidth();
+        final float originalHeight = sprite.getHeight();
+
+        if (smallSprite) {
+            sprite.setSize(Drop.DROP_SIZE, Drop.DROP_SIZE);
+        }
+
+        final float handLength = Assets.playerSprite[0][2].getHeight();
+
+        final SpriteOrigin spriteOrigin = item.getParams().getInHandSpriteOrigin();
+        final int handMultiplier = -getDirection().getBasis();
+        final float xOffset = (-1 + getDirection().getIndex()) * sprite.getWidth() + 4 + handMultiplier * (sprite.getWidth() * spriteOrigin.getX());
+        final float yOffset = !smallSprite ? -sprite.getHeight() / 2 : 0;
+
+        float rotate = anim + 30;
+
+        if (item.isTool()) {
+            sprite.rotate90(looksLeft());
+        }
+
+        final float itemX = x + handLength * MathUtils.sin(handMultiplier * anim * MathUtils.degRad) + xOffset;
+        final float itemY = y + handLength * MathUtils.cos(handMultiplier * anim * MathUtils.degRad) + yOffset;
+
+        if (looksLeft()) {
+            sprite.setFlip(!item.isTool(), sprite.isFlipY());
+            SpriteUtilsKt.applyOrigin(sprite, spriteOrigin.getFlipped(true, false));
+        } else {
+            sprite.setFlip(item.isTool(), sprite.isFlipY());
+            SpriteUtilsKt.applyOrigin(sprite, spriteOrigin);
+        }
+
+        sprite.setRotation(-handMultiplier * rotate);
+        sprite.setPosition(itemX, itemY);
+        sprite.draw(spriteBatch);
+
+        // dont forget to reset
+        sprite.setFlip(false, sprite.isFlipY());
+        sprite.setRotation(0);
+        sprite.setOriginCenter();
+        sprite.setSize(originalWidth, originalHeight);
+        if (item.isTool()) {
+            sprite.rotate90(looksRight());
+        }
+    }
+
+    public void startHitting(boolean withDamage) {
+        if (hitting) {
+            return;
+        }
+
+        hitting = true;
+        hittingWithDamage = withDamage;
+        hitAnim = 90f;
+        hitAnimDelta = ANIMATION_SPEED;
+    }
+
+    public void startHitting() {
+        startHitting(true);
+    }
+
+    public void stopHitting() {
+        blockDamage = 0f;
+        hitting = false;
+    }
+
+    private float getRightHandAnim(float delta) {
+        hitAnim -= hitAnimDelta * delta;
+
+        if (hitAnim < 30f || hitAnim > 90f) {
+            if (hitting) {
+                hitAnim = MathUtils.clamp(hitAnim, 30f, 90f);
+                hitAnimDelta = -hitAnimDelta;
+            } else  {
+                hitAnimDelta = ANIMATION_SPEED;
+            }
+        }
+
+        if (!hitting) {
+            if (hitAnim < hitAnimDelta * delta) {
+                hitAnim = 0;
+                hitAnimDelta = 0;
+                return -mAnim;
+            }
+        }
+
+        return hitAnim;
+    }
+
+    @Override
+    public void draw(SpriteBatch spriteBatch, float x, float y, float delta) {
+        final Sprite backHand = Assets.playerSprite[1][2];
+        final Sprite backLeg = Assets.playerSprite[1][3];
+        final Sprite frontLeg = Assets.playerSprite[0][3];
+        final Sprite head = Assets.playerSprite[getDirection().getIndex()][0];
+        final Sprite body = Assets.playerSprite[getDirection().getIndex()][1];
+        final Sprite frontHand = Assets.playerSprite[0][2];
+
+        float backHandAnim, frontHandAnim;
+
+        final float rightHandAnim = getRightHandAnim(delta);
+
+        if (looksLeft()) {
+            backHandAnim = rightHandAnim;
+            frontHandAnim = mAnim;
         } else {
-            Assets.playerSprite[0][2].setRotation(0);
-            Assets.playerSprite[1][2].setRotation(0);
-            Assets.playerSprite[0][3].setRotation(0);
-            Assets.playerSprite[1][3].setRotation(0);
-        }
-        if (Assets.playerSprite[0][2].getRotation() >= 60 || Assets.playerSprite[0][2].getRotation() <= -60) {
-            mAnimDelta = -mAnimDelta;
-        }
-
-        //back hand
-        Assets.playerSprite[1][2].setPosition(x + 2, y + 8);
-        Assets.playerSprite[1][2].draw(spriteBatch);
-        //back leg
-        Assets.playerSprite[1][3].setPosition(x + 2, y + 20);
-        Assets.playerSprite[1][3].draw(spriteBatch);
-        //front leg
-        Assets.playerSprite[0][3].setPosition(x + 2, y + 20);
-        Assets.playerSprite[0][3].draw(spriteBatch);
-        //head
-        spriteBatch.draw(Assets.playerSprite[dirMultiplier()][0], x, y);
-        //body
-        spriteBatch.draw(Assets.playerSprite[dirMultiplier()][1], x + 2, y + 8);
-        //front hand
-        Assets.playerSprite[0][2].setPosition(x + 2, y + 8);
-        Assets.playerSprite[0][2].draw(spriteBatch);
+            backHandAnim = -mAnim;
+            frontHandAnim = -rightHandAnim;
+        }
+
+        SpriteUtilsKt.drawSprite(spriteBatch, backHand, x + 2, y + 8, backHandAnim);
+
+        if (looksLeft()) {
+            drawItem(spriteBatch, x, y, -backHandAnim);
+        }
+
+        SpriteUtilsKt.drawSprite(spriteBatch, backLeg, x + 2, y + 20, mAnim);
+        SpriteUtilsKt.drawSprite(spriteBatch, frontLeg, x + 2, y + 20, -mAnim);
+        SpriteUtilsKt.drawSprite(spriteBatch, head, x, y, headRotation);
+        SpriteUtilsKt.drawSprite(spriteBatch, body, x + 2, y + 8);
+
+        if (looksRight()) {
+            drawItem(spriteBatch, x, y, frontHandAnim);
+        }
+
+        SpriteUtilsKt.drawSprite(spriteBatch, frontHand, x + 2, y + 8, frontHandAnim);
     }
 
 }
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt b/core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt
new file mode 100644 (file)
index 0000000..76c6ac5
--- /dev/null
@@ -0,0 +1,180 @@
+package ru.deadsoftware.cavedroid.game.model.block
+
+import com.badlogic.gdx.graphics.g2d.Sprite
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.math.Rectangle
+import com.badlogic.gdx.utils.TimeUtils
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.misc.utils.colorFromHexString
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+@OptIn(ExperimentalContracts::class)
+sealed class Block {
+
+    abstract val params: CommonBlockParams
+
+    val width: Float get() = 16f - params.collisionMargins.left - params.collisionMargins.right
+    val height: Float get() = 16f - params.collisionMargins.top - params.collisionMargins.bottom
+
+    val spriteWidth: Float get() = 16f - params.spriteMargins.left - params.spriteMargins.right
+    val spriteHeight: Float get() = 16f - params.spriteMargins.top - params.spriteMargins.bottom
+
+    private var animation: Array<Sprite>? = null
+
+    private var _sprite: Sprite? = null
+        get() {
+            return animation?.get(currentAnimationFrame) ?: field
+        }
+
+    val sprite: Sprite
+        get() = requireNotNull(_sprite) { "null sprite for block '${params.key}'" }
+
+    private val currentAnimationFrame: Int
+        get() {
+            return params.animationInfo?.let { animInfo ->
+                ((TimeUtils.millis() / ANIMATION_FRAME_DURATION_MS) % animInfo.framesCount).toInt()
+            } ?: 0
+        }
+
+    override fun hashCode(): Int {
+        return params.key.hashCode()
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return params.key == (other as Item).params.key
+    }
+
+    fun initialize() {
+        initAnimation()
+        initSprite()
+    }
+
+    private fun initAnimation() {
+        animation = params.animationInfo?.let { animInfo ->
+            requireNotNull(params.texture) { "Cannot derive animation frames from null sprite" }
+            Array(animInfo.framesCount) { y ->
+                val width = 16 - params.spriteMargins.left - params.spriteMargins.right
+                val height = 16 - params.spriteMargins.top - params.spriteMargins.bottom
+                Sprite(params.texture, params.spriteMargins.left, 16 * y + params.spriteMargins.top, width, height)
+                    .apply {
+                        flip(false, true)
+                        params.tint?.let { tint -> color = colorFromHexString(tint) }
+                    }
+            }
+        }
+    }
+
+    private fun initSprite() {
+        _sprite = animation?.get(0) ?: params.texture?.let { tex ->
+            val width = 16 - params.spriteMargins.left - params.spriteMargins.right
+            val height = 16 - params.spriteMargins.top - params.spriteMargins.bottom
+            Sprite(tex, params.spriteMargins.left, params.spriteMargins.top, width, height)
+                .apply {
+                    flip(false, true)
+                    params.tint?.let { tint -> color = colorFromHexString(tint) }
+                }
+        }
+    }
+
+    fun requireSprite() = requireNotNull(sprite)
+
+    fun draw(spriter: SpriteBatch, x: Float, y: Float) {
+        sprite.apply {
+            setBounds(
+                /* x = */ x + params.spriteMargins.left,
+                /* y = */ y + params.spriteMargins.top,
+                /* width = */ spriteWidth,
+                /* height = */ spriteHeight
+            )
+            draw(spriter)
+        }
+    }
+    
+    fun isFluid(): Boolean {
+        contract { returns(true) implies (this@Block is Fluid) }
+        return this is Fluid
+    }
+
+    fun isWater(): Boolean {
+        contract { returns(true) implies (this@Block is Water) }
+        return this is Water
+    }
+
+    fun isLava(): Boolean {
+        contract { returns(true) implies (this@Block is Lava) }
+        return this is Lava
+    }
+
+    fun isSlab(): Boolean {
+        contract { returns(true) implies (this@Block is Slab) }
+        return this is Slab
+    }
+
+    fun isNone(): Boolean {
+        contract { returns(true) implies (this@Block is None) }
+        return this is None
+    }
+
+    fun getRectangle(x: Int, y: Int): Rectangle {
+        return Rectangle(
+            /* x = */ x * 16f + params.collisionMargins.left,
+            /* y = */ y * 16f + params.collisionMargins.top,
+            /* width = */ width,
+            /* height = */ height
+        )
+    }
+
+    data class None(
+        override val params: CommonBlockParams
+    ) : Block()
+
+    data class Normal(
+        override val params: CommonBlockParams,
+    ) : Block()
+
+    data class Slab(
+        override val params: CommonBlockParams,
+        val fullBlockKey: String,
+        val otherPartBlockKey: String,
+    ): Block()
+
+    sealed class Fluid: Block() {
+        abstract val state: Int
+    }
+    
+    data class Water(
+        override val params: CommonBlockParams,
+        override val state: Int,
+    ) : Fluid()
+
+    data class Lava(
+        override val params: CommonBlockParams,
+        override val state: Int,
+    ) : Fluid()
+
+    /* Legacy accessors below */
+
+    // collision margins
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) val left: Int get() = params.collisionMargins.left
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) val right: Int get() = params.collisionMargins.left
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) val top: Int get() = params.collisionMargins.left
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) val bottom: Int get() = params.collisionMargins.left
+    
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) val hp: Int get() = params.hitPoints
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) val collision: Boolean get() = params.hasCollision
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) val animated: Boolean get() = params.animationInfo != null
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) val frames: Int get() = params.animationInfo?.framesCount ?: 0
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) val drop: String get() = params.dropInfo?.itemKey ?: "none"
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) fun hasDrop() = params.dropInfo != null
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) fun toJump() = params.hasCollision && params.collisionMargins.top < 8
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) fun hasCollision() = params.hasCollision
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) fun isBackground() = params.isBackground
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) fun isTransparent() = params.isTransparent
+    @Deprecated(LEGACY_ACCESSOR_DEPRECATION) fun getTexture() = sprite
+
+    companion object {
+        private const val LEGACY_ACCESSOR_DEPRECATION = "legacy accessors will be removed"
+        private const val ANIMATION_FRAME_DURATION_MS = 100L
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockAnimationInfo.kt b/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockAnimationInfo.kt
new file mode 100644 (file)
index 0000000..8d25189
--- /dev/null
@@ -0,0 +1,5 @@
+package ru.deadsoftware.cavedroid.game.model.block
+
+data class BlockAnimationInfo(
+    val framesCount: Int
+)
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockDropInfo.kt b/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockDropInfo.kt
new file mode 100644 (file)
index 0000000..62df2f3
--- /dev/null
@@ -0,0 +1,6 @@
+package ru.deadsoftware.cavedroid.game.model.block
+
+data class BlockDropInfo(
+    val itemKey: String,
+    val count: Int,
+)
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockMargins.kt b/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockMargins.kt
new file mode 100644 (file)
index 0000000..dbd2999
--- /dev/null
@@ -0,0 +1,15 @@
+package ru.deadsoftware.cavedroid.game.model.block
+
+data class BlockMargins(
+    val left: Int,
+    val top: Int,
+    val right: Int,
+    val bottom: Int
+) {
+
+    init {
+        assert(left + right < 16)
+        assert(top + bottom < 16)
+    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/block/CommonBlockParams.kt b/core/src/ru/deadsoftware/cavedroid/game/model/block/CommonBlockParams.kt
new file mode 100644 (file)
index 0000000..e98f1be
--- /dev/null
@@ -0,0 +1,23 @@
+package ru.deadsoftware.cavedroid.game.model.block
+
+import com.badlogic.gdx.graphics.Texture
+import ru.deadsoftware.cavedroid.game.model.item.Item
+
+data class CommonBlockParams(
+    @Deprecated("numeric id's will be removed") val id: Int?,
+    val key: String,
+    val collisionMargins: BlockMargins,
+    val hitPoints: Int,
+    val dropInfo: BlockDropInfo?,
+    val hasCollision: Boolean,
+    val isBackground: Boolean,
+    val isTransparent: Boolean,
+    val requiresBlock: Boolean,
+    val animationInfo: BlockAnimationInfo?,
+    val texture: Texture?,
+    val spriteMargins: BlockMargins,
+    val toolLevel: Int,
+    val toolType: Class<out Item.Tool>?,
+    val damage: Int,
+    val tint: String?,
+)
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/craft/CraftingRecipe.kt b/core/src/ru/deadsoftware/cavedroid/game/model/craft/CraftingRecipe.kt
new file mode 100644 (file)
index 0000000..eebe13f
--- /dev/null
@@ -0,0 +1,16 @@
+package ru.deadsoftware.cavedroid.game.model.craft
+
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.model.item.Item
+
+data class CraftingRecipe(
+    val input: List<Item>,
+    val output: CraftingResult
+)
+
+data class CraftingResult(
+    val item: Item,
+    val amount: Int,
+) {
+    fun toInventoryItem() = InventoryItem(item, amount)
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt b/core/src/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt
new file mode 100644 (file)
index 0000000..c6065b7
--- /dev/null
@@ -0,0 +1,35 @@
+package ru.deadsoftware.cavedroid.game.model.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class BlockDto(
+    @Deprecated("numeric ids will be removed") @SerialName("id") val id: Int? = null,
+    @SerialName("left") val left: Int = 0,
+    @SerialName("top") val top: Int = 0,
+    @SerialName("right") val right: Int = 0,
+    @SerialName("bottom") val bottom: Int = 0,
+    @SerialName("sprite_left") val spriteLeft: Int = 0,
+    @SerialName("sprite_top") val spriteTop: Int = 0,
+    @SerialName("sprite_right") val spriteRight: Int = 0,
+    @SerialName("sprite_bottom") val spriteBottom: Int = 0,
+    @SerialName("hp") val hp: Int = -1,
+    @SerialName("collision") val collision: Boolean = true,
+    @SerialName("background") val background: Boolean = false,
+    @SerialName("transparent") val transparent: Boolean = false,
+    @SerialName("block_required") val blockRequired: Boolean = false,
+    @SerialName("drop") val drop: String,
+    @SerialName("meta") val meta: String? = null,
+    @SerialName("texture") val texture: String,
+    @SerialName("animated") val animated: Boolean = false,
+    @SerialName("frames") val frames: Int = 0,
+    @SerialName("drop_count") val dropCount: Int = 1,
+    @SerialName("full_block") val fullBlock: String? = null,
+    @SerialName("state") val state: Int? = null,
+    @SerialName("other_part") val otherPart: String? = null,
+    @SerialName("tool_level") val toolLevel: Int = 0,
+    @SerialName("tool_type") val toolType: String? = null,
+    @SerialName("damage") val damage: Int = 0,
+    @SerialName("tint") val tint: String? = null,
+)
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/dto/CraftingDto.kt b/core/src/ru/deadsoftware/cavedroid/game/model/dto/CraftingDto.kt
new file mode 100644 (file)
index 0000000..469602a
--- /dev/null
@@ -0,0 +1,9 @@
+package ru.deadsoftware.cavedroid.game.model.dto
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class CraftingDto(
+    val input: List<String>,
+    val count: Int = 1,
+)
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt b/core/src/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt
new file mode 100644 (file)
index 0000000..aef3a13
--- /dev/null
@@ -0,0 +1,11 @@
+package ru.deadsoftware.cavedroid.game.model.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.*
+
+@Serializable
+data class GameItemsDto(
+    @SerialName("blocks") val blocks: Map<String, BlockDto>,
+    @SerialName("items") val items: Map<String, ItemDto>,
+)
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt b/core/src/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt
new file mode 100644 (file)
index 0000000..fe28e64
--- /dev/null
@@ -0,0 +1,21 @@
+package ru.deadsoftware.cavedroid.game.model.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ItemDto(
+    @Deprecated("numeric ids will be removed") @SerialName("id") val id: Int? = null,
+    @SerialName("name") val name: String,
+    @SerialName("type") val type: String = "normal",
+    @SerialName("texture") val texture: String,
+    @SerialName("origin_x") val originX: Float = 0f,
+    @SerialName("origin_y") val origin_y: Float = 1f,
+    @SerialName("action_key") val actionKey: String? = null,
+    @SerialName("mob_damage_multiplier") val mobDamageMultiplier: Float = 1f,
+    @SerialName("block_damage_multiplier") val blockDamageMultiplier: Float = 1f,
+    @SerialName("top_slab_block") val topSlabBlock: String? = null,
+    @SerialName("bottom_slab_block") val bottomSlabBlock: String? = null,
+    @SerialName("tool_level") val toolLevel: Int? = null,
+    @SerialName("max_stack") val maxStack: Int = 64,
+)
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/item/CommonItemParams.kt b/core/src/ru/deadsoftware/cavedroid/game/model/item/CommonItemParams.kt
new file mode 100644 (file)
index 0000000..291cfb8
--- /dev/null
@@ -0,0 +1,11 @@
+package ru.deadsoftware.cavedroid.game.model.item
+
+import ru.deadsoftware.cavedroid.misc.utils.SpriteOrigin
+
+data class CommonItemParams(
+    @Deprecated("numeric id's will be removed") val id: Int?,
+    val key: String,
+    val name: String,
+    val inHandSpriteOrigin: SpriteOrigin,
+    val maxStack: Int,
+)
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/item/InventoryItem.kt b/core/src/ru/deadsoftware/cavedroid/game/model/item/InventoryItem.kt
new file mode 100644 (file)
index 0000000..6c15983
--- /dev/null
@@ -0,0 +1,90 @@
+package ru.deadsoftware.cavedroid.game.model.item
+
+import com.badlogic.gdx.graphics.Color
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.utils.drawSprite
+import ru.deadsoftware.cavedroid.misc.utils.drawString
+import ru.deadsoftware.cavedroid.misc.utils.px
+import java.io.Serializable
+
+class InventoryItem @JvmOverloads constructor(
+    val itemKey: String,
+    var amount: Int = 1,
+) : Serializable {
+
+    @Transient
+    lateinit var item: Item
+        private set
+
+    @JvmOverloads
+    constructor(_item: Item, amount: Int = 1) : this(_item.params.key, amount) {
+        item = _item
+    }
+
+    fun init(gameItemsHolder: GameItemsHolder) {
+        if (this::item.isInitialized) {
+            return
+        }
+        item = gameItemsHolder.getItem(itemKey)
+    }
+
+    private fun drawAmountText(spriteBatch: SpriteBatch, text: String,  x: Float, y: Float) {
+        spriteBatch.drawString(text, x + 1, y + 1, Color.BLACK)
+        spriteBatch.drawString(text, x, y, Color.WHITE)
+    }
+
+    fun drawSelected(spriteBatch: SpriteBatch, x: Float, y: Float) {
+        if (item.isNone()) {
+            return
+        }
+
+        val sprite = item.sprite
+        val amountString = amount.toString()
+        spriteBatch.drawSprite(sprite, x - 10f, y - 10f, rotation = 0f, width = 20f, height = 20f)
+        drawAmountText(
+            spriteBatch = spriteBatch,
+            text = amountString,
+            x = x + 10f - Assets.getStringWidth(amountString) + 1f,
+            y = y + 10f - Assets.getStringHeight(amountString) + 1f
+        )
+    }
+
+    fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, x: Float, y: Float) {
+        if (item.isNone()) {
+            return
+        }
+
+        val sprite = item.sprite
+        spriteBatch.drawSprite(sprite, x, y)
+
+        if (amount < 2) {
+            return
+        }
+
+        if (item.isTool()) {
+            spriteBatch.end()
+            shapeRenderer.begin(ShapeRenderer.ShapeType.Filled)
+            shapeRenderer.color = Color.GREEN
+            shapeRenderer.rect(
+                /* x = */ x,
+                /* y = */ y + 1.px - 2,
+                /* width = */ 1.px * (amount.toFloat() / item.params.maxStack.toFloat()),
+                /* height = */ 2f
+            )
+            shapeRenderer.end()
+            spriteBatch.begin()
+        } else {
+            val amountString = amount.toString()
+            drawAmountText(
+                spriteBatch = spriteBatch,
+                text = amountString,
+                x = x + 1.px - Assets.getStringWidth(amountString),
+                y = y + 1.px - Assets.getStringHeight(amountString)
+            )
+        }
+    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/item/Item.kt b/core/src/ru/deadsoftware/cavedroid/game/model/item/Item.kt
new file mode 100644 (file)
index 0000000..1e6755a
--- /dev/null
@@ -0,0 +1,144 @@
+package ru.deadsoftware.cavedroid.game.model.item
+
+import com.badlogic.gdx.graphics.g2d.Sprite
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.block.Block as BlockModel
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+@OptIn(ExperimentalContracts::class)
+sealed class Item {
+
+    abstract val params: CommonItemParams
+    abstract val sprite: Sprite
+
+    override fun hashCode(): Int {
+        return params.key.hashCode()
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return params.key == (other as Item).params.key
+    }
+
+    fun isNone(): Boolean {
+        contract { returns(true) implies (this@Item is None) }
+        return this is None
+    }
+    
+    fun isPlaceable(): Boolean {
+        contract { returns(true) implies (this@Item is Placeable) }
+        return this is Placeable
+    }
+
+    fun isSlab(): Boolean {
+        contract { returns(true) implies (this@Item is Slab) }
+        return this is Slab
+    }
+
+    fun isTool(): Boolean {
+        contract { returns(true) implies (this@Item is Tool) }
+        return this is Tool
+    }
+
+    fun isShears(): Boolean {
+        contract { returns(true) implies (this@Item is Shears) }
+        return this is Shears
+    }
+
+    fun isUsable(): Boolean {
+        contract { returns(true) implies (this@Item is Placeable) }
+        return this is Placeable
+    }
+
+    @JvmOverloads
+    fun toInventoryItem(amount: Int = 1): InventoryItem {
+        return InventoryItem(this, amount)
+    }
+
+    data class Normal(
+        override val params: CommonItemParams,
+        override val sprite: Sprite
+    ) : Item()
+
+    sealed class Tool : Item() {
+        abstract val mobDamageMultiplier: Float
+        abstract val blockDamageMultiplier: Float
+        abstract val level: Int
+    }
+
+    sealed class Usable : Item() {
+        abstract val useActionKey: String
+    }
+
+    sealed class Placeable : Item() {
+        abstract val block: BlockModel
+        override val sprite: Sprite get() = block.sprite
+    }
+
+    data class None(
+        override val params: CommonItemParams,
+    ): Item() {
+        override val sprite: Sprite
+            get() = throw IllegalAccessException("Trying to get sprite of None")
+    }
+
+    data class Block(
+        override val params: CommonItemParams,
+        override val block: BlockModel
+    ) : Placeable()
+
+    data class Slab(
+        override val params: CommonItemParams,
+        val topPartBlock: BlockModel.Slab,
+        val bottomPartBlock: BlockModel.Slab
+    ) : Placeable() {
+        override val block get() = bottomPartBlock
+    }
+    
+    data class Sword(
+        override val params: CommonItemParams,
+        override val sprite: Sprite,
+        override val mobDamageMultiplier: Float,
+        override val blockDamageMultiplier: Float,
+        override val level: Int,
+    ) : Tool()
+
+    data class Shovel(
+        override val params: CommonItemParams,
+        override val sprite: Sprite,
+        override val mobDamageMultiplier: Float,
+        override val blockDamageMultiplier: Float,
+        override val level: Int,
+    ) : Tool()
+
+    data class Axe(
+        override val params: CommonItemParams,
+        override val sprite: Sprite,
+        override val mobDamageMultiplier: Float,
+        override val blockDamageMultiplier: Float,
+        override val level: Int,
+    ) : Tool()
+
+    data class Pickaxe(
+        override val params: CommonItemParams,
+        override val sprite: Sprite,
+        override val mobDamageMultiplier: Float,
+        override val blockDamageMultiplier: Float,
+        override val level: Int,
+    ) : Tool()
+
+    data class Shears(
+        override val params: CommonItemParams,
+        override val sprite: Sprite,
+        override val mobDamageMultiplier: Float,
+        override val blockDamageMultiplier: Float,
+        override val level: Int,
+    ) : Tool()
+    
+    data class Bucket(
+        override val params: CommonItemParams,
+        override val sprite: Sprite,
+        override val useActionKey: String
+    ) : Usable()
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt b/core/src/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt
new file mode 100644 (file)
index 0000000..85d8cb7
--- /dev/null
@@ -0,0 +1,106 @@
+package ru.deadsoftware.cavedroid.game.model.mapper
+
+import com.badlogic.gdx.graphics.Texture
+import dagger.Reusable
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.block.*
+import ru.deadsoftware.cavedroid.game.model.block.Block.*
+import ru.deadsoftware.cavedroid.game.model.dto.BlockDto
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader
+import javax.inject.Inject
+
+@Reusable
+class BlockMapper @Inject constructor(
+    private val assetLoader: AssetLoader,
+) {
+
+    fun map(key: String, dto: BlockDto): Block {
+        val commonBlockParams = mapCommonParams(key, dto)
+
+        return when (dto.meta) {
+            "water" -> Water(commonBlockParams, requireNotNull(dto.state))
+            "lava" -> Lava(commonBlockParams, requireNotNull(dto.state))
+            "slab" -> Slab(commonBlockParams, requireNotNull(dto.fullBlock), requireNotNull(dto.otherPart))
+            "none" -> None(commonBlockParams)
+            else -> Normal(commonBlockParams)
+        }
+    }
+
+    private fun mapCommonParams(key: String, dto: BlockDto): CommonBlockParams {
+        return CommonBlockParams(
+            id = dto.id,
+            key = key,
+            collisionMargins = BlockMargins(
+                left = dto.left,
+                top = dto.top,
+                right = dto.right,
+                bottom = dto.bottom
+            ),
+            hitPoints = dto.hp,
+            dropInfo = mapBlockDropInfo(dto),
+            hasCollision = dto.collision,
+            isBackground = dto.background,
+            isTransparent = dto.transparent,
+            requiresBlock = dto.blockRequired,
+            animationInfo = mapBlockAnimationInfo(dto),
+            texture = getTexture(dto.texture),
+            spriteMargins = BlockMargins(
+                left = dto.spriteLeft,
+                top = dto.spriteTop,
+                right = dto.spriteRight,
+                bottom = dto.spriteBottom,
+            ),
+            toolLevel = dto.toolLevel,
+            toolType = mapToolType(dto),
+            damage = dto.damage,
+            tint = dto.tint,
+        )
+    }
+
+    private fun mapToolType(dto: BlockDto): Class<out Item.Tool>? {
+        return when(dto.toolType) {
+            "shovel" -> Item.Shovel::class.java
+            "sword" -> Item.Sword::class.java
+            "pickaxe" -> Item.Pickaxe::class.java
+            "axe" -> Item.Axe::class.java
+            "shears" -> Item.Shears::class.java
+
+            else -> null
+        }
+    }
+
+    private fun mapBlockDropInfo(dto: BlockDto): BlockDropInfo? {
+        val drop = dto.drop
+        val dropCount = dto.dropCount
+
+        if (drop == GameItemsHolder.FALLBACK_ITEM_KEY || dropCount == 0) {
+            return null
+        }
+
+        return BlockDropInfo(
+            itemKey = drop,
+            count = dropCount,
+        )
+    }
+
+    private fun mapBlockAnimationInfo(dto: BlockDto): BlockAnimationInfo? {
+        if (!dto.animated) {
+            return null
+        }
+
+        return BlockAnimationInfo(
+            framesCount = dto.frames,
+        )
+    }
+
+    private fun getTexture(textureName: String): Texture? {
+        if (textureName == GameItemsHolder.FALLBACK_BLOCK_KEY) {
+            return null
+        }
+
+        return Assets.resolveBlockTexture(assetLoader, textureName)
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt b/core/src/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt
new file mode 100644 (file)
index 0000000..b951f26
--- /dev/null
@@ -0,0 +1,62 @@
+package ru.deadsoftware.cavedroid.game.model.mapper
+
+import com.badlogic.gdx.graphics.g2d.Sprite
+import dagger.Reusable
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.dto.ItemDto
+import ru.deadsoftware.cavedroid.game.model.item.CommonItemParams
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.model.item.Item.*
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader
+import ru.deadsoftware.cavedroid.misc.utils.SpriteOrigin
+import javax.inject.Inject
+
+@Reusable
+class ItemMapper @Inject constructor(
+    private val assetLoader: AssetLoader,
+) {
+
+    fun map(key: String, dto: ItemDto, block: Block?, slabTopBlock: Block.Slab?, slabBottomBlock: Block.Slab?): Item {
+        val params = mapCommonParams(key, dto)
+
+        return when (dto.type) {
+            "normal" -> Normal(params, requireNotNull(loadSprite(dto)))
+            "bucket" -> Bucket(params, requireNotNull(loadSprite(dto)), requireNotNull(dto.actionKey))
+            "shovel" -> Shovel(params, requireNotNull(loadSprite(dto)), dto.mobDamageMultiplier, dto.blockDamageMultiplier, requireNotNull(dto.toolLevel))
+            "sword" -> Sword(params, requireNotNull(loadSprite(dto)), dto.mobDamageMultiplier, dto.blockDamageMultiplier, requireNotNull(dto.toolLevel))
+            "pickaxe" -> Pickaxe(params, requireNotNull(loadSprite(dto)), dto.mobDamageMultiplier, dto.blockDamageMultiplier, requireNotNull(dto.toolLevel))
+            "axe" -> Axe(params, requireNotNull(loadSprite(dto)), dto.mobDamageMultiplier, dto.blockDamageMultiplier, requireNotNull(dto.toolLevel))
+            "shears" -> Shears(params, requireNotNull(loadSprite(dto)), dto.mobDamageMultiplier, dto.blockDamageMultiplier, requireNotNull(dto.toolLevel))
+            "block" -> Block(params, requireNotNull(block))
+            "slab" -> Slab(params, requireNotNull(slabTopBlock), requireNotNull(slabBottomBlock))
+            "none" -> None(params)
+            else -> throw IllegalArgumentException("Unknown item type ${dto.type}")
+        }
+    }
+
+    private fun mapCommonParams(key: String, dto: ItemDto): CommonItemParams {
+        return CommonItemParams(
+            id = dto.id,
+            key = key,
+            name = dto.name,
+            inHandSpriteOrigin = SpriteOrigin(
+                x = dto.originX,
+                y = dto.origin_y,
+            ),
+            maxStack = dto.maxStack,
+        )
+    }
+
+    private fun loadSprite(dto: ItemDto): Sprite? {
+        if (dto.type == "none" || dto.type == "block" || dto.texture == GameItemsHolder.FALLBACK_ITEM_KEY) {
+            return null
+        }
+
+        val texture = Assets.resolveItemTexture(assetLoader, dto.texture)
+        return Sprite(texture)
+            .apply { flip(false, true) }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/world/Biome.kt b/core/src/ru/deadsoftware/cavedroid/game/model/world/Biome.kt
new file mode 100644 (file)
index 0000000..ee214bd
--- /dev/null
@@ -0,0 +1,7 @@
+package ru.deadsoftware.cavedroid.game.model.world
+
+enum class Biome {
+    PLAINS,
+    DESERT,
+    WINTER
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/world/generator/WorldGeneratorConfig.kt b/core/src/ru/deadsoftware/cavedroid/game/model/world/generator/WorldGeneratorConfig.kt
new file mode 100644 (file)
index 0000000..64da3fe
--- /dev/null
@@ -0,0 +1,34 @@
+package ru.deadsoftware.cavedroid.game.model.world.generator
+
+import com.badlogic.gdx.utils.TimeUtils
+import ru.deadsoftware.cavedroid.game.model.world.Biome
+
+data class WorldGeneratorConfig(
+    val width: Int,
+    val height: Int,
+    val seed: Long,
+    val minSurfaceHeight: Int,
+    val maxSurfaceHeight: Int,
+    val biomes: List<Biome>,
+    val minBiomeSize: Int,
+    val seaLevel: Int,
+) {
+
+    companion object {
+
+        fun getDefault(): WorldGeneratorConfig {
+            return WorldGeneratorConfig(
+                width = 1024,
+                height = 256,
+                seed = TimeUtils.millis(),
+                maxSurfaceHeight = 208,
+                minSurfaceHeight = 128,
+                biomes = Biome.entries.toList(),
+                minBiomeSize = 64,
+                seaLevel = 192,
+            )
+        }
+
+    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/objects/Block.kt b/core/src/ru/deadsoftware/cavedroid/game/objects/Block.kt
deleted file mode 100644 (file)
index d44f017..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-@file:Suppress("DeprecatedCallableAddReplaceWith")
-
-package ru.deadsoftware.cavedroid.game.objects
-
-import com.badlogic.gdx.graphics.Texture
-import com.badlogic.gdx.graphics.g2d.Sprite
-import com.badlogic.gdx.graphics.g2d.SpriteBatch
-import com.badlogic.gdx.math.Rectangle
-
-private const val ANIMATION_FRAME_DURATION = 100L
-private const val DEPRECATION_MESSAGE =
-        "Deprecated since moved to Kotlin. Use generated getter or kotlin property access."
-
-/**
- * @param left          margin from left edge
- * @param top           margin from top edge
- * @param right         margin from right edge
- * @param bottom        margin from bottom edge
- * @param hp            hit points
- * @param drop          id of an item the block will drop when destroyed
- * @param collision     true if block has collision
- * @param background    true if block should be drawn behind player
- * @param transparent   true if block is transparent and renderer should draw a block behind it
- * @param requiresBlock true if block should break when there is no block with collision under it
- * @param fluid         true if fluid
- * @param meta          extra info for storing
- * @param texture       block's texture
- * @param animated      indicates if block has animation
- * @param frames        number of animation frames. ignored if animated is false
- * @param spriteLeft    block's sprite x on texture
- * @param spriteTop     block's sprite y on texture
- * @param spriteRight   block's sprite right edge on texture
- * @param spriteBottom  block's sprite bottom on texture
- */
-data class Block(
-        val left: Int,
-        val top: Int,
-        val right: Int,
-        val bottom: Int,
-        val hp: Int,
-        val drop: String,
-        val collision: Boolean,
-        val background: Boolean,
-        val transparent: Boolean,
-        val requiresBlock: Boolean,
-        val fluid: Boolean,
-        val meta: String,
-        private val texture: Texture?,
-        val animated: Boolean,
-        val frames: Int,
-        private val spriteLeft: Int,
-        private val spriteTop: Int,
-        private val spriteRight: Int,
-        private val spriteBottom: Int
-) {
-
-    val width = 16 - right - left
-    val height = 16 - top - bottom
-
-    private val spriteWidth = 16 - spriteLeft - spriteRight
-    private val spriteHeight = 16 - spriteTop - spriteBottom
-
-    private val sprite: Sprite?
-        get() {
-            return if (animated) {
-                animation[currentFrame()]
-            } else {
-                field
-            }
-        }
-
-
-    private val animation: Array<Sprite>
-
-    init {
-        if (frames !in 0..Int.MAX_VALUE) {
-            throw IllegalArgumentException("Animation frames must be in range [0, ${Int.MAX_VALUE}]")
-        }
-
-        animation = if (animated) {
-            if (texture == null) {
-                throw IllegalArgumentException("Cannot derive animation frames from null sprite")
-            }
-            Array(frames) { y ->
-                Sprite(texture, spriteLeft, 16 * y + spriteTop, spriteWidth, spriteHeight).apply {
-                    flip(false, true)
-                }
-            }
-        } else {
-            emptyArray()
-        }
-
-        sprite = if (animated) { animation[0] } else {
-            if (texture != null) {
-                Sprite(texture, spriteLeft, spriteTop, spriteWidth, spriteHeight).apply {
-                    flip(false, true)
-                }
-            } else {
-                null
-            }
-        }
-    }
-
-    private fun currentFrame() = if (animated) {
-        ((System.currentTimeMillis() / ANIMATION_FRAME_DURATION) % frames).toInt()
-    } else {
-        0
-    }
-
-    fun requireSprite() = sprite ?: throw IllegalStateException("Sprite is null")
-
-    fun draw(spriter: SpriteBatch, x: Float, y: Float) {
-        requireSprite().apply {
-            setBounds(x + spriteLeft, y + spriteTop, spriteWidth.toFloat(), spriteHeight.toFloat())
-            draw(spriter)
-        }
-    }
-
-    fun getRectangle(x: Int, y: Int) =
-            Rectangle(x * 16f + left, y * 16f + this.top, width.toFloat(), height.toFloat())
-
-    fun hasDrop() = drop != "none"
-
-    fun toJump() = top < 8 && collision
-
-    @Deprecated(DEPRECATION_MESSAGE)
-    fun hasCollision() = collision
-
-    @Deprecated(DEPRECATION_MESSAGE)
-    fun isBackground() = background
-
-    @Deprecated(DEPRECATION_MESSAGE)
-    fun isTransparent() = transparent
-
-    @Deprecated(DEPRECATION_MESSAGE)
-    fun isFluid() = fluid
-
-    @Deprecated(DEPRECATION_MESSAGE)
-    fun requiresBlock() = requiresBlock
-
-    @Deprecated("Was renamed to Sprite to comply with variable type.", ReplaceWith("requireSprite()"))
-    fun getTexture() = sprite
-
-}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/objects/Drop.java b/core/src/ru/deadsoftware/cavedroid/game/objects/Drop.java
deleted file mode 100644 (file)
index 3b5c760..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-package ru.deadsoftware.cavedroid.game.objects;
-
-import com.badlogic.gdx.math.Intersector;
-import com.badlogic.gdx.math.MathUtils;
-import com.badlogic.gdx.math.Rectangle;
-import com.badlogic.gdx.math.Vector2;
-import ru.deadsoftware.cavedroid.game.GameWorld;
-import ru.deadsoftware.cavedroid.game.mobs.Player;
-
-import java.io.Serializable;
-
-public class Drop extends Rectangle implements Serializable {
-
-    private final int id;
-    private final Vector2 move;
-    private boolean pickedUp = false;
-
-    Drop(float x, float y, int id) {
-        super(x, y, 8, 8);
-        this.id = id;
-        this.move = new Vector2(0, -1);
-    }
-
-    public Vector2 getMove() {
-        return move;
-    }
-
-    public int closeToPlayer(GameWorld gameWorld, Player player) {
-        boolean[] c = new boolean[3];
-
-        c[0] = Intersector.overlaps(new Rectangle(player.getX() - 16,
-                player.getY() - 16, player.getWidth() + 32, player.getHeight() + 32), this);
-        c[1] = Intersector.overlaps(new Rectangle((player.getX() + gameWorld.getWidthPx()) - 16,
-                player.getY() - 16, player.getWidth() + 32, player.getHeight() + 32), this);
-        c[2] = Intersector.overlaps(new Rectangle((player.getX() - gameWorld.getWidthPx()) - 16,
-                player.getY() - 16, player.getWidth() + 32, player.getHeight() + 32), this);
-
-        for (int i = 0; i < 3; i++) {
-            if (c[i]) {
-                return i + 1;
-            }
-        }
-
-        return 0;
-    }
-
-    public void moveToPlayer(GameWorld gameWorld, Player player, int ctp) {
-        if (ctp > 0) {
-            float px = player.getX();
-            float py = player.getY();
-
-            switch (ctp) {
-                case 2:
-                    px += gameWorld.getWidthPx();
-                    break;
-                case 3:
-                    px -= gameWorld.getWidthPx();
-                    break;
-            }
-
-            float dx = 0, dy = 0;
-
-            if (px + player.getWidth() < x + 4) {
-                dx = -.5f;
-            } else if (px > x + 4) {
-                dx = .5f;
-            }
-
-            if (py + player.getHeight() < y + 4) {
-                dy = -.5f;
-            } else if (py > y + 4) {
-                dy = .5f;
-            }
-
-            move.add(dx, dy);
-
-            if (move.x > 2) {
-                move.x = 1;
-            } else if (move.x < -2) {
-                move.x = -1;
-            }
-
-            if (move.y > 2) {
-                move.y = 1;
-            } else if (move.y < -2) {
-                move.y = -1;
-            }
-        }
-    }
-
-    public void pickUpDrop(Player pl) {
-        for (int i = 0; i < pl.inventory.length; i++) {
-            if (pl.inventory[i] == 0 || pl.inventory[i] == id) {
-                pl.inventory[i] = id;
-                pickedUp = true;
-                break;
-            }
-        }
-    }
-
-    private void checkWorldBounds() {
-//        if (x + 8 > world.getWidthPx()) {
-//            x -= world.getWidthPx();
-//        } else if (x < 0) {
-//            x += world.getWidthPx();
-//        }
-    }
-
-    public void move() {
-        x += move.x;
-        y += move.y;
-        checkWorldBounds();
-        y = MathUtils.round(y);
-    }
-
-    public int getId() {
-        return id;
-    }
-
-    public boolean isPickedUp() {
-        return pickedUp;
-    }
-
-    public void setPickedUp(boolean pickedUp) {
-        this.pickedUp = pickedUp;
-    }
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/objects/Drop.kt b/core/src/ru/deadsoftware/cavedroid/game/objects/Drop.kt
new file mode 100644 (file)
index 0000000..151e486
--- /dev/null
@@ -0,0 +1,57 @@
+package ru.deadsoftware.cavedroid.game.objects
+
+import com.badlogic.gdx.math.Intersector
+import com.badlogic.gdx.math.Rectangle
+import com.badlogic.gdx.math.Vector2
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.item.Item
+
+class Drop(
+    x: Float,
+    y: Float,
+    _item: Item,
+) : Rectangle(x, y, DROP_SIZE, DROP_SIZE) {
+
+    val itemKey = _item.params.key
+    val velocity = getInitialVelocity()
+    var pickedUp = false
+
+    @Transient
+    lateinit var item: Item
+        private set
+
+    init {
+        item = _item
+    }
+
+    fun initItem(gameItemsHolder: GameItemsHolder) {
+        if (this::item.isInitialized) {
+            return
+        }
+
+        item = gameItemsHolder.getItem(itemKey)
+    }
+
+    fun canMagnetTo(rectangle: Rectangle): Boolean {
+        val magnetArea = getMagnetArea()
+        return Intersector.overlaps(magnetArea, rectangle)
+    }
+
+    private fun getMagnetArea(): Rectangle {
+        return Rectangle(
+            /* x = */ x - MAGNET_DISTANCE,
+            /* y = */ y - MAGNET_DISTANCE,
+            /* width = */ width + MAGNET_DISTANCE * 2,
+            /* height = */ height + MAGNET_DISTANCE * 2,
+        )
+    }
+
+    companion object {
+        private const val MAGNET_DISTANCE = 16f
+
+        const val MAGNET_VELOCITY = 128f
+        const val DROP_SIZE = 8f
+
+        private fun getInitialVelocity(): Vector2 = Vector2(0f, -1f)
+    }
+}
index 0e4c6592abe3badac802872be953ab84c68b2324..798a84ce5b77415bbf88f87187c39d51e636ff04 100644 (file)
@@ -1,6 +1,8 @@
 package ru.deadsoftware.cavedroid.game.objects;
 
+import ru.deadsoftware.cavedroid.game.GameItemsHolder;
 import ru.deadsoftware.cavedroid.game.GameScope;
+import ru.deadsoftware.cavedroid.game.model.item.Item;
 
 import javax.inject.Inject;
 import java.io.Serializable;
@@ -20,8 +22,15 @@ public class DropController implements Serializable {
     public DropController() {
     }
 
-    public void addDrop(float x, float y, int id) {
-        mDrops.add(new Drop(x, y, id));
+    public void initDrops(GameItemsHolder gameItemsHolder) {
+        mDrops.forEach((drop) -> drop.initItem(gameItemsHolder));
+    }
+
+    public void addDrop(float x, float y, Item item) {
+        if (item.isNone()) {
+            return;
+        }
+        mDrops.add(new Drop(x, y, item));
     }
 
     public int getSize() {
diff --git a/core/src/ru/deadsoftware/cavedroid/game/objects/Item.kt b/core/src/ru/deadsoftware/cavedroid/game/objects/Item.kt
deleted file mode 100644 (file)
index 59d231d..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package ru.deadsoftware.cavedroid.game.objects
-
-import com.badlogic.gdx.graphics.g2d.Sprite
-
-data class Item(
-        val name: String,
-        val type: String,
-        val sprite: Sprite?
-) {
-
-    init {
-        sprite?.flip(false, true)
-    }
-
-    fun requireSprite() = sprite ?: throw IllegalStateException("Sprite is null")
-
-    fun isBlock() = type == "block"
-
-    @Deprecated("Was renamed to Sprite to comply with variable type.", ReplaceWith("requireSprite()"))
-    fun getTexture() = sprite
-
-}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt
new file mode 100644 (file)
index 0000000..aae4f98
--- /dev/null
@@ -0,0 +1,53 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.graphics.GL20
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.utils.forEachBlockInArea
+import javax.inject.Inject
+
+@GameScope
+class BackgroundBlocksRenderer @Inject constructor(
+    gameWorld: GameWorld,
+    mobsController: MobsController
+) : BlocksRenderer(gameWorld, mobsController) {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    override val background = true
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        forEachBlockInArea(viewport) { x, y ->
+            drawBackMap(spriteBatch, viewport, x, y)
+        }
+
+        drawBlockDamage(spriteBatch, viewport)
+
+        spriteBatch.end()
+        Gdx.gl.glEnable(GL20.GL_BLEND)
+        Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA)
+        shapeRenderer.begin(ShapeRenderer.ShapeType.Filled)
+        shapeRenderer.setColor(0f, 0f, 0f, .5f)
+
+        forEachBlockInArea(viewport) { x, y ->
+            shadeBackMap(shapeRenderer, viewport, x, y)
+        }
+
+        shapeRenderer.end()
+        Gdx.gl.glDisable(GL20.GL_BLEND)
+        spriteBatch.begin()
+
+        forEachBlockInArea(viewport) { x, y ->
+            drawForeMap(spriteBatch, viewport, x, y)
+        }
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100000
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt
new file mode 100644 (file)
index 0000000..378ed7b
--- /dev/null
@@ -0,0 +1,108 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.Sprite
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.g2d.TextureRegion
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.MathUtils
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.utils.px
+
+abstract class BlocksRenderer(
+    protected val gameWorld: GameWorld,
+    protected val mobsController: MobsController,
+) : IGameRenderer {
+
+    protected abstract val background: Boolean
+
+    private val Block.canSeeThrough
+        get() = isNone() || params.isTransparent
+
+    private fun blockDamageSprite(index: Int): Sprite? {
+        if (index !in 0..< MAX_BLOCK_DAMAGE_INDEX) {
+            return null
+        }
+        return Assets.blockDamageSprites[index]
+    }
+
+    protected fun drawBlockDamage(spriteBatch: SpriteBatch, viewport: Rectangle) {
+        val player = mobsController.player
+        val blockDamage = player.blockDamage.takeIf { it > 0f } ?: return
+        val cursorX = player.cursorX
+        val cursorY = player.cursorY
+
+        val block = if (background) {
+            gameWorld.getBackMap(cursorX, cursorY)
+        } else {
+            gameWorld.getForeMap(cursorX, cursorY)
+        }
+
+        val index = (MAX_BLOCK_DAMAGE_INDEX.toFloat() * (blockDamage.toFloat() / block.params.hitPoints.toFloat()))
+            .let(MathUtils::floor)
+        val sprite = blockDamageSprite(index) ?: return
+
+        if (gameWorld.hasForeAt(cursorX, cursorY) != background) {
+            sprite.setBounds(
+                /* x = */ cursorX.px - viewport.x + block.params.spriteMargins.left,
+                /* y = */ cursorY.px - viewport.y + block.params.spriteMargins.top,
+                /* width = */ block.spriteWidth,
+                /* height = */ block.spriteHeight
+            )
+            sprite.draw(spriteBatch)
+        }
+    }
+
+    protected fun shadeBackMap(
+        shapeRenderer: ShapeRenderer,
+        viewport: Rectangle,
+        x: Int,
+        y: Int
+    ) {
+        val foregroundBlock = gameWorld.getForeMap(x, y)
+        val backgroundBlock = gameWorld.getBackMap(x, y)
+
+        if (foregroundBlock.canSeeThrough && !backgroundBlock.isNone()) {
+            val drawX = x.px - viewport.x
+            val drawY = y.px - viewport.y
+            val marginLeft = backgroundBlock.params.spriteMargins.left
+            val marginTop = backgroundBlock.params.spriteMargins.top
+
+            shapeRenderer.rect(
+                /* x = */ drawX + marginLeft,
+                /* y = */ drawY + marginTop,
+                /* width = */ backgroundBlock.width,
+                /* height = */ backgroundBlock.height
+            )
+        }
+    }
+
+    protected fun drawBackMap(spriteBatch: SpriteBatch, viewport: Rectangle, x: Int, y: Int) {
+        val foregroundBlock = gameWorld.getForeMap(x, y)
+        val backgroundBlock = gameWorld.getBackMap(x, y)
+
+        if (foregroundBlock.canSeeThrough && !backgroundBlock.isNone()) {
+            val drawX = x.px - viewport.x
+            val drawY = y.px - viewport.y
+            backgroundBlock.draw(spriteBatch, drawX, drawY)
+        }
+    }
+
+    protected fun drawForeMap(spriteBatch: SpriteBatch, viewport: Rectangle, x: Int, y: Int) {
+        val foregroundBlock = gameWorld.getForeMap(x, y)
+
+        if (!foregroundBlock.isNone() && foregroundBlock.params.isBackground == background) {
+            val drawX = x.px - viewport.x
+            val drawY = y.px - viewport.y
+            foregroundBlock.draw(spriteBatch, drawX, drawY)
+        }
+    }
+
+    companion object {
+        private const val MAX_BLOCK_DAMAGE_INDEX = 10
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt
new file mode 100644 (file)
index 0000000..03f6253
--- /dev/null
@@ -0,0 +1,114 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.Color
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.debug.DebugInfoStringsProvider
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.utils.bl
+import ru.deadsoftware.cavedroid.misc.utils.drawString
+import ru.deadsoftware.cavedroid.misc.utils.forEachBlockInArea
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+class DebugRenderer @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+    private val debugInfoStringsProvider: DebugInfoStringsProvider,
+) : IGameRenderer {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    private fun getMinimapColor(x: Int, y: Int): Color? {
+        val foregroundBlock = gameWorld.getForeMap(x, y)
+
+        return if (!foregroundBlock.isNone()) {
+            when (foregroundBlock) {
+                is Block.Water -> Color.BLUE
+                is Block.Lava -> Color.RED
+                else -> Color.BLACK
+            }
+        } else if (gameWorld.hasBackAt(x, y)) {
+            Color.DARK_GRAY
+        } else {
+            null
+        }
+    }
+
+    private fun drawMinimap(
+        spriteBatch: SpriteBatch,
+        shapeRenderer: ShapeRenderer,
+        minimapX: Float,
+        minimapY: Float,
+        minimapSize: Float
+    ) {
+        val mapArea = Rectangle(
+            /* x = */ mobsController.player.x - (minimapSize.px / 2),
+            /* y = */ mobsController.player.y - (minimapSize.px / 2),
+            /* width = */ minimapSize.px,
+            /* height = */ minimapSize.px
+        )
+
+        spriteBatch.end()
+        shapeRenderer.begin(ShapeRenderer.ShapeType.Filled)
+        shapeRenderer.color = Color.LIGHT_GRAY
+        shapeRenderer.rect(minimapX, minimapY, minimapSize, minimapSize)
+
+        forEachBlockInArea(mapArea) { x, y ->
+            getMinimapColor(x, y)?.let { color ->
+                shapeRenderer.setColor(color)
+                shapeRenderer.rect(
+                    /* x = */ minimapX + (x - mapArea.x.bl),
+                    /* y = */ minimapY + (y - mapArea.y.bl),
+                    /* width = */ 1f,
+                    /* height = */ 1f
+                )
+            }
+        }
+
+        shapeRenderer.color = Color.OLIVE
+        shapeRenderer.rect(minimapX + minimapSize / 2, minimapY + minimapSize / 2, 1f, 2f)
+        shapeRenderer.end()
+        spriteBatch.begin()
+    }
+
+    private fun drawDebugInfo(spriteBatch: SpriteBatch) {
+        debugInfoStringsProvider.getDebugStrings().forEachIndexed { index, str ->
+            spriteBatch.drawString(str, 0f, index * 10f)
+        }
+    }
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        if (mainConfig.isShowInfo) {
+            drawDebugInfo(spriteBatch)
+        }
+
+        if (mainConfig.isShowMap) {
+            drawMinimap(
+                spriteBatch = spriteBatch,
+                shapeRenderer = shapeRenderer,
+                minimapX = viewport.width - MinimapConfig.margin - MinimapConfig.size,
+                minimapY = MinimapConfig.margin,
+                minimapSize = MinimapConfig.size
+            )
+        }
+
+    }
+
+    companion object {
+        private const val RENDER_LAYER = Int.MAX_VALUE
+
+        private data object MinimapConfig {
+            const val margin = 24f
+            const val size = 64f
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt
new file mode 100644 (file)
index 0000000..a17bd61
--- /dev/null
@@ -0,0 +1,40 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.objects.DropController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.utils.cycledInsideWorld
+import ru.deadsoftware.cavedroid.misc.utils.drawSprite
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+class DropsRenderer @Inject constructor(
+    private val dropController: DropController,
+    private val gameWorld: GameWorld,
+) : IGameRenderer {
+
+    override val renderLayer = RENDER_LAYER
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        dropController.forEach { drop ->
+            drop.cycledInsideWorld(viewport, gameWorld.width.px)?.let { dropRect ->
+                spriteBatch.drawSprite(
+                    sprite = drop.item.sprite,
+                    x = dropRect.x - viewport.x,
+                    y = dropRect.y - viewport.y,
+                    width = dropRect.width,
+                    height = dropRect.height,
+                )
+            }
+        }
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100200
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt
new file mode 100644 (file)
index 0000000..ef8be71
--- /dev/null
@@ -0,0 +1,32 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.utils.forEachBlockInArea
+import javax.inject.Inject
+
+@GameScope
+class ForegroundBlocksRenderer @Inject constructor(
+    gameWorld: GameWorld,
+    mobsController: MobsController
+) : BlocksRenderer(gameWorld, mobsController) {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    override val background = false
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        forEachBlockInArea(viewport) { x, y ->
+            drawForeMap(spriteBatch, viewport, x, y)
+        }
+        drawBlockDamage(spriteBatch, viewport)
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100400
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt
new file mode 100644 (file)
index 0000000..b3f8990
--- /dev/null
@@ -0,0 +1,122 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.Player.ControlMode
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+class HudRenderer @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+) : IGameRenderer {
+
+    override val renderLayer = RENDER_LAYER
+
+    private val cursorTexture get() = requireNotNull(Assets.textureRegions[CURSOR_KEY])
+    private val hotbarTexture get() = requireNotNull(Assets.textureRegions[HOTBAR_KEY])
+    private val hotbarSelectorTexture get() = requireNotNull(Assets.textureRegions[HOTBAR_SELECTOR_KEY])
+    private val wholeHeartTexture get() = requireNotNull(Assets.textureRegions[WHOLE_HEART_KEY])
+    private val halfHeartTexture get() = requireNotNull(Assets.textureRegions[HALF_HEART_KEY])
+
+    private fun drawCursor(spriteBatch: SpriteBatch, viewport: Rectangle) {
+        val cursorX = mobsController.player.cursorX
+        val cursorY = mobsController.player.cursorY
+
+        if (gameWorld.hasForeAt(cursorX, cursorY) ||
+            gameWorld.hasBackAt(cursorX, cursorY) ||
+            mobsController.player.controlMode == ControlMode.CURSOR
+        ) {
+            spriteBatch.draw(cursorTexture, cursorX.px - viewport.x, cursorY.px - viewport.y)
+        }
+    }
+
+    private fun drawHealth(spriteBatch: SpriteBatch, x: Float, y: Float) {
+        val player = mobsController.player
+
+        if (player.gameMode == 1) {
+            return
+        }
+
+        val wholeHeart = wholeHeartTexture
+        val wholeHearts = player.health / 2
+
+        for (i in 0..<wholeHearts) {
+            spriteBatch.draw(wholeHeart, x + i * wholeHeart.regionWidth, y)
+        }
+
+        if (player.health % 2 == 1) {
+            spriteBatch.draw(halfHeartTexture, x + wholeHearts * wholeHeart.regionWidth, y)
+        }
+    }
+
+    private fun drawHotbarItems(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer,  hotbarX: Float) {
+        mobsController.player.inventory.asSequence().take(HotbarConfig.hotbarCells)
+            .forEachIndexed { index, item ->
+                if (item.item.isNone()) {
+                    return@forEachIndexed
+                }
+
+                item.draw(
+                    spriteBatch = spriteBatch,
+                    shapeRenderer = shapeRenderer,
+                    x = hotbarX + HotbarConfig.horizontalMargin +
+                            index * (HotbarConfig.itemSeparatorWidth + HotbarConfig.itemSlotSpace),
+                    y = HotbarConfig.verticalMargin,
+                )
+            }
+    }
+
+    private fun drawHotbarSelector(spriteBatch: SpriteBatch, hotbarX: Float) {
+        spriteBatch.draw(
+            /* region = */ hotbarSelectorTexture,
+            /* x = */ hotbarX - HotbarSelectorConfig.horizontalPadding
+                    + mobsController.player.slot * (HotbarConfig.itemSeparatorWidth + HotbarConfig.itemSlotSpace),
+            /* y = */ -HotbarSelectorConfig.verticalPadding
+        )
+    }
+
+    private fun drawHotbar(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle) {
+        val hotbar = hotbarTexture
+        val hotbarX = viewport.width / 2 - hotbar.regionWidth / 2
+
+        spriteBatch.draw(hotbar, hotbarX, 0f)
+        drawHealth(spriteBatch, hotbarX, hotbarTexture.regionHeight.toFloat())
+        drawHotbarSelector(spriteBatch, hotbarX)
+        drawHotbarItems(spriteBatch, shapeRenderer, hotbarX)
+    }
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        drawCursor(spriteBatch, viewport)
+        drawHotbar(spriteBatch, shapeRenderer, viewport)
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100500
+
+        private const val CURSOR_KEY = "cursor"
+        private const val HOTBAR_KEY = "hotbar"
+        private const val HOTBAR_SELECTOR_KEY = "hotbar_selector"
+        private const val WHOLE_HEART_KEY = "heart_whole"
+        private const val HALF_HEART_KEY = "heart_half"
+
+        private data object HotbarConfig {
+            const val horizontalMargin = 3f
+            const val verticalMargin = 3f
+            const val itemSeparatorWidth = 4f
+            const val itemSlotSpace = 16f
+            const val hotbarCells = 9
+        }
+
+        private data object HotbarSelectorConfig {
+            const val horizontalPadding = 1f
+            const val verticalPadding = 1f
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/IGameRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/IGameRenderer.kt
new file mode 100644 (file)
index 0000000..88b5744
--- /dev/null
@@ -0,0 +1,20 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+
+interface IGameRenderer {
+
+    val renderLayer: Int
+
+    /**
+     * When called, [spriteBatch] is beginned!
+     */
+    fun draw(
+        spriteBatch: SpriteBatch,
+        shapeRenderer: ShapeRenderer,
+        viewport: Rectangle,
+        delta: Float
+    )
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt
new file mode 100644 (file)
index 0000000..6d388bb
--- /dev/null
@@ -0,0 +1,45 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.Mob
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.utils.cycledInsideWorld
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+class MobsRenderer @Inject constructor(
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+) : IGameRenderer {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    private fun drawMob(spriteBatch: SpriteBatch, viewport: Rectangle, mob: Mob, delta: Float) {
+         mob.cycledInsideWorld(viewport, gameWorld.width.px)?.let { mobRect ->
+             mob.draw(spriteBatch, mobRect.x - viewport.x, mobRect.y - viewport.y, delta)
+         }
+    }
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        val player = mobsController.player
+        player.draw(
+            /* spriteBatch = */ spriteBatch,
+            /* x = */ player.x - viewport.x - player.width / 2,
+            /* y = */ player.y - viewport.y,
+            /* delta = */ delta
+        )
+
+        mobsController.mobs.forEach { mob ->
+            drawMob(spriteBatch, viewport, mob, delta)
+        }
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100100
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/RenderModule.kt b/core/src/ru/deadsoftware/cavedroid/game/render/RenderModule.kt
new file mode 100644 (file)
index 0000000..9ae3b46
--- /dev/null
@@ -0,0 +1,59 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import ru.deadsoftware.cavedroid.game.GameScope
+
+@Module
+object RenderModule {
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindBackGroundBlocksRenderer(renderer: BackgroundBlocksRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindForegroundBlocksRenderer(renderer: ForegroundBlocksRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindMobsRenderer(renderer: MobsRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindDropsRenderer(renderer: DropsRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindHudRenderer(renderer: HudRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindWindowsRenderer(renderer: WindowsRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindTouchControlsRenderer(renderer: TouchControlsRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindDebugRenderer(renderer: DebugRenderer): IGameRenderer = renderer
+
+//    @Provides
+//    @GameScope
+//    fun provideGameRenderers(renderers: Set<@JvmSuppressWildcards IGameRenderer>): List<IGameRenderer> {
+//        return renderers.asSequence()
+//            .sortedWith(Comparator.comparingInt(IGameRenderer::renderLayer))
+//            .toList()
+//    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt
new file mode 100644 (file)
index 0000000..5c95d69
--- /dev/null
@@ -0,0 +1,59 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.Player.ControlMode
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.utils.ArrayMapExtensions.component1
+import ru.deadsoftware.cavedroid.misc.utils.ArrayMapExtensions.component2
+import javax.inject.Inject
+
+@GameScope
+class TouchControlsRenderer @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+    private val gameWindowsManager: GameWindowsManager,
+) : IGameRenderer {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    private val shadeTexture get() = Assets.textureRegions[SHADE_KEY]
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        if (!mainConfig.isTouch || gameWindowsManager.getCurrentWindow() != GameUiWindow.NONE) {
+            return
+        }
+
+        val touchControlsMap = Assets.guiMap
+
+        touchControlsMap.forEach { (key, value) ->
+            val touchKey = value.rect
+            spriteBatch.draw(
+                /* region = */ Assets.textureRegions[key],
+                /* x = */ touchKey.x,
+                /* y = */ touchKey.y,
+                /* width = */ touchKey.width,
+                /* height = */ touchKey.height
+            )
+        }
+
+        // FIXME: Add pressed state for buttons
+        if (mobsController.player.controlMode == ControlMode.CURSOR) {
+            val altKeyRect = touchControlsMap.get("alt").rect
+            spriteBatch.draw(shadeTexture, altKeyRect.x, altKeyRect.y, altKeyRect.width, altKeyRect.height)
+        }
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100700
+
+        private const val SHADE_KEY = "shade"
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt
new file mode 100644 (file)
index 0000000..2fa093f
--- /dev/null
@@ -0,0 +1,40 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.render.windows.CraftingWindowRenderer
+import ru.deadsoftware.cavedroid.game.render.windows.CreativeWindowRenderer
+import ru.deadsoftware.cavedroid.game.render.windows.SurvivalWindowRenderer
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import javax.inject.Inject
+
+@GameScope
+class WindowsRenderer @Inject constructor(
+    private val creativeWindowRenderer: CreativeWindowRenderer,
+    private val survivalWindowRenderer: SurvivalWindowRenderer,
+    private val craftingWindowRenderer: CraftingWindowRenderer,
+    private val gameWindowsManager: GameWindowsManager,
+) : IGameRenderer {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        when (val windowType = gameWindowsManager.getCurrentWindow()) {
+            GameUiWindow.CREATIVE_INVENTORY -> creativeWindowRenderer.draw(spriteBatch, shapeRenderer, viewport, delta)
+            GameUiWindow.SURVIVAL_INVENTORY -> survivalWindowRenderer.draw(spriteBatch, shapeRenderer, viewport, delta)
+            GameUiWindow.CRAFTING_TABLE -> craftingWindowRenderer.draw(spriteBatch, shapeRenderer, viewport, delta)
+            GameUiWindow.NONE -> return
+            else -> Gdx.app.error(TAG, "Cannot draw window: ${windowType.name}")
+        }
+    }
+
+    companion object {
+        private const val TAG = "WindowsRenderer"
+
+        const val RENDER_LAYER = 100600
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/windows/AbstractWindowRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/windows/AbstractWindowRenderer.kt
new file mode 100644 (file)
index 0000000..57cf81b
--- /dev/null
@@ -0,0 +1,51 @@
+package ru.deadsoftware.cavedroid.game.render.windows
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.misc.utils.drawSprite
+
+abstract class AbstractWindowRenderer {
+
+    protected inline fun <reified T> drawItemsGrid(
+        spriteBatch: SpriteBatch,
+        shapeRenderer: ShapeRenderer,
+        gridX: Float,
+        gridY: Float,
+        items: Iterable<T>,
+        itemsInRow: Int,
+        cellWidth: Float,
+        cellHeight: Float
+    ) {
+        if (T::class != Item::class && T::class != InventoryItem::class) {
+            Gdx.app.log(_TAG, "Trying to draw items grid of not items")
+            return
+        }
+
+        items.forEachIndexed { index, element ->
+            val item = element as? Item
+            val inventoryItem = element as? InventoryItem
+
+            if (item == null && inventoryItem == null) {
+                throw IllegalStateException("This should be unreachable")
+            }
+
+            if (item?.isNone() == true || inventoryItem?.item?.isNone() == true) {
+                return@forEachIndexed
+            }
+
+            val itemX = gridX + (index % itemsInRow) * cellWidth
+            val itemY = gridY + (index / itemsInRow) * cellHeight
+
+            inventoryItem?.draw(spriteBatch, shapeRenderer, itemX, itemY)
+                ?: item?.let { spriteBatch.drawSprite(it.sprite, itemX, itemY) }
+        }
+    }
+
+    companion object {
+        protected const val _TAG = "AbstractWindowRenderer"
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/windows/CraftingWindowRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/windows/CraftingWindowRenderer.kt
new file mode 100644 (file)
index 0000000..3b08ed7
--- /dev/null
@@ -0,0 +1,95 @@
+package ru.deadsoftware.cavedroid.game.render.windows
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.render.IGameRenderer
+import ru.deadsoftware.cavedroid.game.render.WindowsRenderer
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.windows.inventory.CraftingInventoryWindow
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+class CraftingWindowRenderer @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+    private val gameWindowsManager: GameWindowsManager,
+    private val gameItemsHolder: GameItemsHolder,
+) : AbstractWindowRenderer(), IGameRenderer {
+
+    override val renderLayer get() = WindowsRenderer.RENDER_LAYER
+
+    private val craftingWindowTexture get() = requireNotNull(Assets.textureRegions[CRAFTING_WINDOW_KEY])
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        val windowTexture = craftingWindowTexture
+        val window = gameWindowsManager.currentWindow as CraftingInventoryWindow
+
+        val windowX = viewport.width / 2 - windowTexture.regionWidth / 2
+        val windowY = viewport.height / 2 - windowTexture.regionHeight / 2
+
+        spriteBatch.draw(windowTexture, windowX, windowY)
+
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            gridX = windowX + GameWindowsConfigs.Crafting.itemsGridMarginLeft,
+            gridY = windowY + GameWindowsConfigs.Crafting.itemsGridMarginTop,
+            items = mobsController.player.inventory.asSequence()
+                .drop(GameWindowsConfigs.Crafting.hotbarCells)
+                .take(GameWindowsConfigs.Crafting.itemsInCol * GameWindowsConfigs.Crafting.itemsInRow)
+                .asIterable(),
+            itemsInRow = GameWindowsConfigs.Crafting.itemsInRow,
+            cellWidth = GameWindowsConfigs.Crafting.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Crafting.itemsGridRowHeight,
+        )
+
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            gridX = windowX + GameWindowsConfigs.Crafting.itemsGridMarginLeft,
+            gridY = windowY + windowTexture.regionHeight - GameWindowsConfigs.Crafting.hotbarOffsetFromBottom,
+            items = mobsController.player.inventory.asSequence()
+                .take(GameWindowsConfigs.Crafting.hotbarCells)
+                .asIterable(),
+            itemsInRow = GameWindowsConfigs.Crafting.hotbarCells,
+            cellWidth = GameWindowsConfigs.Crafting.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Crafting.itemsGridRowHeight,
+        )
+
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            gridX = windowX + GameWindowsConfigs.Crafting.craftOffsetX,
+            gridY = windowY + GameWindowsConfigs.Crafting.craftOffsetY,
+            items = window.craftingItems.asSequence().map {  it ?: gameItemsHolder.fallbackItem.toInventoryItem()}.asIterable(),
+            itemsInRow = GameWindowsConfigs.Crafting.craftGridSize,
+            cellWidth = GameWindowsConfigs.Crafting.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Crafting.itemsGridRowHeight,
+        )
+
+        window.craftResult?.draw(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            x = windowX + GameWindowsConfigs.Crafting.craftResultOffsetX,
+            y = windowY + GameWindowsConfigs.Crafting.craftResultOffsetY
+        )
+
+        window.selectedItem?.drawSelected(
+            spriteBatch = spriteBatch,
+            x = Gdx.input.x * (viewport.width / Gdx.graphics.width),
+            y = Gdx.input.y * (viewport.height / Gdx.graphics.height)
+        )
+    }
+
+    companion object {
+        private const val CRAFTING_WINDOW_KEY = "crafting_table"
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt
new file mode 100644 (file)
index 0000000..4c43600
--- /dev/null
@@ -0,0 +1,83 @@
+package ru.deadsoftware.cavedroid.game.render.windows
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.render.IGameRenderer
+import ru.deadsoftware.cavedroid.game.render.WindowsRenderer
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+import kotlin.math.min
+
+@GameScope
+class CreativeWindowRenderer @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val gameWindowsManager: GameWindowsManager,
+    private val gameItemsHolder: GameItemsHolder,
+    private val mobsController: MobsController,
+) : AbstractWindowRenderer(), IGameRenderer {
+
+    override val renderLayer get() = WindowsRenderer.RENDER_LAYER
+
+    private val creativeWindowTexture get() = requireNotNull(Assets.textureRegions[CREATIVE_WINDOW_KEY])
+    private val scrollIndicatorTexture get() = requireNotNull(Assets.textureRegions[SCROLL_INDICATOR_KEY])
+
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        val creativeWindow = creativeWindowTexture
+
+        val windowX = viewport.width / 2 - creativeWindow.regionWidth / 2
+        val windowY = viewport.height / 2 - creativeWindow.regionHeight / 2
+        val oneScrollAmount = GameWindowsConfigs.Creative.scrollIndicatorFullHeight / gameItemsHolder.getMaxCreativeScrollAmount()
+
+        spriteBatch.draw(creativeWindow, windowX, windowY)
+        spriteBatch.draw(
+            /* region = */ scrollIndicatorTexture,
+            /* x = */ windowX + GameWindowsConfigs.Creative.scrollIndicatorMarginLeft,
+            /* y = */ windowY + GameWindowsConfigs.Creative.scrollIndicatorMarginTop
+                    + (gameWindowsManager.creativeScrollAmount * oneScrollAmount)
+        )
+
+        val allItems = gameItemsHolder.getAllItems()
+        val startIndex = gameWindowsManager.creativeScrollAmount * GameWindowsConfigs.Creative.itemsInRow
+        val endIndex = min(startIndex + GameWindowsConfigs.Creative.itemsOnPage, allItems.size)
+        val items = sequence {
+            for (i in startIndex..<endIndex) {
+                yield(allItems.elementAt(i))
+            }
+        }
+
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            gridX = windowX + GameWindowsConfigs.Creative.itemsGridMarginLeft,
+            gridY = windowY + GameWindowsConfigs.Creative.itemsGridMarginTop,
+            items = items.asIterable(),
+            itemsInRow = GameWindowsConfigs.Creative.itemsInRow,
+            cellWidth = GameWindowsConfigs.Creative.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Creative.itemsGridRowHeight,
+        )
+
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            gridX = windowX + GameWindowsConfigs.Creative.itemsGridMarginLeft,
+            gridY = windowY + creativeWindow.regionHeight - GameWindowsConfigs.Creative.playerInventoryOffsetFromBottom,
+            items = mobsController.player.inventory.asSequence().take(GameWindowsConfigs.Creative.invItems).asIterable(),
+            itemsInRow = GameWindowsConfigs.Creative.invItems,
+            cellWidth = GameWindowsConfigs.Creative.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Creative.itemsGridRowHeight,
+        )
+    }
+
+    companion object {
+        private const val CREATIVE_WINDOW_KEY = "creative"
+        private const val SCROLL_INDICATOR_KEY = "handle"
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/windows/SurvivalWindowRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/windows/SurvivalWindowRenderer.kt
new file mode 100644 (file)
index 0000000..56df2ca
--- /dev/null
@@ -0,0 +1,137 @@
+package ru.deadsoftware.cavedroid.game.render.windows
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.MathUtils
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.Mob
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.render.IGameRenderer
+import ru.deadsoftware.cavedroid.game.render.WindowsRenderer
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.windows.inventory.SurvivalInventoryWindow
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+import kotlin.math.atan
+
+@GameScope
+class SurvivalWindowRenderer @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+    private val gameWindowsManager: GameWindowsManager,
+    private val gameItemsHolder: GameItemsHolder,
+) : AbstractWindowRenderer(), IGameRenderer {
+
+    override val renderLayer get() = WindowsRenderer.RENDER_LAYER
+
+    private val survivalWindowTexture get() = requireNotNull(Assets.textureRegions[SURVIVAL_WINDOW_KEY])
+
+    private fun setPortraitHeadRotation(portraitX: Float, portraitY: Float) {
+        if (mainConfig.isTouch) {
+            return
+        }
+
+        val mouseX = Gdx.input.x * (mainConfig.width / Gdx.graphics.width)
+        val mouseY = Gdx.input.y * (mainConfig.height / Gdx.graphics.height)
+
+        val h = mouseX.toDouble() - portraitX.toDouble()
+        val v = mouseY.toDouble() - portraitY.toDouble()
+
+        mobsController.player.setDir(
+            if (mouseX < portraitX + mobsController.player.width / 2)
+                Mob.Direction.LEFT
+            else
+                Mob.Direction.RIGHT
+        )
+
+        mobsController.player.headRotation = atan(v / h).toFloat() * MathUtils.radDeg
+    }
+
+    private fun drawPlayerPortrait(spriteBatch: SpriteBatch, windowX: Float, windowY: Float, delta: Float) {
+        val portraitX = windowX + GameWindowsConfigs.Survival.portraitMarginLeft +
+                (GameWindowsConfigs.Survival.portraitWidth / 2 - mobsController.player.width / 2)
+        val portraitY = windowY + GameWindowsConfigs.Survival.portraitMarginTop +
+                (GameWindowsConfigs.Survival.portraitHeight / 2 - mobsController.player.height / 2)
+
+        setPortraitHeadRotation(portraitX, portraitY)
+        mobsController.player.draw(spriteBatch, portraitX, portraitY, delta)
+    }
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        val windowTexture = survivalWindowTexture
+        val window = gameWindowsManager.currentWindow as SurvivalInventoryWindow
+
+        val windowX = viewport.width / 2 - windowTexture.regionWidth / 2
+        val windowY = viewport.height / 2 - windowTexture.regionHeight / 2
+
+        spriteBatch.draw(windowTexture, windowX, windowY)
+
+        drawPlayerPortrait(spriteBatch, windowX, windowY, delta)
+
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            gridX = windowX + GameWindowsConfigs.Survival.itemsGridMarginLeft,
+            gridY = windowY + GameWindowsConfigs.Survival.itemsGridMarginTop,
+            items = mobsController.player.inventory.asSequence()
+                .drop(GameWindowsConfigs.Survival.hotbarCells)
+                .take(GameWindowsConfigs.Survival.itemsInCol * GameWindowsConfigs.Survival.itemsInRow)
+                .asIterable(),
+            itemsInRow = GameWindowsConfigs.Survival.itemsInRow,
+            cellWidth = GameWindowsConfigs.Survival.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Survival.itemsGridRowHeight,
+        )
+
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            gridX = windowX + GameWindowsConfigs.Survival.itemsGridMarginLeft,
+            gridY = windowY + windowTexture.regionHeight - GameWindowsConfigs.Survival.hotbarOffsetFromBottom,
+            items = mobsController.player.inventory.asSequence()
+                .take(GameWindowsConfigs.Survival.hotbarCells)
+                .asIterable(),
+            itemsInRow = GameWindowsConfigs.Survival.hotbarCells,
+            cellWidth = GameWindowsConfigs.Survival.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Survival.itemsGridRowHeight,
+        )
+
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            gridX = windowX + GameWindowsConfigs.Survival.craftOffsetX,
+            gridY = windowY + GameWindowsConfigs.Survival.craftOffsetY,
+            items = window.craftingItems.asSequence().mapIndexedNotNull { index, it ->
+                if (index % 3 > 1 || index / 3 > 1) {
+                    null
+                } else {
+                    it ?: gameItemsHolder.fallbackItem.toInventoryItem()
+                }
+            }.asIterable(),
+            itemsInRow = GameWindowsConfigs.Survival.craftGridSize,
+            cellWidth = GameWindowsConfigs.Survival.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Survival.itemsGridRowHeight,
+        )
+
+        window.craftResult?.draw(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            x = windowX + GameWindowsConfigs.Survival.craftResultOffsetX,
+            y = windowY + GameWindowsConfigs.Survival.craftResultOffsetY
+        )
+
+        window.selectedItem?.drawSelected(
+            spriteBatch = spriteBatch,
+            x = Gdx.input.x * (viewport.width / Gdx.graphics.width),
+            y = Gdx.input.y * (viewport.height / Gdx.graphics.height)
+        )
+    }
+
+    companion object {
+        private const val SURVIVAL_WINDOW_KEY = "survival"
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/windows/GameWindowsConfigs.kt b/core/src/ru/deadsoftware/cavedroid/game/windows/GameWindowsConfigs.kt
new file mode 100644 (file)
index 0000000..a6cff63
--- /dev/null
@@ -0,0 +1,73 @@
+package ru.deadsoftware.cavedroid.game.windows
+
+object GameWindowsConfigs {
+    data object Creative {
+        const val scrollIndicatorMarginLeft = 156f
+        const val scrollIndicatorMarginTop = 18f
+        const val scrollIndicatorFullHeight = 72f
+
+        const val itemsGridMarginLeft = 8f
+        const val itemsGridMarginTop = 18f
+
+        const val itemsGridRowHeight = 18f
+        const val itemsGridColWidth = 18f
+
+        const val itemsInRow = 8
+        const val itemsInCol = 5
+
+        const val invItems = 9
+
+        const val playerInventoryOffsetFromBottom = 24f
+
+        val itemsOnPage get() = itemsInCol * itemsInRow
+    }
+
+    data object Survival {
+        const val itemsGridMarginLeft = 8f
+        const val itemsGridMarginTop = 84f
+
+        const val itemsGridRowHeight = 18f
+        const val itemsGridColWidth = 18f
+
+        const val itemsInRow = 9
+        const val itemsInCol = 5
+
+        const val hotbarOffsetFromBottom = 24f
+        const val hotbarCells = 9
+
+        const val portraitMarginLeft = 24f
+        const val portraitMarginTop = 8f
+        const val portraitWidth = 48f
+        const val portraitHeight = 68f
+
+        const val craftGridSize = 2
+
+        const val craftOffsetX = 98f
+        const val craftOffsetY = 18f
+
+        const val craftResultOffsetX = 154f
+        const val craftResultOffsetY = 28f
+    }
+
+    data object Crafting {
+        const val itemsGridMarginLeft = 8f
+        const val itemsGridMarginTop = 84f
+
+        const val itemsGridRowHeight = 18f
+        const val itemsGridColWidth = 18f
+
+        const val itemsInRow = 9
+        const val itemsInCol = 5
+
+        const val hotbarOffsetFromBottom = 24f
+        const val hotbarCells = 9
+
+        const val craftGridSize = 3
+
+        const val craftOffsetX = 30f
+        const val craftOffsetY = 18f
+
+        const val craftResultOffsetX = 128f
+        const val craftResultOffsetY = 36f
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/windows/GameWindowsManager.kt b/core/src/ru/deadsoftware/cavedroid/game/windows/GameWindowsManager.kt
new file mode 100644 (file)
index 0000000..b07f1fd
--- /dev/null
@@ -0,0 +1,46 @@
+package ru.deadsoftware.cavedroid.game.windows
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.windows.inventory.AbstractInventoryWindow
+import ru.deadsoftware.cavedroid.game.windows.inventory.CraftingInventoryWindow
+import ru.deadsoftware.cavedroid.game.windows.inventory.CreativeInventoryWindow
+import ru.deadsoftware.cavedroid.game.windows.inventory.SurvivalInventoryWindow
+import javax.inject.Inject
+
+@GameScope
+class GameWindowsManager @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) {
+
+    var creativeScrollAmount = 0
+    var isDragging = false
+
+    var currentWindow: AbstractInventoryWindow? = null
+
+    @JvmName("getCurrentWindowType")
+    fun getCurrentWindow(): GameUiWindow {
+        return currentWindow?.type ?: GameUiWindow.NONE
+    }
+
+    fun openInventory() {
+        if (mobsController.player.gameMode == 1) {
+            currentWindow = CreativeInventoryWindow(GameUiWindow.CREATIVE_INVENTORY)
+        } else {
+            currentWindow = SurvivalInventoryWindow(GameUiWindow.SURVIVAL_INVENTORY)
+        }
+    }
+
+    fun openCrafting() {
+        currentWindow = CraftingInventoryWindow(GameUiWindow.CRAFTING_TABLE)
+    }
+
+    fun closeWindow() {
+        currentWindow = null
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/windows/inventory/AbstractInventoryWindow.kt b/core/src/ru/deadsoftware/cavedroid/game/windows/inventory/AbstractInventoryWindow.kt
new file mode 100644 (file)
index 0000000..0660604
--- /dev/null
@@ -0,0 +1,12 @@
+package ru.deadsoftware.cavedroid.game.windows.inventory
+
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+
+abstract class AbstractInventoryWindow {
+
+    abstract val type: GameUiWindow
+
+    abstract var selectedItem: InventoryItem?
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/windows/inventory/CraftingInventoryWindow.kt b/core/src/ru/deadsoftware/cavedroid/game/windows/inventory/CraftingInventoryWindow.kt
new file mode 100644 (file)
index 0000000..03808f1
--- /dev/null
@@ -0,0 +1,15 @@
+package ru.deadsoftware.cavedroid.game.windows.inventory
+
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+
+class CraftingInventoryWindow(
+    override val type: GameUiWindow,
+) : AbstractInventoryWindow() {
+
+    override var selectedItem: InventoryItem? = null
+
+    val craftingItems = MutableList<InventoryItem?>(9) { null }
+
+    var craftResult: InventoryItem? = null
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/windows/inventory/CreativeInventoryWindow.kt b/core/src/ru/deadsoftware/cavedroid/game/windows/inventory/CreativeInventoryWindow.kt
new file mode 100644 (file)
index 0000000..5f72243
--- /dev/null
@@ -0,0 +1,10 @@
+package ru.deadsoftware.cavedroid.game.windows.inventory
+
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+
+class CreativeInventoryWindow(
+    override val type: GameUiWindow,
+) : AbstractInventoryWindow() {
+    override var selectedItem: InventoryItem? = null
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/windows/inventory/SurvivalInventoryWindow.kt b/core/src/ru/deadsoftware/cavedroid/game/windows/inventory/SurvivalInventoryWindow.kt
new file mode 100644 (file)
index 0000000..4eeba3c
--- /dev/null
@@ -0,0 +1,15 @@
+package ru.deadsoftware.cavedroid.game.windows.inventory
+
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+
+class SurvivalInventoryWindow(
+    override val type: GameUiWindow,
+) : AbstractInventoryWindow() {
+
+    override var selectedItem: InventoryItem? = null
+
+    val craftingItems = MutableList<InventoryItem?>(9) { null }
+
+    var craftResult: InventoryItem? = null
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorld.java b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorld.java
new file mode 100644 (file)
index 0000000..f2fd27d
--- /dev/null
@@ -0,0 +1,218 @@
+package ru.deadsoftware.cavedroid.game.world;
+
+import kotlin.Pair;
+import ru.deadsoftware.cavedroid.game.GameItemsHolder;
+import ru.deadsoftware.cavedroid.game.GameScope;
+import ru.deadsoftware.cavedroid.game.mobs.MobsController;
+import ru.deadsoftware.cavedroid.game.model.block.Block;
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem;
+import ru.deadsoftware.cavedroid.game.model.item.Item;
+import ru.deadsoftware.cavedroid.game.model.world.generator.WorldGeneratorConfig;
+import ru.deadsoftware.cavedroid.game.objects.DropController;
+import ru.deadsoftware.cavedroid.misc.utils.MeasureUnitsUtilsKt;
+
+import javax.annotation.CheckForNull;
+import javax.inject.Inject;
+
+@GameScope
+public class GameWorld {
+
+    private final DropController mDropController;
+    private final MobsController mMobsController;
+    private final GameItemsHolder mGameItemsHolder;
+
+    private final int mWidth;
+    private final int mHeight;
+    private final Block[][] mForeMap;
+    private final Block[][] mBackMap;
+
+    private final WorldGeneratorConfig mWorldConfig =  WorldGeneratorConfig.Companion.getDefault();
+
+    @Inject
+    public GameWorld(DropController dropController,
+                     MobsController mobsController,
+                     GameItemsHolder gameItemsHolder,
+                     @CheckForNull Block[][] foreMap,
+                     @CheckForNull Block[][] backMap) {
+        mDropController = dropController;
+        mMobsController = mobsController;
+        mGameItemsHolder = gameItemsHolder;
+
+        boolean isNewGame = foreMap == null || backMap == null;
+
+        if (isNewGame) {
+            mWidth = mWorldConfig.getWidth();
+            mHeight = mWorldConfig.getHeight();
+            Pair<Block[][], Block[][]> maps = new GameWorldGenerator(mWorldConfig, mGameItemsHolder).generate();
+            mForeMap = maps.getFirst();
+            mBackMap = maps.getSecond();
+            mMobsController.getPlayer().respawn(this, mGameItemsHolder);
+        } else {
+            mForeMap = foreMap;
+            mBackMap = backMap;
+            mWidth = mForeMap.length;
+            mHeight = mForeMap[0].length;
+        }
+    }
+
+    public int getWidth() {
+        return mWidth;
+    }
+
+    public int getHeight() {
+        return mHeight;
+    }
+
+    /**
+     * @deprecated for kotlin use {@link MeasureUnitsUtilsKt#getPx } extension val
+     */
+    @Deprecated
+    public float getWidthPx() {
+        return MeasureUnitsUtilsKt.getPx(mWidth);
+    }
+
+    /**
+     * @deprecated for kotlin use {@link MeasureUnitsUtilsKt#getPx } extension val
+     */
+    @Deprecated
+    public float getHeightPx() {
+        return MeasureUnitsUtilsKt.getPx(mHeight);
+    }
+
+    public Block[][] getFullForeMap() {
+        return mForeMap;
+    }
+
+    public Block[][] getFullBackMap() {
+        return mBackMap;
+    }
+
+    private int transformX(int x) {
+        x = x % getWidth();
+        if (x < 0) {
+            x = getWidth() - Math.abs(x);
+        }
+        return x;
+    }
+
+    private Block getMap(int x, int y, int layer) {
+        Block map = mGameItemsHolder.getFallbackBlock();
+        try {
+            x = transformX(x);
+            map = (layer == 0) ? mForeMap[x][y] : mBackMap[x][y];
+        } catch (ArrayIndexOutOfBoundsException ignored) {
+        }
+        return map;
+    }
+
+    private void setMap(int x, int y, int layer, Block value) {
+        try {
+            x = transformX(x);
+            if (layer == 0) {
+                mForeMap[x][y] = value;
+            } else {
+                mBackMap[x][y] = value;
+            }
+        } catch (ArrayIndexOutOfBoundsException ignored) {
+        }
+    }
+
+    private boolean isSameSlab(Block slab1, Block slab2) {
+        if (!(slab1 instanceof Block.Slab) || !(slab2 instanceof Block.Slab)) {
+            return false;
+        }
+
+        return slab1.getParams().getKey().equals(((Block.Slab) slab2).getOtherPartBlockKey())
+                || slab1.getParams().getKey().equals(slab2.getParams().getKey());
+    }
+
+    public boolean hasForeAt(int x, int y) {
+        return getMap(x, y, 0) != mGameItemsHolder.getFallbackBlock();
+    }
+
+    public boolean hasBackAt(int x, int y) {
+        return getMap(x, y, 1) != mGameItemsHolder.getFallbackBlock();
+    }
+
+    public Block getForeMap(int x, int y) {
+        return getMap(x, y, 0);
+    }
+
+    public void setForeMap(int x, int y, Block block) {
+        setMap(x, y, 0, block);
+    }
+
+    public void resetForeMap(int x, int y) {
+        setForeMap(x, y, mGameItemsHolder.getFallbackBlock());
+    }
+
+    public Block getBackMap(int x, int y) {
+        return getMap(x, y, 1);
+    }
+
+    public void setBackMap(int x, int y, Block block) {
+        setMap(x, y, 1, block);
+    }
+
+    public boolean placeToForeground(int x, int y, Block value) {
+        if (!hasForeAt(x, y) || value == mGameItemsHolder.getFallbackBlock() || !getForeMap(x, y).hasCollision()) {
+            setForeMap(x, y, value);
+            return true;
+        } else if (value instanceof Block.Slab && isSameSlab(value, getForeMap(x, y))) {
+            setForeMap(x, y, mGameItemsHolder.getBlock(((Block.Slab) value).getFullBlockKey()));
+            return true;
+        }
+        return false;
+    }
+
+    public boolean placeToBackground(int x, int y, Block value) {
+        if (value == mGameItemsHolder.getFallbackBlock() || (getBackMap(x, y) == mGameItemsHolder.getFallbackBlock() && value.hasCollision()) &&
+                (!value.isTransparent() || value == mGameItemsHolder.getBlock("glass"))) {
+            setBackMap(x, y, value);
+            return true;
+        }
+        return false;
+    }
+
+    private void playerDurateTool() {
+        final InventoryItem playerCurrentItem = mMobsController.getPlayer().getCurrentItem();
+        if (mMobsController.getPlayer().getCurrentItem().getItem().isTool()) {
+            mMobsController.getPlayer().decreaseCurrentItemCount(mGameItemsHolder);
+        }
+    }
+
+    private boolean shouldDrop(Block block) {
+        final Item item = mMobsController.getPlayer().getCurrentItem().getItem();
+        int toolLevel = item.isTool() ? ((Item.Tool)item).getLevel() : 0;
+        if (item.isTool() && block.getParams().getToolType() != item.getClass()) {
+            toolLevel = 0;
+        }
+        return toolLevel >= block.getParams().getToolLevel();
+    }
+
+    public void destroyForeMap(int x, int y) {
+        Block block = getForeMap(x, y);
+        if (block.hasDrop() && shouldDrop(block)) {
+            for (int i = 0; i < block.getParams().getDropInfo().getCount(); i++) {
+                mDropController.addDrop(transformX(x) * 16 + 4, y * 16 + 4, mGameItemsHolder.getItem(block.getDrop()));
+            }
+        }
+        playerDurateTool();
+        placeToForeground(x, y, mGameItemsHolder.getFallbackBlock());
+    }
+
+    public WorldGeneratorConfig getWorldConfig() {
+        return mWorldConfig;
+    }
+
+    public void destroyBackMap(int x, int y) {
+        Block block = getBackMap(x, y);
+        if (block.hasDrop() && shouldDrop(block)) {
+            for (int i = 0; i < block.getParams().getDropInfo().getCount(); i++) {
+                mDropController.addDrop(transformX(x) * 16 + 4, y * 16 + 4, mGameItemsHolder.getItem(block.getDrop()));
+            }
+        }
+        playerDurateTool();
+        placeToBackground(x, y, mGameItemsHolder.getFallbackBlock());
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldBlocksLogicControllerTask.kt b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldBlocksLogicControllerTask.kt
new file mode 100644 (file)
index 0000000..387407a
--- /dev/null
@@ -0,0 +1,62 @@
+package ru.deadsoftware.cavedroid.game.world
+
+import com.badlogic.gdx.utils.Timer.Task
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.actions.getRequiresBlockAction
+import ru.deadsoftware.cavedroid.game.actions.updateblock.IUpdateBlockAction
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import javax.inject.Inject
+
+@GameScope
+class GameWorldBlocksLogicControllerTask @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val updateBlockActions: Map<String, @JvmSuppressWildcards IUpdateBlockAction>,
+    private val mobsController: MobsController,
+) : Task() {
+
+    private var currentRelativeChunk = 0
+
+    private fun getChunkStart(): Int {
+        val playerX = mobsController.player.mapX
+        val playerChunk = playerX / CHUNK_WIDTH
+        val currentChunk = playerChunk - CHUNKS / 2 + currentRelativeChunk
+
+        return currentChunk * 16
+    }
+
+    private fun updateBlock(x: Int, y: Int) {
+        val block = gameWorld.getForeMap(x, y)
+
+        if (block.isNone()) {
+            return
+        }
+
+        val blockKey = block.params.key
+        val action = updateBlockActions[blockKey]
+            ?: updateBlockActions.getRequiresBlockAction().takeIf { block.params.requiresBlock }
+
+        action?.update(x, y)
+    }
+
+    override fun run() {
+        val startX = getChunkStart()
+
+        for (y in gameWorld.height downTo 0) {
+            for (x in startX ..< startX + CHUNK_WIDTH) {
+                updateBlock(x, y)
+            }
+        }
+
+        currentRelativeChunk = (currentRelativeChunk + 1) % CHUNKS
+    }
+
+    companion object {
+        private const val TAG = "GameWorldBlocksLogicControllerTask"
+
+        private const val CHUNK_WIDTH = 16
+        private const val CHUNKS = 3
+
+        const val WORLD_BLOCKS_LOGIC_UPDATE_INTERVAL_SEC = .1f
+    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.java b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.java
new file mode 100644 (file)
index 0000000..416721f
--- /dev/null
@@ -0,0 +1,223 @@
+package ru.deadsoftware.cavedroid.game.world;
+
+import com.badlogic.gdx.utils.Timer;
+import ru.deadsoftware.cavedroid.game.GameItemsHolder;
+import ru.deadsoftware.cavedroid.game.GameScope;
+import ru.deadsoftware.cavedroid.game.mobs.MobsController;
+import ru.deadsoftware.cavedroid.game.model.block.Block;
+
+import javax.annotation.CheckForNull;
+import javax.inject.Inject;
+import java.util.*;
+
+@GameScope
+public class GameWorldFluidsLogicControllerTask extends Timer.Task {
+
+    public static final float FLUID_UPDATE_INTERVAL_SEC = 0.25f;
+
+    private short mUpdateTick = 0;
+
+    private final GameWorld mGameWorld;
+    private final MobsController mMobsController;
+    private final GameItemsHolder mGameItemsHolder;
+
+    private final Map<Class<? extends Block.Fluid>, List<? extends Block.Fluid>> mFluidStatesMap;
+
+    private final class UpdateCommand {
+        final Runnable command;
+        final int priority;
+
+        private UpdateCommand(int priority, Runnable command) {
+            this.priority = priority;
+            this.command = command;
+        }
+
+        private UpdateCommand(Block block, int x, int y, int priority) {
+            this(priority, () -> mGameWorld.setForeMap(x, y, block));
+        }
+
+        private UpdateCommand(Block.Fluid fluid, int x, int y) {
+            this(fluid, x, y, ((5 -fluid.getState() )+ 1) * (fluid.isLava() ? 2 : 1));
+        }
+
+        private int getPriority() {
+            return priority;
+        }
+
+        private void exec() {
+           command.run();
+        }
+    }
+
+    private final PriorityQueue<UpdateCommand> mUpdateQueue
+            = new PriorityQueue<>(Comparator.comparingInt(UpdateCommand::getPriority));
+
+    @Inject
+    GameWorldFluidsLogicControllerTask(GameWorld gameWorld,
+                                       MobsController mobsController,
+                                       GameItemsHolder gameItemsHolder) {
+        mGameWorld = gameWorld;
+        mMobsController = mobsController;
+        mGameItemsHolder = gameItemsHolder;
+
+        final List<Block.Water> waters = mGameItemsHolder.getBlocksByType(Block.Water.class);
+        waters.sort(Comparator.comparingInt(Block.Water::getState));
+
+        final List<Block.Lava> lavas = mGameItemsHolder.getBlocksByType(Block.Lava.class);
+        lavas.sort(Comparator.comparingInt(Block.Lava::getState));
+
+        mFluidStatesMap = new HashMap<>();
+        mFluidStatesMap.put(Block.Water.class, waters);
+        mFluidStatesMap.put(Block.Lava.class, lavas);
+    }
+
+    @CheckForNull
+    private List<? extends Block.Fluid> getFluidStateList(Block.Fluid fluid) {
+        return mFluidStatesMap.get(fluid.getClass());
+    }
+
+    private int getCurrentStateIndex(Block.Fluid fluid) {
+        @CheckForNull final List<? extends Block.Fluid> stateList = getFluidStateList(fluid);
+
+        if (stateList == null) {
+            return -1;
+        }
+
+        return stateList.indexOf(fluid);
+    }
+
+    @CheckForNull
+    private Block.Fluid getNextStateBlock(Block.Fluid fluid) {
+        @CheckForNull final List<? extends Block.Fluid> stateList = getFluidStateList(fluid);
+
+        if (stateList == null) {
+            return null;
+        }
+
+        int currentState = stateList.indexOf(fluid);
+
+        if (currentState < 0) {
+            return null;
+        }
+
+        int nextState = currentState + 1;
+
+        if (nextState == 1) {
+            nextState++;
+        }
+
+        if (nextState < stateList.size()) {
+            return stateList.get(nextState);
+        }
+
+        return null;
+    }
+
+    private boolean noFluidNearby(int x, int y) {
+        return !mGameWorld.getForeMap(x, y - 1).isFluid() &&
+                (!mGameWorld.getForeMap(x - 1, y).isFluid() || ((Block.Fluid)mGameWorld.getForeMap(x - 1, y)).getState() >= ((Block.Fluid)mGameWorld.getForeMap(x, y)).getState()) &&
+                (!mGameWorld.getForeMap(x + 1, y).isFluid() || ((Block.Fluid)mGameWorld.getForeMap(x + 1, y)).getState() >= ((Block.Fluid)mGameWorld.getForeMap(x, y)).getState());
+    }
+
+    private boolean drainFluid(int x, int y) {
+        final Block block = mGameWorld.getForeMap(x, y);
+
+        if (!(block instanceof Block.Fluid fluid)) {
+            return true;
+        }
+
+        if (fluid.getState() > 0) {
+            if (noFluidNearby(x, y)) {
+                @CheckForNull final Block.Fluid nextState = getNextStateBlock(fluid);
+                if (nextState == null) {
+                    mUpdateQueue.offer(new UpdateCommand(-1, () -> mGameWorld.resetForeMap(x, y)));
+                    return true;
+                }
+
+                mUpdateQueue.offer(new UpdateCommand(nextState, x, y));
+            }
+        }
+        return false;
+    }
+
+    private boolean fluidCanFlowThere(Block.Fluid fluid, Block targetBlock) {
+        return targetBlock == mGameItemsHolder.getFallbackBlock() ||
+                (!targetBlock.getParams().getHasCollision() && !targetBlock.isFluid()) ||
+                (fluid.getClass() == targetBlock.getClass() && fluid.getState() < ((Block.Fluid)targetBlock).getState());
+    }
+
+    private void flowFluidTo(Block.Fluid currentFluid, int x, int y, Block.Fluid nextStateFluid) {
+        final Block targetBlock = mGameWorld.getForeMap(x, y);
+
+        if (fluidCanFlowThere(currentFluid, targetBlock)) {
+            mUpdateQueue.offer(new UpdateCommand(nextStateFluid, x, y));
+        } else if (currentFluid.isWater() && targetBlock.isLava()) {
+            if (((Block.Lava)targetBlock).getState() > 0) {
+                mUpdateQueue.offer(new UpdateCommand(100, () -> mGameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("cobblestone"))));
+            } else {
+                mUpdateQueue.offer(new UpdateCommand(300, () -> mGameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("obsidian"))));
+            }
+        } else if (currentFluid.isLava() && targetBlock.isWater()) {
+            mUpdateQueue.offer(new UpdateCommand(200, () -> mGameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("stone"))));
+        }
+    }
+
+    private void flowFluid(int x, int y) {
+        Block.Fluid fluid = (Block.Fluid) mGameWorld.getForeMap(x, y);
+        @CheckForNull final List<? extends Block.Fluid> stateList = getFluidStateList(fluid);
+
+        if (stateList == null) {
+            return;
+        }
+
+        if (fluid.getState() < stateList.size() - 1 && mGameWorld.getForeMap(x, y + 1).hasCollision()) {
+            @CheckForNull Block.Fluid nextState = getNextStateBlock(fluid);
+
+            if (nextState == null) {
+                return;
+            }
+
+            flowFluidTo(fluid, x - 1, y, nextState);
+            flowFluidTo(fluid, x + 1, y, nextState);
+        } else {
+            flowFluidTo(fluid, x, y + 1, stateList.get(1));
+        }
+
+    }
+
+    private void updateFluids(int x, int y) {
+        final Block block = mGameWorld.getForeMap(x, y);
+        if (!block.isFluid() || (block.isLava() && mUpdateTick % 2 == 0)) {
+            return;
+        }
+        if (drainFluid(x, y)) {
+            return;
+        }
+        flowFluid(x, y);
+    }
+
+    private void fluidUpdater() {
+        int midScreen = (int) mMobsController.getPlayer().x / 16;
+        for (int y = mGameWorld.getHeight() - 1; y >= 0; y--) {
+            for (int x = 0; x <= Math.min(mGameWorld.getWidth() / 2, 32); x++) {
+                updateFluids(midScreen + x, y);
+                updateFluids(midScreen - x, y);
+            }
+        }
+
+        while (!mUpdateQueue.isEmpty()) {
+            final UpdateCommand command = mUpdateQueue.poll();
+            command.exec();
+        }
+    }
+
+    @Override
+    public void run() {
+        if (mUpdateTick < 0xFF) {
+            mUpdateTick ++;
+        } else {
+            mUpdateTick = 0;
+        }
+        fluidUpdater();
+    }
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt
new file mode 100644 (file)
index 0000000..e00a0ea
--- /dev/null
@@ -0,0 +1,291 @@
+package ru.deadsoftware.cavedroid.game.world
+
+import com.google.common.primitives.Ints.min
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.world.Biome
+import ru.deadsoftware.cavedroid.game.model.world.generator.WorldGeneratorConfig
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.random.Random
+
+class GameWorldGenerator(
+    private val config: WorldGeneratorConfig,
+    private val gameItemsHolder: GameItemsHolder,
+) {
+
+    private val random = Random(config.seed)
+
+    private val foreMap by lazy { Array(config.width) { Array(config.height) { gameItemsHolder.fallbackBlock } } }
+    private val backMap by lazy { Array(config.width) { Array(config.height) { gameItemsHolder.fallbackBlock } } }
+
+    private val heights by lazy { generateHeights() }
+    private val biomesMap by lazy { generateBiomes() }
+
+    private val plainsPlants = listOf("dandelion", "rose", "tallgrass")
+    private val mushrooms = listOf("mushroom_brown", "mushroom_red",)
+
+    private fun generateHeights(): IntArray {
+        val surfaceHeightRange = config.minSurfaceHeight .. config.maxSurfaceHeight
+        val result = IntArray(config.width)
+
+        result[0] = surfaceHeightRange.random(random)
+
+        for (x in 1 ..< config.width) {
+            val previous = result[x - 1]
+            var d = random.nextInt(-5, 6).let { if (it !in -4..4) it / abs(it) else 0 }
+
+            if (previous + d !in surfaceHeightRange) { d = -d }
+
+            if (result.lastIndex - x < abs(result[0] - previous) * 3) {
+                d = result[0].compareTo(previous).let { if (it != 0) it / abs(it) else 0 }
+            }
+
+            result[x] = result[x - 1] + d
+        }
+
+        return result
+    }
+
+    private fun generateBiomes(): Map<Int, Biome> {
+        val xSequence = sequence {
+            var lastX = 0
+            var count = 0
+
+            while (lastX < config.width - config.minBiomeSize - 1) {
+                yield(lastX)
+
+                lastX = random.nextInt(lastX + config.minBiomeSize, config.width)
+                count++
+            }
+        }
+
+        return xSequence.associateWith { config.biomes.random(random) }
+    }
+
+    private fun winterBiome(x: Int) {
+        assert(x in 0 ..< config.width) { "x not in range of world width" }
+
+        val surfaceHeight = heights[x]
+
+        val grass = gameItemsHolder.getBlock("grass_snowed")
+        val bedrock = gameItemsHolder.getBlock("bedrock")
+        val dirt = gameItemsHolder.getBlock("dirt")
+        val stone = gameItemsHolder.getBlock("stone")
+
+        foreMap[x][surfaceHeight] = grass
+        foreMap[x][config.height - 1] = bedrock
+        backMap[x][surfaceHeight] = grass
+        backMap[x][config.height - 1] = bedrock
+
+        for (y in min(surfaceHeight + 1, config.seaLevel) ..< config.height - 1) {
+            if (y <= surfaceHeight) {
+                backMap[x][y] = dirt
+                continue
+            }
+
+            foreMap[x][y] = when {
+                y < surfaceHeight + random.nextInt(5, 8) -> dirt
+                else -> stone
+            }
+            backMap[x][y] = foreMap[x][y]
+        }
+    }
+
+    private fun plainsBiome(x: Int) {
+        assert(x in 0 ..< config.width) { "x not in range of world width" }
+
+        val surfaceHeight = heights[x]
+
+        val grass = gameItemsHolder.getBlock("grass")
+        val bedrock = gameItemsHolder.getBlock("bedrock")
+        val dirt = gameItemsHolder.getBlock("dirt")
+        val stone = gameItemsHolder.getBlock("stone")
+
+        foreMap[x][surfaceHeight] = grass
+        foreMap[x][config.height - 1] = bedrock
+        backMap[x][surfaceHeight] = grass
+        backMap[x][config.height - 1] = bedrock
+
+        for (y in min(surfaceHeight + 1, config.seaLevel) ..< config.height - 1) {
+            if (y <= surfaceHeight) {
+                backMap[x][y] = dirt
+                continue
+            }
+
+            foreMap[x][y] = when {
+                y < surfaceHeight + random.nextInt(5, 8) -> dirt
+                else -> stone
+            }
+            backMap[x][y] = foreMap[x][y]
+        }
+
+        val plant = random.nextInt(100)
+        if (surfaceHeight < config.seaLevel) {
+            if (plant < 10) {
+                generateOak(x)
+            } else if (plant < 40) {
+                generateTallGrass(x)
+            }
+        }
+    }
+
+    private fun desertBiome(x: Int) {
+        assert(x in 0 ..< config.width) { "x not in range of world width" }
+
+        val surfaceHeight = heights[x]
+
+        val sand = gameItemsHolder.getBlock("sand")
+        val bedrock = gameItemsHolder.getBlock("bedrock")
+        val sandstone = gameItemsHolder.getBlock("sandstone")
+        val stone = gameItemsHolder.getBlock("stone")
+
+
+        foreMap[x][surfaceHeight] = sand
+        foreMap[x][config.height - 1] = bedrock
+        backMap[x][surfaceHeight] = sand
+        backMap[x][config.height - 1] = bedrock
+
+        for (y in min(surfaceHeight + 1, config.seaLevel) ..< config.height - 1) {
+            if (y <= surfaceHeight) {
+                backMap[x][y] = sand
+                continue
+            }
+
+            foreMap[x][y] = when {
+                y < surfaceHeight + random.nextInt(5, 8) -> sand
+                y < surfaceHeight + random.nextInt(0, 2) -> sandstone
+                else -> stone
+            }
+            backMap[x][y] = foreMap[x][y]
+        }
+
+        val plant = random.nextInt(100)
+        if (surfaceHeight < config.seaLevel) {
+            if (plant < 5) {
+                generateCactus(x)
+            } else if (plant < 10) {
+                generateDeadBush(x)
+            }
+        }
+    }
+
+    private fun fillWater() {
+        val water = gameItemsHolder.getBlock("water")
+
+        for (x in 0 ..< config.width) {
+            for (y in config.seaLevel ..< config.height) {
+                if (foreMap[x][y] != gameItemsHolder.fallbackBlock) {
+                    break
+                }
+
+                foreMap[x][y] = water
+            }
+        }
+    }
+
+    private fun generateCactus(x: Int) {
+        val cactus = gameItemsHolder.getBlock("cactus")
+        val cactusHeight = random.nextInt(3)
+        val h = heights[x] - 1
+
+        for (y in h downTo max(0, h - cactusHeight)) {
+            foreMap[x][y] = cactus
+        }
+    }
+
+    private fun generateOak(x: Int) {
+        val log = gameItemsHolder.getBlock("log_oak")
+        val leaves = gameItemsHolder.getBlock("leaves_oak")
+        val h = heights[x] - 1
+        val treeH = random.nextInt(5, 7)
+        val height = max(0, h - treeH)
+
+        val top = height - 1
+        if (top >= 0) {
+            foreMap[x][top] = leaves
+            backMap[x][top] = leaves
+        }
+
+        for (x1 in max(0, x - 1) .. min(config.width - 1, x + 1)) {
+            for (y in height .. height + treeH - 4) {
+                foreMap[x1][y] = leaves
+                backMap[x1][y] = leaves
+            }
+            if (random.nextInt(15) < 3) {
+                foreMap[x1][heights[x1] - 1] = gameItemsHolder.getBlock(mushrooms.random(random))
+            }
+        }
+
+        for (y in h downTo height) {
+            backMap[x][y] = log
+        }
+    }
+
+    private fun generateTallGrass(x: Int) {
+        val tallGrass = gameItemsHolder.getBlock(plainsPlants.random(random))
+        val h = heights[x] - 1
+        if (h > 0) {
+            foreMap[x][h] = tallGrass
+        }
+    }
+
+    private fun generateDeadBush(x: Int) {
+        val bush = gameItemsHolder.getBlock("deadbush")
+        val h = heights[x] - 1
+        if (h > 0) {
+            foreMap[x][h] = bush
+        }
+    }
+
+    private fun generateOres(x : Int) {
+        val stone = gameItemsHolder.getBlock("stone")
+        val coal = gameItemsHolder.getBlock("coal_ore")
+        val iron = gameItemsHolder.getBlock("iron_ore")
+        val gold = gameItemsHolder.getBlock("gold_ore")
+        val diamond = gameItemsHolder.getBlock("diamond_ore")
+        val lapis = gameItemsHolder.getBlock("lapis_ore")
+
+        for (y in heights[x] ..< config.height) {
+            val res = random.nextInt(10000)
+
+            val h = config.height - y
+            val block = when {
+                res in 0..<25 && h < 16 -> diamond
+                res in 25 ..< 50 && h < 32 -> gold
+                res in 50 ..< 250 && h < 64 -> iron
+                res in 250 ..< 450 && h < 128 -> coal
+                res in 450 ..< (450 + (25 - (abs(h - 16) * (25 / 16)))) -> lapis
+                else -> null
+            }
+
+            if (block != null && foreMap[x][y] == stone) {
+                foreMap[x][y] = block
+            }
+        }
+    }
+
+    /**
+     * Generate world
+     */
+    fun generate(): Pair<Array<Array<Block>>, Array<Array<Block>>> {
+        var biome = Biome.PLAINS
+
+        for (x in 0 until config.width) {
+            biome = biomesMap[x] ?: biome
+
+            when (biome) {
+                Biome.PLAINS -> plainsBiome(x)
+                Biome.DESERT -> desertBiome(x)
+                Biome.WINTER -> winterBiome(x)
+            }
+
+            generateOres(x)
+        }
+
+        fillWater()
+
+        return Pair(foreMap, backMap)
+    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldMobDamageControllerTask.kt b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldMobDamageControllerTask.kt
new file mode 100644 (file)
index 0000000..8dc39b9
--- /dev/null
@@ -0,0 +1,38 @@
+package ru.deadsoftware.cavedroid.game.world
+
+import com.badlogic.gdx.utils.Timer
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.misc.utils.forEachBlockInArea
+import javax.inject.Inject
+import kotlin.math.max
+
+@GameScope
+class GameWorldMobDamageControllerTask @Inject constructor(
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+    private val gameItemsHolder: GameItemsHolder,
+) : Timer.Task() {
+
+    override fun run() {
+        sequence {
+            yield(mobsController.player)
+            yieldAll(mobsController.mobs)
+        }.forEach { mob ->
+            forEachBlockInArea(mob) { x, y ->
+                val foregroundBlock = gameWorld.getForeMap(x, y)
+                val backgroundBlock = gameWorld.getBackMap(x, y)
+
+                mob.damage(max(foregroundBlock.params.damage, backgroundBlock.params.damage))
+            }
+        }
+
+
+    }
+
+    companion object {
+        const val ENVIRONMENTAL_MOB_DAMAGE_INTERVAL_SEC = 0.5f
+    }
+
+}
\ No newline at end of file
index f96d4d42756545c2d13cfc20de9a74655f986c15..6accb5726147700119cb351f1d327d7760df4937 100644 (file)
@@ -19,7 +19,7 @@ public class MenuProc extends Renderer {
 
     public class Input {
         private void startNewGame(int gameMode) {
-            mMainConfig.getCaveGame().newGame();
+            mMainConfig.getCaveGame().newGame(gameMode);
         }
 
         public void newGameClicked() {
@@ -55,15 +55,19 @@ public class MenuProc extends Renderer {
     private Menu mCurrentMenu;
 
     @Inject
-    public MenuProc(MainConfig mainConfig) {
+    public MenuProc(
+            MainConfig mainConfig,
+            MenuMain.Factory menuMainFactory,
+            MenuNewGame.Factory menuNewGameFactory
+    ) {
         super(mainConfig.getWidth(), mainConfig.getHeight());
 
         mMainConfig = mainConfig;
 
         Input menuInput = new Input();
 
-        mMenuMain = new MenuMain(getWidth(), getHeight(), this::drawButton, mainConfig, menuInput);
-        mMenuNewGame = new MenuNewGame(getWidth(), getHeight(), this::drawButton, mainConfig, menuInput);
+        mMenuMain = menuMainFactory.get(getWidth(), getHeight(), this::drawButton, menuInput);
+        mMenuNewGame = menuNewGameFactory.get(getWidth(), getHeight(), this::drawButton, menuInput);
 
         mCurrentMenu = mMenuMain;
     }
index 9f8df4c25d96d498c6019c1e123ef99a61e05c6e..587d0f622ebbc5df347d482734e75996bc006c3a 100644 (file)
@@ -12,6 +12,7 @@ import ru.deadsoftware.cavedroid.menu.objects.Button;
 import ru.deadsoftware.cavedroid.menu.objects.ButtonEventListener;
 import ru.deadsoftware.cavedroid.menu.objects.ButtonRenderer;
 import ru.deadsoftware.cavedroid.misc.Assets;
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader;
 
 import java.util.HashMap;
 
@@ -19,6 +20,7 @@ public abstract class Menu {
 
     protected final MainConfig mMainConfig;
     protected final MenuProc.Input mMenuInput;
+    protected final AssetLoader mAssetLoader;
 
     private final ButtonRenderer mButtonRenderer;
 
@@ -35,12 +37,18 @@ public abstract class Menu {
      * @param height         Viewport height
      * @param buttonRenderer {@link ButtonRenderer} that will draw the buttons of this menu
      */
-    Menu(float width, float height, ButtonRenderer buttonRenderer, MainConfig mainConfig, MenuProc.Input menuInput) {
+    Menu(float width,
+         float height,
+         ButtonRenderer buttonRenderer,
+         MainConfig mainConfig,
+         MenuProc.Input menuInput,
+         AssetLoader assetLoader) {
         mWidth = width;
         mHeight = height;
         mButtonRenderer = buttonRenderer;
         mMainConfig = mainConfig;
         mMenuInput = menuInput;
+        mAssetLoader = assetLoader;
         initButtons();
     }
 
index d2dedd23c351f3fc8e91626375e55a0f3b3fd90d..f71183a0583ce95b4c78fa760cb081e5323188e0 100644 (file)
@@ -7,13 +7,20 @@ import ru.deadsoftware.cavedroid.menu.MenuProc;
 import ru.deadsoftware.cavedroid.menu.objects.Button;
 import ru.deadsoftware.cavedroid.menu.objects.ButtonEventListener;
 import ru.deadsoftware.cavedroid.menu.objects.ButtonRenderer;
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader;
 
+import javax.inject.Inject;
 import java.util.HashMap;
 
 public class MenuMain extends Menu {
 
-    public MenuMain(float width, float height, ButtonRenderer buttonRenderer, MainConfig mainConfig, MenuProc.Input menuInput) {
-        super(width, height, buttonRenderer, mainConfig, menuInput);
+    public MenuMain(float width,
+                    float height,
+                    ButtonRenderer buttonRenderer,
+                    MainConfig mainConfig,
+                    MenuProc.Input menuInput,
+                    AssetLoader assetLoader) {
+        super(width, height, buttonRenderer, mainConfig, menuInput, assetLoader);
     }
 
     @Override
@@ -27,9 +34,27 @@ public class MenuMain extends Menu {
 
     @Override
     protected void initButtons() {
-        loadButtonsFromJson(Gdx.files.internal("json/menu_main_buttons.json"));
+        loadButtonsFromJson(mAssetLoader.getAssetHandle("json/menu_main_buttons.json"));
         if (GameSaver.exists(mMainConfig)) {
             getButtons().get("load_game").setType(Button.NORMAL);
         }
     }
+
+    public static class Factory {
+
+        private final MainConfig mMainConfig;
+        private final AssetLoader mAssetLoader;
+
+        @Inject
+        public Factory(MainConfig mainConfig, AssetLoader assetLoader) {
+            mMainConfig = mainConfig;
+            mAssetLoader = assetLoader;
+        }
+
+        public MenuMain get(float width, float height, ButtonRenderer buttonRenderer, MenuProc.Input menuInput) {
+            return new MenuMain(width, height, buttonRenderer, mMainConfig, menuInput, mAssetLoader);
+        }
+
+    }
+
 }
index 2c3b38bee9b041a5ae16fd4e2e5a4d1d707aacad..33985ff8206d395939c21c54d474e0017e26c5a3 100644 (file)
@@ -1,17 +1,23 @@
 package ru.deadsoftware.cavedroid.menu.submenus;
 
-import com.badlogic.gdx.Gdx;
 import ru.deadsoftware.cavedroid.MainConfig;
 import ru.deadsoftware.cavedroid.menu.MenuProc;
 import ru.deadsoftware.cavedroid.menu.objects.ButtonEventListener;
 import ru.deadsoftware.cavedroid.menu.objects.ButtonRenderer;
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader;
 
+import javax.inject.Inject;
 import java.util.HashMap;
 
 public class MenuNewGame extends Menu {
 
-    public MenuNewGame(float width, float height, ButtonRenderer buttonRenderer, MainConfig mainConfig, MenuProc.Input menuInput) {
-        super(width, height, buttonRenderer, mainConfig, menuInput);
+    public MenuNewGame(float width,
+                       float height,
+                       ButtonRenderer buttonRenderer,
+                       MainConfig mainConfig,
+                       MenuProc.Input menuInput,
+                       AssetLoader assetLoader) {
+        super(width, height, buttonRenderer, mainConfig, menuInput, assetLoader);
     }
 
     @Override
@@ -25,6 +31,23 @@ public class MenuNewGame extends Menu {
 
     @Override
     protected void initButtons() {
-        loadButtonsFromJson(Gdx.files.internal("json/menu_new_game_buttons.json"));
+        loadButtonsFromJson(mAssetLoader.getAssetHandle("json/menu_new_game_buttons.json"));
+    }
+
+    public static class Factory {
+
+        private final MainConfig mMainConfig;
+        private final AssetLoader mAssetLoader;
+
+        @Inject
+        public Factory(MainConfig mainConfig, AssetLoader assetLoader) {
+            mMainConfig = mainConfig;
+            mAssetLoader = assetLoader;
+        }
+
+        public MenuNewGame get(float width, float height, ButtonRenderer buttonRenderer, MenuProc.Input menuInput) {
+            return new MenuNewGame(width, height, buttonRenderer, mMainConfig, menuInput, mAssetLoader);
+        }
+
     }
 }
index 335e3c0d2239a450908f325c3b94dbaa38982143..b7fd03e132063167fe37cc0ee2d97ac5f2b6090e 100644 (file)
@@ -1,27 +1,57 @@
 package ru.deadsoftware.cavedroid.misc;
 
-import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.Input;
+import com.badlogic.gdx.files.FileHandle;
 import com.badlogic.gdx.graphics.Texture;
 import com.badlogic.gdx.graphics.g2d.BitmapFont;
 import com.badlogic.gdx.graphics.g2d.GlyphLayout;
 import com.badlogic.gdx.graphics.g2d.Sprite;
 import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.math.Rectangle;
 import com.badlogic.gdx.utils.ArrayMap;
 import com.badlogic.gdx.utils.JsonReader;
 import com.badlogic.gdx.utils.JsonValue;
 import ru.deadsoftware.cavedroid.game.objects.TouchButton;
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader;
 
+import java.io.File;
 import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
 
 public class Assets {
 
+    private static final int BLOCK_DAMAGE_STAGES = 10;
+
     public static final JsonReader jsonReader = new JsonReader();
+
+    private static final List<Texture> loadedTextures = new LinkedList<>();
+
     public static final Sprite[][] playerSprite = new Sprite[2][4];
     public static final Sprite[][] pigSprite = new Sprite[2][2];
+
+    public static final Sprite[] blockDamageSprites = new Sprite[10];
+
     public static final HashMap<String, TextureRegion> textureRegions = new HashMap<>();
     public static final ArrayMap<String, TouchButton> guiMap = new ArrayMap<>();
     private static final GlyphLayout glyphLayout = new GlyphLayout();
-    static BitmapFont minecraftFont;
+    public static BitmapFont minecraftFont;
+
+    public static Map<String, Texture> blockTextures = new HashMap<>();
+    public static Map<String, Texture> itemTextures = new HashMap<>();
+
+    public static void dispose() {
+        minecraftFont.dispose();
+        loadedTextures.forEach(Texture::dispose);
+        loadedTextures.clear();
+    }
+    
+    private static Texture loadTexture(FileHandle fileHandle) {
+        Texture texture = new Texture(fileHandle);
+        loadedTextures.add(texture);
+        return texture;
+    }
 
     private static TextureRegion flippedRegion(Texture texture, int x, int y, int width, int height) {
         return new TextureRegion(texture, x, y + height, width, -height);
@@ -39,24 +69,37 @@ public class Assets {
         return sprite;
     }
 
-    private static void loadMob(Sprite[][] sprite, String mob) {
+    private static void loadMob(AssetLoader assetLoader, Sprite[][] sprite, String mob) {
         for (int i = 0; i < sprite.length; i++) {
             for (int j = 0; j < sprite[i].length; j++) {
-                sprite[i][j] = flippedSprite(new Texture(
-                        Gdx.files.internal("mobs/" + mob + "/" + i + "_" + j + ".png")));
+                sprite[i][j] = flippedSprite(loadTexture(
+                        assetLoader.getAssetHandle("mobs/" + mob + "/" + i + "_" + j + ".png")));
                 sprite[i][j].setOrigin(sprite[i][j].getWidth() / 2, 0);
             }
         }
     }
 
+    private static void loadBlockDamage(AssetLoader assetLoader) {
+        final Texture blockDamageTexture = loadTexture(assetLoader.getAssetHandle("break.png"));
+        for (int i = 0; i < BLOCK_DAMAGE_STAGES; i++) {
+            blockDamageSprites[i] = new Sprite(flippedRegion(blockDamageTexture, i * 16, 0, 16, 16));
+        }
+    }
+
+    private static void setPlayerHeadOrigin() {
+        for (Sprite[] sprites : playerSprite) {
+            sprites[0].setOrigin(sprites[0].getWidth() / 2, sprites[0].getHeight());
+        }
+    }
+
     /**
      * Loads texture names and sizes from <b>json/texture_regions.json</b>, cuts them to TextureRegions
      * and puts to {@link #textureRegions} HashMap
      */
-    private static void loadJSON() {
-        JsonValue json = jsonReader.parse(Gdx.files.internal("json/texture_regions.json"));
+    private static void loadJSON(AssetLoader assetLoader) {
+        JsonValue json = jsonReader.parse(assetLoader.getAssetHandle("json/texture_regions.json"));
         for (JsonValue file = json.child(); file != null; file = file.next()) {
-            Texture texture = new Texture(Gdx.files.internal(file.name() + ".png"));
+            Texture texture = loadTexture(assetLoader.getAssetHandle(file.name() + ".png"));
             if (file.size == 0) {
                 textureRegions.put(file.name(),
                         flippedRegion(texture, 0, 0, texture.getWidth(), texture.getHeight()));
@@ -72,11 +115,89 @@ public class Assets {
         }
     }
 
-    public static void load() {
-        loadMob(playerSprite, "char");
-        loadMob(pigSprite, "pig");
-        loadJSON();
-        minecraftFont = new BitmapFont(Gdx.files.internal("font.fnt"), true);
+    private static int getMouseKey(String name) {
+        switch (name) {
+            case "Left":
+                return Input.Buttons.LEFT;
+            case "Right":
+                return Input.Buttons.RIGHT;
+            case "Middle":
+                return Input.Buttons.MIDDLE;
+            case "Back":
+                return Input.Buttons.BACK;
+            case "Forward":
+                return Input.Buttons.FORWARD;
+            default:
+                return -1;
+        }
+    }
+
+    private static void loadTouchButtonsFromJSON(AssetLoader assetLoader) {
+        JsonValue json = Assets.jsonReader.parse(assetLoader.getAssetHandle("json/touch_buttons.json"));
+        for (JsonValue key = json.child(); key != null; key = key.next()) {
+            float x = key.getFloat("x");
+            float y = key.getFloat("y");
+            float w = key.getFloat("w");
+            float h = key.getFloat("h");
+            boolean mouse = Assets.getBooleanFromJson(key, "mouse", false);
+            String name = key.getString("key");
+            int code = mouse ? getMouseKey(name) : Input.Keys.valueOf(name);
+            if (x < 0) {
+                x = assetLoader.getGameRendererWidth() + x;
+            }
+            if (y < 0) {
+                y = assetLoader.getGameRendererHeight() + y;
+            }
+            Assets.guiMap.put(key.name(), new TouchButton(new Rectangle(x, y, w, h), code, mouse));
+        }
+
+    }
+
+    private static Texture resolveTexture(AssetLoader assetLoader, String textureName, String lookUpPath, Map<String, Texture> cache) {
+        if (cache.containsKey(textureName)) {
+            return cache.get(textureName);
+        }
+
+        final Texture texture = loadTexture(assetLoader.getAssetHandle(lookUpPath + File.separator + textureName + ".png"));
+        cache.put(textureName, texture);
+
+        return texture;
+    }
+
+    public static Texture resolveItemTexture(AssetLoader assetLoader, String textureName) {
+        return resolveTexture(assetLoader, textureName, "textures/items", itemTextures);
+    }
+
+    public static Texture resolveBlockTexture(AssetLoader assetLoader, String textureName) {
+        return resolveTexture(assetLoader, textureName, "textures/blocks", blockTextures);
+    }
+
+    private static void loadAllPngsFromDirInto(FileHandle dir, Map<String, Texture> loadInto) {
+        for (FileHandle handle : dir.list((d, name) -> name.endsWith(".png"))) {
+            loadInto.put(handle.nameWithoutExtension(), loadTexture(handle));
+        }
+    }
+
+    private static void loadItems(AssetLoader assetLoader) {
+        final FileHandle itemsDir = assetLoader.getAssetHandle("textures/items");
+        loadAllPngsFromDirInto(itemsDir, itemTextures);
+    }
+
+    private static void loadBlocks(AssetLoader assetLoader) {
+        final FileHandle blocksDir = assetLoader.getAssetHandle("textures/blocks");
+        loadAllPngsFromDirInto(blocksDir, blockTextures);
+    }
+
+    public static void load(final AssetLoader assetLoader) {
+        loadMob(assetLoader, playerSprite, "char");
+        loadMob(assetLoader, pigSprite, "pig");
+        loadBlockDamage(assetLoader);
+        loadJSON(assetLoader);
+        loadBlocks(assetLoader);
+        loadItems(assetLoader);
+        loadTouchButtonsFromJSON(assetLoader);
+        setPlayerHeadOrigin();
+        minecraftFont = new BitmapFont(assetLoader.getAssetHandle("font.fnt"), true);
         minecraftFont.getData().setScale(.375f);
     }
 
@@ -102,6 +223,10 @@ public class Assets {
         return json.has(name) ? json.getInt(name) : defaultValue;
     }
 
+    public static float getFloatFromJson(JsonValue json, String name, float defaultValue) {
+        return json.has(name) ? json.getFloat(name) : defaultValue;
+    }
+
     public static String getStringFromJson(JsonValue json, String name, String defaultValue) {
         return json.has(name) ? json.getString(name) : defaultValue;
     }
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/ControlMode.java b/core/src/ru/deadsoftware/cavedroid/misc/ControlMode.java
deleted file mode 100644 (file)
index dab3ac3..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-package ru.deadsoftware.cavedroid.misc;
-
-public enum ControlMode {
-    WALK,
-    CURSOR
-}
index b59e977c5045c05195f72e870c2c3c0b82d69335..c2577d36e4940a996468b3dbb382369b19eb000d 100644 (file)
@@ -5,12 +5,14 @@ import com.badlogic.gdx.InputProcessor;
 import com.badlogic.gdx.graphics.OrthographicCamera;
 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.math.Rectangle;
 
 public abstract class Renderer implements InputProcessor {
 
     protected final ShapeRenderer shaper;
     protected final SpriteBatch spriter;
     private final OrthographicCamera camera;
+    private final Rectangle mCameraViewport;
 
     protected Renderer() {
         this(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
@@ -23,6 +25,9 @@ public abstract class Renderer implements InputProcessor {
         shaper.setProjectionMatrix(camera.combined);
         spriter = new SpriteBatch();
         spriter.setProjectionMatrix(camera.combined);
+
+        mCameraViewport =
+                new Rectangle(camera.position.x, camera.position.y, camera.viewportWidth, camera.viewportHeight);
     }
 
     public float getWidth() {
@@ -43,6 +48,12 @@ public abstract class Renderer implements InputProcessor {
 
     public void setCamPos(float x, float y) {
         camera.position.set(x, y, 0);
+        mCameraViewport.x = x;
+        mCameraViewport.y = y;
+    }
+
+    public Rectangle getCameraViewport() {
+        return mCameraViewport;
     }
 
     public void setFontScale(float scale) {
@@ -101,8 +112,12 @@ public abstract class Renderer implements InputProcessor {
     }
 
     @Override
-    public boolean scrolled(int amount) {
+    public boolean scrolled(float amountX, float amountY) {
         return false;
     }
 
+    @Override
+    public boolean touchCancelled(int i, int i1, int i2, int i3) {
+        return false;
+    }
 }
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/ArrayMapExtensions.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/ArrayMapExtensions.kt
new file mode 100644 (file)
index 0000000..70a5dc6
--- /dev/null
@@ -0,0 +1,9 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.utils.ObjectMap
+
+object ArrayMapExtensions {
+    operator fun <K, V> ObjectMap.Entry<K, V>.component1(): K = this.key
+
+    operator fun <K, V> ObjectMap.Entry<K, V>.component2(): V = this.value
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt
new file mode 100644 (file)
index 0000000..dff4d90
--- /dev/null
@@ -0,0 +1,35 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.files.FileHandle
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import java.io.File
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class AssetLoader @Inject constructor(
+    private val mainConfig: MainConfig,
+) {
+
+    fun getAssetHandle(path: String): FileHandle {
+        val texturePackPath =
+            mainConfig.assetsPackPath?.let { if (!it.endsWith(File.separator)) "$it${File.separator}" else it }
+
+        return if (texturePackPath == null) {
+            Gdx.files.internal(path)
+        } else {
+            Gdx.files.absolute("$texturePackPath$path")
+        }
+    }
+
+    fun getGameRendererWidth(): Float {
+        return mainConfig.width
+    }
+
+    fun getGameRendererHeight(): Float {
+        return mainConfig.height
+    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/ItemUtils.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/ItemUtils.kt
new file mode 100644 (file)
index 0000000..8e1f730
--- /dev/null
@@ -0,0 +1,8 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder.Companion.FALLBACK_ITEM_KEY
+import ru.deadsoftware.cavedroid.game.model.item.Item
+
+fun Item.isFallback(): Boolean {
+    return this.params.key == FALLBACK_ITEM_KEY
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt
new file mode 100644 (file)
index 0000000..ecf1ce0
--- /dev/null
@@ -0,0 +1,18 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.math.MathUtils
+
+/**
+ * Converts this value in BLOCKS into pixels
+ */
+val Float.px get() = this * 16f
+
+/**
+ * Converts this value in BLOCKS into pixels
+ */
+val Int.px get() = this * 16f
+
+/**
+ * Converts this value in PIXELS into blocks
+ */
+val Float.bl get() = MathUtils.floor(this / 16)
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt
new file mode 100644 (file)
index 0000000..8976c99
--- /dev/null
@@ -0,0 +1,71 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.graphics.Color
+import com.badlogic.gdx.graphics.g2d.GlyphLayout
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.misc.Assets
+
+private fun Rectangle.shifted(shift: Float) = Rectangle(x + shift, y, width, height)
+
+private fun Rectangle.getLazyShifts(worldWidthPx: Float)
+    = Triple(
+        first = lazy { shifted(0f) },
+        second = lazy { shifted(-worldWidthPx) },
+        third = lazy { shifted(worldWidthPx) }
+    )
+
+fun Rectangle.cycledInsideWorld(
+    viewport: Rectangle,
+    worldWidthPx: Float,
+): Rectangle? {
+    val (notShifted, shiftedLeft, shiftedRight) = getLazyShifts(worldWidthPx)
+
+    return when {
+        viewport.overlaps(notShifted.value) -> notShifted.value
+        viewport.overlaps(shiftedLeft.value) -> shiftedLeft.value
+        viewport.overlaps(shiftedRight.value) -> shiftedRight.value
+        else -> null
+    }
+}
+
+fun forEachBlockInArea(
+    area: Rectangle,
+    func: (x: Int, y: Int) -> Unit,
+) {
+    val startMapX = area.x.bl
+    val endMapX = (area.x + area.width - 1f).bl
+    val startMapY = area.y.bl
+    val endMapY = (area.y + area.height - 1f).bl
+
+    for (x in startMapX..endMapX) {
+        for (y in startMapY..endMapY) {
+            func(x, y)
+        }
+    }
+}
+
+@JvmOverloads
+fun SpriteBatch.drawString(str: String, x: Float, y: Float, color: Color = Color.WHITE): GlyphLayout {
+    Assets.minecraftFont.color = color
+    return Assets.minecraftFont.draw(this, str, x, y)
+}
+
+/**
+ * Parses hex color string into [Color]
+ * Format is strictly #FFFFFF
+ */
+fun colorFromHexString(hex: String): Color {
+    if (hex[0] != '#' || hex.length != 7) {
+        return Color.WHITE
+    }
+
+    var rgba = try {
+        hex.substring(1).toInt(16)
+    } catch (e: NumberFormatException) {
+        0xffffff
+    }
+
+    rgba = (rgba shl 8) or 0xFF
+    return Color(rgba)
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteOrigin.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteOrigin.kt
new file mode 100644 (file)
index 0000000..d465ce1
--- /dev/null
@@ -0,0 +1,31 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.graphics.g2d.Sprite
+
+/**
+ * An origin of a [com.badlogic.gdx.graphics.g2d.Sprite]
+ *
+ * x and y must be between 0 and 1 in percents from sprite size
+ */
+data class SpriteOrigin(
+    val x: Float,
+    val y: Float,
+) {
+
+    init {
+        assert(x in 0f..1f)
+        assert(y in 0f..1f)
+    }
+
+    fun getFlipped(flipX: Boolean, flipY: Boolean): SpriteOrigin {
+        return SpriteOrigin(
+            x = if (flipX) 1 - x else x,
+            y = if (flipY) 1 - y else y,
+        )
+    }
+
+    fun applyToSprite(sprite: Sprite) {
+        sprite.setOrigin(sprite.width * x, sprite.height * y)
+    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteUtils.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteUtils.kt
new file mode 100644 (file)
index 0000000..7b75e38
--- /dev/null
@@ -0,0 +1,29 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.graphics.g2d.Sprite
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+
+/**
+ * Draw sprite at given position rotated by [rotation] degrees
+ */
+@JvmOverloads
+fun SpriteBatch.drawSprite(
+    sprite: Sprite,
+    x: Float,
+    y: Float,
+    rotation: Float = 0f,
+    width: Float = sprite.regionWidth.toFloat(),
+    height: Float = sprite.regionHeight.toFloat(),
+) {
+    sprite.setPosition(x, y)
+    sprite.setSize(width, height)
+    sprite.rotation = rotation
+    sprite.draw(this)
+
+    sprite.setSize(sprite.regionWidth.toFloat(), sprite.regionHeight.toFloat())
+    sprite.rotation = 0f
+}
+
+fun Sprite.applyOrigin(origin: SpriteOrigin) {
+    origin.applyToSprite(this)
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/mobs/MobSprites.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/mobs/MobSprites.kt
new file mode 100644 (file)
index 0000000..3b5eb4c
--- /dev/null
@@ -0,0 +1,47 @@
+package ru.deadsoftware.cavedroid.misc.utils.mobs
+
+import ru.deadsoftware.cavedroid.game.mobs.Mob
+import ru.deadsoftware.cavedroid.game.mobs.Mob.Direction
+import ru.deadsoftware.cavedroid.misc.Assets
+
+object MobSprites {
+
+    object Player {
+
+        fun getBackgroundHand() = Assets.playerSprite[1][2]
+
+        fun getForegroundHand() = Assets.playerSprite[0][2]
+
+        fun getBackgroundLeg() = Assets.playerSprite[1][3]
+
+        fun getForegroundLeg() = Assets.playerSprite[0][3]
+
+        fun getHead(direction: Mob.Direction) = Assets.playerSprite[direction.index][0]
+
+        fun getBody(direction: Direction) = Assets.playerSprite[direction.index][1]
+
+        fun getBodyRelativeX() = 2
+
+        fun getBodyRelativeY() = 8
+
+        fun getLegsRelativeY() = 20
+
+    }
+
+    object Pig {
+
+        fun getForegroundLeg() = Assets.pigSprite[0][1]
+
+        fun getBackgroundLeg() = Assets.pigSprite[1][1]
+
+        fun getBody(direction: Direction) = Assets.pigSprite[direction.index][0]
+
+        fun getLeftLegRelativeX(direction: Direction) = 9 - direction.index * 9
+
+        fun getRightLegRelativeX(direction: Direction) = 21 - (9 * direction.index)
+
+        fun getLegsRelativeY() = 12
+
+    }
+
+}
\ No newline at end of file
index b3a6693f285b98a686c56c8db34af36336b6be2c..70a63ba56bc3d5f3207941b4c50ac64eab09ee3e 100644 (file)
@@ -1,6 +1,9 @@
-apply plugin: "java"
+plugins {
+    id 'kotlin'
+    id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlinVersion"
+}
 
-sourceCompatibility = 1.8
+sourceCompatibility = 17
 sourceSets.main.java.srcDirs = [ "src/" ]
 sourceSets.main.resources.srcDirs = ["../android/assets"]
 
@@ -13,6 +16,7 @@ task run(dependsOn: classes, type: JavaExec) {
     standardInput = System.in
     workingDir = project.assetsDir
     ignoreExitValue = true as JavaExecSpec
+    args "--debug"
 }
 
 task runTouch(dependsOn: classes, type: JavaExec) {
@@ -21,7 +25,7 @@ task runTouch(dependsOn: classes, type: JavaExec) {
     standardInput = System.in
     workingDir = project.assetsDir
     ignoreExitValue = true as JavaExecSpec
-    args "--touch"
+    args "--touch", "--debug"
 }
 
 task debug(dependsOn: classes, type: JavaExec) {
@@ -34,6 +38,7 @@ task debug(dependsOn: classes, type: JavaExec) {
 }
 
 task dist(type: Jar) {
+    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
     manifest {
         attributes 'Main-Class': project.mainClassName
     }
diff --git a/desktop/gradle/wrapper/gradle-wrapper.properties b/desktop/gradle/wrapper/gradle-wrapper.properties
new file mode 100644 (file)
index 0000000..d884b3f
--- /dev/null
@@ -0,0 +1,6 @@
+#Sat Apr 13 17:22:59 NOVT 2024
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
index 2e9ae424749693629aa5adfa9acd0226b50b754f..fe78235dd8fe5bc0ec937561b82ae1a9b4f6e87e 100644 (file)
@@ -1,25 +1,42 @@
 package ru.deadsoftware.cavedroid.desktop;
 
 import com.badlogic.gdx.Files;
-import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
-import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
+import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
+import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
 import ru.deadsoftware.cavedroid.CaveGame;
 
 class DesktopLauncher {
-       public static void main (String[] arg) {
-               LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
-               config.addIcon("icons/icon256.png", Files.FileType.Internal);
-               config.addIcon("icons/icon128.png", Files.FileType.Internal);
-               config.foregroundFPS = 144;
-        config.title = "CaveDroid";
-               config.width = 960;
-               config.height = 540;
-               config.forceExit = false;
-
-               boolean touch = false;
-               for (String anArg : arg) {
-                       if (anArg.equals("--touch")) touch = true;
-               }
-        new LwjglApplication(new CaveGame(System.getProperty("user.home") + "/.cavedroid", touch), config);
-       }
+    public static void main(String[] arg) {
+        Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration();
+        config.setWindowIcon(Files.FileType.Internal, "icons/icon256.png", "icons/icon128.png");
+        config.setTitle("CaveDroid");
+        config.setWindowedMode(960, 540);
+        config.useVsync(true);
+
+        boolean touch = false;
+        boolean debug = false;
+        String assetsPath = null;
+
+        for (String anArg : arg) {
+            if (anArg.equals("--touch")) {
+                touch = true;
+            }
+
+            if (anArg.equals("--debug")) {
+                debug = true;
+            }
+
+            if (anArg.startsWith("--assets")) {
+                String[] splitArg = anArg.split("=");
+                if (splitArg.length >= 2) {
+                    assetsPath = splitArg[1];
+                }
+            }
+        }
+
+        CaveGame caveGame = new CaveGame(System.getProperty("user.home") + "/.cavedroid", touch, assetsPath);
+        caveGame.setDebug(debug);
+
+        new Lwjgl3Application(caveGame, config);
+    }
 }
diff --git a/gen-changelog.sh b/gen-changelog.sh
new file mode 100755 (executable)
index 0000000..397e28d
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+# Waku-2 CC BY-SA 3.0
+# https://stackoverflow.com/a/46033999
+# https://creativecommons.org/licenses/by-sa/3.0/
+
+previous_tag=0
+i=0
+
+echo '<!--#include virtual="/includes/pre_header.shtml" -->'
+
+for current_tag in $(git tag --sort=-creatordate)
+do
+
+if [ "$previous_tag" != 0 ] &&  [ $i -lt 1 ]; then
+    i=$(echo "$i + 1" | bc -q )
+    tag_date=$(git log -1 --pretty=format:'%ad' --date=short ${previous_tag})
+    printf "## ${previous_tag} (${tag_date})\n\n"
+    git log ${current_tag}...${previous_tag} --pretty=format:'*  %s ' --reverse | grep -v Merge
+    printf "\n\n"
+fi
+previous_tag=${current_tag}
+done
+
+echo '<!--#include virtual="/includes/pre_footer.shtml" -->'
index 339fa1508b04d641faacd40a6958049724bbdf76..b7f66d430f6f4d25e3db7900a59a42c0962076ff 100644 (file)
@@ -1,3 +1,4 @@
 org.gradle.daemon=true
 org.gradle.jvmargs=-Xms128m -Xmx1500m
 org.gradle.configureondemand=true
+android.useAndroidX=true
index 374585553662a55219dc2f375458e4cb4db35db0..b3497272b316d215d0731d4299d41673c6d1621f 100644 (file)
@@ -1,6 +1,6 @@
 #Thu Sep 26 22:30:23 NOVT 2019
 distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
 distributionPath=wrapper/dists
 zipStorePath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index 4453ccea33d960069d9137ee65f6b21fc65e7e92..54f115a3e6a1a49ab69cc9f25cf875b24ea352da 100755 (executable)
--- a/gradlew
+++ b/gradlew
@@ -1,5 +1,22 @@
 #!/usr/bin/env sh
 
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
 ##############################################################################
 ##
 ##  Gradle start up script for UN*X
index f9553162f122c71b34635112e717c3e733b5b212..c42c57956b0c0329894230ee9aee43a6153439af 100644 (file)
@@ -1,3 +1,19 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
 @if "%DEBUG%" == "" @echo off
 @rem ##########################################################################
 @rem
diff --git a/make-release.sh b/make-release.sh
new file mode 100755 (executable)
index 0000000..5e3f71a
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+if [[ ! $1 ]]; then
+  echo "usage: $0 versionName"
+  exit
+fi
+
+./require_clean_work_tree "$0"
+
+release_dir="release-$1"
+
+mkdir "$release_dir"
+
+./up-version.sh "$1"
+./gen-changelog.sh > "$release_dir/CHANGELOG"
+
+./gradlew clean android:assembleRelease desktop:dist
+
+cp android/build/outputs/apk/release/*.apk "$release_dir/"
+cp desktop/build/libs/*.jar "$release_dir/"
+
+echo "$release_dir/"
diff --git a/require-celan-work-tree.sh b/require-celan-work-tree.sh
new file mode 100644 (file)
index 0000000..cdd7e75
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+# VonC CC BY-SA 4.0
+# https://stackoverflow.com/a/3879077
+# https://creativecommons.org/licenses/by-sa/4.0/
+
+    # Update the index
+    git update-index -q --ignore-submodules --refresh
+    err=0
+
+    # Disallow unstaged changes in the working tree
+    if ! git diff-files --quiet --ignore-submodules --
+    then
+        echo >&2 "cannot $1: you have unstaged changes."
+        git diff-files --name-status -r --ignore-submodules -- >&2
+        err=1
+    fi
+
+    # Disallow uncommitted changes in the index
+    if ! git diff-index --cached --quiet HEAD --ignore-submodules --
+    then
+        echo >&2 "cannot $1: your index contains uncommitted changes."
+        git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2
+        err=1
+    fi
+
+    if [ $err = 1 ]
+    then
+        echo >&2 "Please commit or stash them."
+        exit 1
+    fi
diff --git a/up-version.sh b/up-version.sh
new file mode 100755 (executable)
index 0000000..7637355
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+new_version=$1
+
+new_version_string=$(echo $new_version | sed 's/\(alpha\|beta\)\(.*\)/\1 \2/')
+
+sed -i 's/\(version\s=\s\)'"'"'.*'"'"'/\1'"'"''"$new_version"''"'"'/g' build.gradle
+sed -i 's/\(versionName\s\)\".*\"/\1\"'"$new_version"'\"/g' android/build.gradle
+sed -i 's/\(^\s*versionCode\s\)\([0-9]*\)/echo "\1$((\2+1))"/ge' android/build.gradle
+sed -i 's/\(public static final String VERSION = \)\".*\"/\1\"'"$new_version_string"'\"/' core/src/ru/deadsoftware/cavedroid/CaveGame.java
+
+git add build.gradle android/build.gradle core/src/ru/deadsoftware/cavedroid/CaveGame.java
+
+git commit -m "Update version"
+git tag "$new_version"