DEADSOFTWARE

Update version script master
authorfredboy <fredboy@protonmail.com>
Sat, 25 May 2024 09:31:39 +0000 (16:31 +0700)
committerfredboy <fredboy@protonmail.com>
Sat, 25 May 2024 09:41:54 +0000 (16:41 +0700)
454 files changed:
.github/workflows/android.yml [new file with mode: 0644]
.gitignore
COPYING
README.md
android/assets/icons/icon128.png [deleted file]
android/assets/icons/icon256.png [deleted file]
android/assets/json/game_items.json [deleted file]
android/assets/json/touch_buttons.json [deleted file]
android/assets/textures/blocks/furnace_off.png [deleted file]
android/assets/textures/blocks/furnace_on.png [deleted file]
android/assets/textures/blocks/lapis_ore.png [deleted file]
android/assets/textures/blocks/leaves_birch.png [deleted file]
android/assets/textures/blocks/leaves_oak.png [deleted file]
android/assets/textures/blocks/leaves_spruce.png [deleted file]
android/assets/touch_gui.png [deleted file]
android/build.gradle [deleted file]
android/build.gradle.kts [new file with mode: 0644]
android/debug/res/drawable-anydpi-v26/ic_launcher_foreground.xml [deleted file]
android/debug/res/drawable-hdpi/ic_launcher.png [deleted file]
android/debug/res/drawable-mdpi/ic_launcher.png [deleted file]
android/debug/res/drawable-xhdpi/ic_launcher.png [deleted file]
android/debug/res/drawable-xxhdpi/ic_launcher.png [deleted file]
android/debug/res/drawable-xxxhdpi/ic_launcher.png [deleted file]
android/ic_launcher-web.png
android/project.properties [deleted file]
android/res/drawable-anydpi-v26/ic_launcher_background.xml [deleted file]
android/res/drawable-anydpi-v26/ic_launcher_foreground.xml [deleted file]
android/res/drawable-hdpi/ic_launcher.png [deleted file]
android/res/drawable-mdpi/ic_launcher.png [deleted file]
android/res/drawable-xhdpi/ic_launcher.png [deleted file]
android/res/drawable-xxhdpi/ic_launcher.png [deleted file]
android/res/drawable-xxxhdpi/ic_launcher.png [deleted file]
android/src/debug/res/drawable-hdpi/ic_launcher.png [new file with mode: 0644]
android/src/debug/res/drawable-mdpi/ic_launcher.png [new file with mode: 0644]
android/src/debug/res/drawable-xhdpi/ic_launcher.png [new file with mode: 0644]
android/src/debug/res/drawable-xxhdpi/ic_launcher.png [new file with mode: 0644]
android/src/debug/res/drawable-xxxhdpi/ic_launcher.png [new file with mode: 0644]
android/src/debug/res/drawable/ic_launcher_foreground.xml [new file with mode: 0644]
android/src/debug/res/values/strings.xml [moved from android/debug/res/values/strings.xml with 100% similarity]
android/src/main/AndroidManifest.xml [moved from android/AndroidManifest.xml with 91% similarity]
android/src/main/assets [new symlink]
android/src/main/kotlin/ru/deadsoftware/cavedroid/AndroidLauncher.kt [new file with mode: 0644]
android/src/main/kotlin/ru/deadsoftware/cavedroid/AndroidPreferencesStore.kt [new file with mode: 0644]
android/src/main/res/drawable-anydpi-v26/ic_launcher.xml [moved from android/res/drawable-anydpi-v26/ic_launcher.xml with 100% similarity]
android/src/main/res/drawable-hdpi/ic_launcher.png [new file with mode: 0644]
android/src/main/res/drawable-mdpi/ic_launcher.png [new file with mode: 0644]
android/src/main/res/drawable-xhdpi/ic_launcher.png [new file with mode: 0644]
android/src/main/res/drawable-xxhdpi/ic_launcher.png [new file with mode: 0644]
android/src/main/res/drawable-xxxhdpi/ic_launcher.png [new file with mode: 0644]
android/src/main/res/drawable/ic_launcher_background.xml [new file with mode: 0644]
android/src/main/res/drawable/ic_launcher_foreground.xml [new file with mode: 0644]
android/src/main/res/values/strings.xml [moved from android/res/values/strings.xml with 100% similarity]
android/src/main/res/values/styles.xml [moved from android/res/values/styles.xml with 100% similarity]
android/src/main/res/xml/backup_descriptor.xml [moved from android/res/xml/backup_descriptor.xml with 100% similarity]
android/src/ru/deadsoftware/cavedroid/AndroidLauncher.java [deleted file]
assets/font.fnt [moved from android/assets/font.fnt with 100% similarity]
assets/font.png [moved from android/assets/font.png with 100% similarity]
assets/gamelogo.png [moved from android/assets/gamelogo.png with 100% similarity]
assets/icons/icon128.png [new file with mode: 0644]
assets/icons/icon256.png [new file with mode: 0644]
assets/icons/icon512.png [new file with mode: 0644]
assets/joy_background.png [new file with mode: 0644]
assets/joy_stick.png [new file with mode: 0644]
assets/json/crafting.json [new file with mode: 0644]
assets/json/game_items.json [new file with mode: 0644]
assets/json/menu_main_buttons.json [moved from android/assets/json/menu_main_buttons.json with 76% similarity]
assets/json/menu_new_game_buttons.json [moved from android/assets/json/menu_new_game_buttons.json with 72% similarity]
assets/json/menu_options_buttons.json [new file with mode: 0644]
assets/json/texture_regions.json [moved from android/assets/json/texture_regions.json with 56% similarity]
assets/json/touch_buttons.json [new file with mode: 0644]
assets/pp/allitems.png [moved from android/assets/allitems.png with 100% similarity]
assets/pp/background.png [moved from android/assets/background.png with 100% similarity]
assets/pp/background_top.png [moved from android/assets/background_top.png with 100% similarity]
assets/pp/break.png [moved from android/assets/break.png with 100% similarity]
assets/pp/buttons.png [moved from android/assets/buttons.png with 100% similarity]
assets/pp/chest.png [new file with mode: 0644]
assets/pp/chest_large.png [new file with mode: 0644]
assets/pp/crafting_table.png [new file with mode: 0644]
assets/pp/furnace.png [new file with mode: 0644]
assets/pp/gui.png [moved from android/assets/gui.png with 100% similarity]
assets/pp/health.png [new file with mode: 0644]
assets/pp/inventory.png [new file with mode: 0644]
assets/pp/mobs/char/0_0.png [moved from android/assets/mobs/char/0_0.png with 100% similarity]
assets/pp/mobs/char/0_1.png [moved from android/assets/mobs/char/0_1.png with 100% similarity]
assets/pp/mobs/char/0_2.png [moved from android/assets/mobs/char/0_2.png with 100% similarity]
assets/pp/mobs/char/0_3.png [moved from android/assets/mobs/char/0_3.png with 100% similarity]
assets/pp/mobs/char/1_0.png [moved from android/assets/mobs/char/1_0.png with 100% similarity]
assets/pp/mobs/char/1_1.png [moved from android/assets/mobs/char/1_1.png with 100% similarity]
assets/pp/mobs/char/1_2.png [moved from android/assets/mobs/char/1_2.png with 100% similarity]
assets/pp/mobs/char/1_3.png [moved from android/assets/mobs/char/1_3.png with 100% similarity]
assets/pp/mobs/pig/0_0.png [moved from android/assets/mobs/pig/0_0.png with 100% similarity]
assets/pp/mobs/pig/0_1.png [moved from android/assets/mobs/pig/0_1.png with 100% similarity]
assets/pp/mobs/pig/1_0.png [moved from android/assets/mobs/pig/1_0.png with 100% similarity]
assets/pp/mobs/pig/1_1.png [moved from android/assets/mobs/pig/1_1.png with 100% similarity]
assets/pp/shade.png [moved from android/assets/shade.png with 100% similarity]
assets/pp/textures/blocks/bed_l.png [moved from android/assets/textures/blocks/bed_l.png with 100% similarity]
assets/pp/textures/blocks/bed_r.png [moved from android/assets/textures/blocks/bed_r.png with 100% similarity]
assets/pp/textures/blocks/bedrock.png [moved from android/assets/textures/blocks/bedrock.png with 100% similarity]
assets/pp/textures/blocks/bookshelf.png [moved from android/assets/textures/blocks/bookshelf.png with 100% similarity]
assets/pp/textures/blocks/bricks.png [moved from android/assets/textures/blocks/bricks.png with 100% similarity]
assets/pp/textures/blocks/cactus.png [moved from android/assets/textures/blocks/cactus.png with 100% similarity]
assets/pp/textures/blocks/cake.png [moved from android/assets/textures/blocks/cake.png with 100% similarity]
assets/pp/textures/blocks/chest.png [new file with mode: 0644]
assets/pp/textures/blocks/clay.png [moved from android/assets/textures/blocks/clay.png with 100% similarity]
assets/pp/textures/blocks/coal_block.png [moved from android/assets/textures/blocks/coal_block.png with 100% similarity]
assets/pp/textures/blocks/coal_ore.png [moved from android/assets/textures/blocks/coal_ore.png with 100% similarity]
assets/pp/textures/blocks/cobblestone.png [moved from android/assets/textures/blocks/cobblestone.png with 100% similarity]
assets/pp/textures/blocks/cobblestone_mossy.png [moved from android/assets/textures/blocks/cobblestone_mossy.png with 100% similarity]
assets/pp/textures/blocks/crafting_table.png [moved from android/assets/textures/blocks/crafting_table.png with 100% similarity]
assets/pp/textures/blocks/dandelion.png [moved from android/assets/textures/blocks/dandelion.png with 100% similarity]
assets/pp/textures/blocks/deadbush.png [moved from android/assets/textures/blocks/deadbush.png with 100% similarity]
assets/pp/textures/blocks/diamond_block.png [moved from android/assets/textures/blocks/diamond_block.png with 100% similarity]
assets/pp/textures/blocks/diamond_ore.png [moved from android/assets/textures/blocks/diamond_ore.png with 100% similarity]
assets/pp/textures/blocks/dirt.png [moved from android/assets/textures/blocks/dirt.png with 100% similarity]
assets/pp/textures/blocks/furnace.png [new file with mode: 0644]
assets/pp/textures/blocks/glass.png [moved from android/assets/textures/blocks/glass.png with 100% similarity]
assets/pp/textures/blocks/gold_block.png [moved from android/assets/textures/blocks/gold_block.png with 100% similarity]
assets/pp/textures/blocks/gold_ore.png [moved from android/assets/textures/blocks/gold_ore.png with 100% similarity]
assets/pp/textures/blocks/grass.png [moved from android/assets/textures/blocks/grass.png with 100% similarity]
assets/pp/textures/blocks/grass_snowed.png [new file with mode: 0644]
assets/pp/textures/blocks/gravel.png [moved from android/assets/textures/blocks/gravel.png with 100% similarity]
assets/pp/textures/blocks/iron_bars.png [moved from android/assets/textures/blocks/iron_bars.png with 100% similarity]
assets/pp/textures/blocks/iron_block.png [moved from android/assets/textures/blocks/iron_block.png with 100% similarity]
assets/pp/textures/blocks/iron_ore.png [moved from android/assets/textures/blocks/iron_ore.png with 100% similarity]
assets/pp/textures/blocks/ladder.png [moved from android/assets/textures/blocks/ladder.png with 100% similarity]
assets/pp/textures/blocks/lapis_block.png [moved from android/assets/textures/blocks/lapis_block.png with 100% similarity]
assets/pp/textures/blocks/lapis_ore.png [new file with mode: 0644]
assets/pp/textures/blocks/lava_flow.png [moved from android/assets/textures/blocks/lava_flow.png with 100% similarity]
assets/pp/textures/blocks/lava_still.png [moved from android/assets/textures/blocks/lava_still.png with 100% similarity]
assets/pp/textures/blocks/leaves_oak.png [new file with mode: 0644]
assets/pp/textures/blocks/leaves_spruce.png [new file with mode: 0644]
assets/pp/textures/blocks/log_birch.png [moved from android/assets/textures/blocks/log_birch.png with 100% similarity]
assets/pp/textures/blocks/log_oak.png [moved from android/assets/textures/blocks/log_oak.png with 100% similarity]
assets/pp/textures/blocks/log_spruce.png [moved from android/assets/textures/blocks/log_spruce.png with 100% similarity]
assets/pp/textures/blocks/mushroom_brown.png [moved from android/assets/textures/blocks/mushroom_brown.png with 100% similarity]
assets/pp/textures/blocks/mushroom_red.png [moved from android/assets/textures/blocks/mushroom_red.png with 100% similarity]
assets/pp/textures/blocks/noteblock.png [moved from android/assets/textures/blocks/noteblock.png with 100% similarity]
assets/pp/textures/blocks/obsidian.png [moved from android/assets/textures/blocks/obsidian.png with 100% similarity]
assets/pp/textures/blocks/planks_birch.png [moved from android/assets/textures/blocks/planks_birch.png with 100% similarity]
assets/pp/textures/blocks/planks_oak.png [moved from android/assets/textures/blocks/planks_oak.png with 100% similarity]
assets/pp/textures/blocks/planks_spruce.png [moved from android/assets/textures/blocks/planks_spruce.png with 100% similarity]
assets/pp/textures/blocks/rose.png [moved from android/assets/textures/blocks/rose.png with 100% similarity]
assets/pp/textures/blocks/sand.png [moved from android/assets/textures/blocks/sand.png with 100% similarity]
assets/pp/textures/blocks/sandstone.png [moved from android/assets/textures/blocks/sandstone.png with 100% similarity]
assets/pp/textures/blocks/sapling_birch.png [moved from android/assets/textures/blocks/sapling_birch.png with 100% similarity]
assets/pp/textures/blocks/sapling_oak.png [moved from android/assets/textures/blocks/sapling_oak.png with 100% similarity]
assets/pp/textures/blocks/sapling_spruce.png [moved from android/assets/textures/blocks/sapling_spruce.png with 100% similarity]
assets/pp/textures/blocks/snow.png [new file with mode: 0644]
assets/pp/textures/blocks/sponge.png [moved from android/assets/textures/blocks/sponge.png with 100% similarity]
assets/pp/textures/blocks/sponge_wet.png [moved from android/assets/textures/blocks/sponge_wet.png with 100% similarity]
assets/pp/textures/blocks/stone.png [moved from android/assets/textures/blocks/stone.png with 100% similarity]
assets/pp/textures/blocks/stone_slab.png [moved from android/assets/textures/blocks/stone_slab.png with 100% similarity]
assets/pp/textures/blocks/stonebrick.png [moved from android/assets/textures/blocks/stonebrick.png with 100% similarity]
assets/pp/textures/blocks/tallgrass.png [moved from android/assets/textures/blocks/tallgrass.png with 100% similarity]
assets/pp/textures/blocks/water_flow.png [moved from android/assets/textures/blocks/water_flow.png with 100% similarity]
assets/pp/textures/blocks/water_still.png [moved from android/assets/textures/blocks/water_still.png with 100% similarity]
assets/pp/textures/blocks/web.png [moved from android/assets/textures/blocks/web.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_black.png [moved from android/assets/textures/blocks/wool_colored_black.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_blue.png [moved from android/assets/textures/blocks/wool_colored_blue.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_brown.png [moved from android/assets/textures/blocks/wool_colored_brown.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_cyan.png [moved from android/assets/textures/blocks/wool_colored_cyan.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_gray.png [moved from android/assets/textures/blocks/wool_colored_gray.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_green.png [moved from android/assets/textures/blocks/wool_colored_green.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_light_blue.png [moved from android/assets/textures/blocks/wool_colored_light_blue.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_lime.png [moved from android/assets/textures/blocks/wool_colored_lime.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_magenta.png [moved from android/assets/textures/blocks/wool_colored_magenta.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_orange.png [moved from android/assets/textures/blocks/wool_colored_orange.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_pink.png [moved from android/assets/textures/blocks/wool_colored_pink.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_purple.png [moved from android/assets/textures/blocks/wool_colored_purple.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_red.png [moved from android/assets/textures/blocks/wool_colored_red.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_silver.png [moved from android/assets/textures/blocks/wool_colored_silver.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_white.png [moved from android/assets/textures/blocks/wool_colored_white.png with 100% similarity]
assets/pp/textures/blocks/wool_colored_yellow.png [moved from android/assets/textures/blocks/wool_colored_yellow.png with 100% similarity]
assets/pp/textures/items/bed.png [new file with mode: 0644]
assets/pp/textures/items/bucket_empty.png [moved from android/assets/textures/items/bucket_empty.png with 100% similarity]
assets/pp/textures/items/bucket_lava.png [moved from android/assets/textures/items/bucket_lava.png with 100% similarity]
assets/pp/textures/items/bucket_milk.png [moved from android/assets/textures/items/bucket_milk.png with 100% similarity]
assets/pp/textures/items/bucket_water.png [moved from android/assets/textures/items/bucket_water.png with 100% similarity]
assets/pp/textures/items/charcoal.png [new file with mode: 0644]
assets/pp/textures/items/coal.png [new file with mode: 0644]
assets/pp/textures/items/diamond.png [new file with mode: 0644]
assets/pp/textures/items/diamond_axe.png [new file with mode: 0644]
assets/pp/textures/items/diamond_hoe.png [new file with mode: 0644]
assets/pp/textures/items/diamond_pickaxe.png [new file with mode: 0644]
assets/pp/textures/items/diamond_shovel.png [moved from android/assets/textures/items/diamond_shovel.png with 100% similarity]
assets/pp/textures/items/diamond_sword.png [moved from android/assets/textures/items/diamond_sword.png with 100% similarity]
assets/pp/textures/items/gold_axe.png [new file with mode: 0644]
assets/pp/textures/items/gold_hoe.png [new file with mode: 0644]
assets/pp/textures/items/gold_ingot.png [new file with mode: 0644]
assets/pp/textures/items/gold_pickaxe.png [new file with mode: 0644]
assets/pp/textures/items/gold_shovel.png [moved from android/assets/textures/items/gold_shovel.png with 100% similarity]
assets/pp/textures/items/gold_sword.png [moved from android/assets/textures/items/gold_sword.png with 100% similarity]
assets/pp/textures/items/iron_axe.png [new file with mode: 0644]
assets/pp/textures/items/iron_hoe.png [new file with mode: 0644]
assets/pp/textures/items/iron_ingot.png [new file with mode: 0644]
assets/pp/textures/items/iron_pickaxe.png [new file with mode: 0644]
assets/pp/textures/items/iron_shovel.png [moved from android/assets/textures/items/iron_shovel.png with 100% similarity]
assets/pp/textures/items/iron_sword.png [moved from android/assets/textures/items/iron_sword.png with 100% similarity]
assets/pp/textures/items/lapis_lazuli.png [new file with mode: 0644]
assets/pp/textures/items/porkchop_cooked.png [new file with mode: 0644]
assets/pp/textures/items/porkchop_raw.png [new file with mode: 0644]
assets/pp/textures/items/shears.png [new file with mode: 0644]
assets/pp/textures/items/snowball.png [new file with mode: 0644]
assets/pp/textures/items/spawn_egg.png [new file with mode: 0644]
assets/pp/textures/items/stick.png [new file with mode: 0644]
assets/pp/textures/items/stone_axe.png [new file with mode: 0644]
assets/pp/textures/items/stone_hoe.png [new file with mode: 0644]
assets/pp/textures/items/stone_pickaxe.png [new file with mode: 0644]
assets/pp/textures/items/stone_shovel.png [moved from android/assets/textures/items/stone_shovel.png with 100% similarity]
assets/pp/textures/items/stone_sword.png [moved from android/assets/textures/items/stone_sword.png with 100% similarity]
assets/pp/textures/items/wood_axe.png [new file with mode: 0644]
assets/pp/textures/items/wood_hoe.png [new file with mode: 0644]
assets/pp/textures/items/wood_pickaxe.png [new file with mode: 0644]
assets/pp/textures/items/wood_shovel.png [moved from android/assets/textures/items/wood_shovel.png with 100% similarity]
assets/pp/textures/items/wood_sword.png [moved from android/assets/textures/items/wood_sword.png with 100% similarity]
assets/touch_gui.png [new file with mode: 0644]
build.gradle [deleted file]
build.gradle.kts [new file with mode: 0644]
buildSrc/build.gradle.kts [new file with mode: 0644]
buildSrc/src/main/kotlin/ApplicationInfo.kt [new file with mode: 0644]
buildSrc/src/main/kotlin/Dependencies.kt [new file with mode: 0644]
buildSrc/src/main/kotlin/Versions.kt [new file with mode: 0644]
core/build.gradle [deleted file]
core/build.gradle.kts [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/MainConfig.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/game/GameComponent.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/game/GameModule.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/game/GamePhysics.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/game/GameProc.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/game/GameRenderer.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/game/GameScope.java [moved from core/src/ru/deadsoftware/cavedroid/game/GameScope.java with 100% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/game/GameScreen.java [moved from core/src/ru/deadsoftware/cavedroid/game/GameScreen.java with 71% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/game/GameUiWindow.java [moved from core/src/ru/deadsoftware/cavedroid/game/GameUiWindow.java with 100% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/game/mobs/Mob.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/game/mobs/player/Player.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/game/objects/TouchButton.java [moved from core/src/ru/deadsoftware/cavedroid/game/objects/TouchButton.java with 100% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/game/objects/drop/DropController.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/game/world/GameWorld.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/menu/MenuComponent.java [moved from core/src/ru/deadsoftware/cavedroid/menu/MenuComponent.java with 100% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/menu/MenuProc.java [moved from core/src/ru/deadsoftware/cavedroid/menu/MenuProc.java with 58% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/menu/MenuScope.java [moved from core/src/ru/deadsoftware/cavedroid/menu/MenuScope.java with 100% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/menu/MenuScreen.java [moved from core/src/ru/deadsoftware/cavedroid/menu/MenuScreen.java with 100% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/menu/objects/Button.java [moved from core/src/ru/deadsoftware/cavedroid/menu/objects/Button.java with 100% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/menu/objects/ButtonEventListener.java [moved from core/src/ru/deadsoftware/cavedroid/menu/objects/ButtonEventListener.java with 100% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/menu/objects/ButtonRenderer.java [moved from core/src/ru/deadsoftware/cavedroid/menu/objects/ButtonRenderer.java with 100% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/menu/submenus/Menu.java [moved from core/src/ru/deadsoftware/cavedroid/menu/submenus/Menu.java with 72% similarity]
core/src/main/java/ru/deadsoftware/cavedroid/menu/submenus/MenuMain.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/menu/submenus/MenuNewGame.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/misc/Assets.java [new file with mode: 0644]
core/src/main/java/ru/deadsoftware/cavedroid/misc/Renderer.java [moved from core/src/ru/deadsoftware/cavedroid/misc/Renderer.java with 83% similarity]
core/src/main/kotlin/ru/deadsoftware/cavedroid/CaveGame.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/MainComponent.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/CommonBlockActionUtils.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/placeblock/IPlaceBlockAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToBackgroundAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToForegroundAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceSlabAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/IUpdateBlockAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedLeftAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedRightAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGrassAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateRequiresBlockAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSnowedGrassAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/IUseBlockAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/UseChestAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/UseCraftingTableAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/UseFurnaceAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/IUseItemAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseBedAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseEmptyBucketAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseLavaBucketAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UsePigSpawnEggAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseWaterBucketAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/IGameInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/InputUtils.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/Joystick.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/IGameInputAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/KeyboardInputAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/MouseInputAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/keys/KeyboardInputActionKey.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/keys/MouseInputActionKey.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/CloseGameWindowKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/DropItemKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyDownKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyUpKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoLeftKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoRightKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/JumpKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/MoveCursorControlsModeKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenInventoryKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/PauseGameKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SelectHotbarSlotKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/StopSwimKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SwimUpKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleControlsModeKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleDebugInfoKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleGameModeKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleMinimapKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/TurnOnFlyModeKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/AbstractInventoryItemsMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/AttackMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/CloseGameWindowMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/CreativeInventoryScrollMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/CursorMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/HotbarMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectChestInventoryItemMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCraftingInventoryItemMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCreativeInventoryItemMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectFurnaceInventoryItemMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectSurvivalInventoryItemMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/UseItemMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/touch/JoystickInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/mapper/KeyboardInputActionMapper.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/mapper/MouseInputActionMapper.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/FallingBlock.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/MobSaveDataMapper.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/MobsController.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/PeacefulMob.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/Pig.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/player/Inventory.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/Block.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/BlockAnimationInfo.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/BlockDropInfo.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/BlockMargins.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/CommonBlockParams.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/craft/CraftingRecipe.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/CraftingDto.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/SaveDataDto.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/item/CommonItemParams.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/item/InventoryItem.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/item/Item.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/world/Biome.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/world/generator/WorldGeneratorConfig.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/Chest.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/Container.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/ContainerController.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/Furnace.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/drop/Drop.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/IGameRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/AbstractWindowRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/ChestWindowRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/CraftingWindowRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/FurnaceWindowRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/SurvivalWindowRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/save/GameSaveData.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/save/GameSaveLoader.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/TooltipManager.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsConfigs.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsManager.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindow.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindowWithCraftGrid.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/ChestInventoryWindow.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CraftingInventoryWindow.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CreativeInventoryWindow.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/FurnaceInventoryWindow.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/SurvivalInventoryWindow.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldBlocksLogicControllerTask.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldMobDamageControllerTask.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/menu/objects/BooleanOptionButton.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/menu/submenus/MenuOptions.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/menu/submenus/MenusFactory.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/Saveable.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindKeyboardInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindMouseInputHandler.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindPlaceBlockAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindRenderer.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindUpdateBlockAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindUseBlockAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindUseItemAction.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/MultibindingConfig.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/ArrayMapExtensions.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/GdxExtensions.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/ItemUtils.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/SpriteOrigin.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/SpriteUtils.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/mobs/MobSprites.kt [new file with mode: 0644]
core/src/main/kotlin/ru/deadsoftware/cavedroid/prefs/PreferencesStore.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/CaveGame.java [deleted file]
core/src/ru/deadsoftware/cavedroid/MainComponent.java [deleted file]
core/src/ru/deadsoftware/cavedroid/MainConfig.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/GameComponent.java [deleted file]
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/GameModule.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/GamePhysics.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/GameProc.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/GameRenderer.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/GameSaver.java [deleted file]
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/mobs/FallingGravel.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/mobs/FallingSand.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/mobs/Mob.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/mobs/MobsController.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/mobs/Player.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/mobs/package-info.java [deleted file]
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/DropController.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/objects/Item.kt [deleted file]
core/src/ru/deadsoftware/cavedroid/game/objects/package-info.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/package-info.java [deleted file]
core/src/ru/deadsoftware/cavedroid/menu/objects/package-info.java [deleted file]
core/src/ru/deadsoftware/cavedroid/menu/package-info.java [deleted file]
core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuMain.java [deleted file]
core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuNewGame.java [deleted file]
core/src/ru/deadsoftware/cavedroid/menu/submenus/package-info.java [deleted file]
core/src/ru/deadsoftware/cavedroid/misc/Assets.java [deleted file]
core/src/ru/deadsoftware/cavedroid/misc/ControlMode.java [deleted file]
core/src/ru/deadsoftware/cavedroid/misc/annotations/NonnullByDefault.java [deleted file]
core/src/ru/deadsoftware/cavedroid/misc/package-info.java [deleted file]
core/src/ru/deadsoftware/cavedroid/package-info.java [deleted file]
desktop/build.gradle [deleted file]
desktop/build.gradle.kts [new file with mode: 0644]
desktop/gradle/wrapper/gradle-wrapper.properties [new file with mode: 0644]
desktop/src/main/kotlin/ru/deadsoftware/cavedroid/desktop/DesktopLauncher.kt [new file with mode: 0644]
desktop/src/main/kotlin/ru/deadsoftware/cavedroid/desktop/DesktopPreferencesStore.kt [new file with mode: 0644]
desktop/src/main/resources [new symlink]
desktop/src/ru/deadsoftware/cavedroid/desktop/DesktopLauncher.java [deleted file]
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-clean-work-tree.sh [new file with mode: 0755]
settings.gradle [deleted file]
settings.gradle.kts [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..b24a8edfa256f29fdaa63c65c53aa04125be7892 100644 (file)
@@ -116,3 +116,8 @@ Thumbs.db
 !/ios-moe/xcode/*.xcodeproj/xcshareddata
 !/ios-moe/xcode/*.xcodeproj/project.pbxproj
 /ios-moe/xcode/native/
 !/ios-moe/xcode/*.xcodeproj/xcshareddata
 !/ios-moe/xcode/*.xcodeproj/project.pbxproj
 /ios-moe/xcode/native/
+
+release-*/
+keystore.properties
+
+*/build/
diff --git a/COPYING b/COPYING
index 37400b601b719a895b831164d55737df9ddd37b2..90ecac317cec31dfa6bb755d18d10b7034ab4fcb 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -1,5 +1,13 @@
 This game is distributed under MIT License (see LICENSE).
 
 This game is distributed under MIT License (see LICENSE).
 
-Textures used in this game:
+Textures used in this game (in android/assets/pp directory):
 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/
 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/
+
+On screen joystick is CC-0 from opengameart.org:
+https://opengameart.org/content/mmorpg-virtual-joysticks
+
+Font is Minecraft Font by JDGraphics licensed as Public Domain:
+https://www.fontspace.com/minecraft-font-f28180
+
+Some scripts from stack overflow are distributed under applicable licenses
index b0f75259d51a0543afa1e874894aca6733b2c176..bf5e3713ed7512add61b4810b1329da1eeedbe7e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,12 +1,20 @@
 # CaveDroid
 # 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>
 2D Minecraft clone for Android and Desktop. <br>
 Written in Java using libGDX framework. <br>
+<details>
+  <summary>Screenshot</summary>
+
+![Screenshot](https://fredboy.ru/pub/cavedroid/screenshot.png)
+
+</details>
+
 ## Binary releases
 You can download apk and jar from here: <br>
 <https://fredboy.ru/pub/cavedroid/>
 ## Build instructions
 ## Binary releases
 You can download apk and jar from here: <br>
 <https://fredboy.ru/pub/cavedroid/>
 ## Build instructions
+You need to publish [my ksp processor](https://github.com/fredboy/automultibind) to mavenLocal repository first.
 ### Android
 To build for Android use <br>
 `./gradlew android:assemble` <br>
 ### Android
 To build for Android use <br>
 `./gradlew android:assemble` <br>
diff --git a/android/assets/icons/icon128.png b/android/assets/icons/icon128.png
deleted file mode 100644 (file)
index 19e4532..0000000
Binary files a/android/assets/icons/icon128.png and /dev/null differ
diff --git a/android/assets/icons/icon256.png b/android/assets/icons/icon256.png
deleted file mode 100644 (file)
index 3b8b92b..0000000
Binary files a/android/assets/icons/icon256.png and /dev/null differ
diff --git a/android/assets/json/game_items.json b/android/assets/json/game_items.json
deleted file mode 100644 (file)
index 042f0c9..0000000
+++ /dev/null
@@ -1,654 +0,0 @@
-{
-  "blocks": {
-    "none": {
-      "collision": false,
-      "transparent": true
-    },
-    "stone": {
-      "hp": 450,
-      "drop": "cobblestone"
-    },
-    "grass": {
-      "hp": 54,
-      "drop": "dirt"
-    },
-    "dirt": {
-      "hp": 45
-    },
-    "cobblestone": {
-      "hp": 600
-    },
-    "planks_oak": {
-      "hp": 180
-    },
-    "sapling_oak": {
-      "hp": 0,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "block_required": true
-    },
-    "bedrock": {
-      "hp": -1
-    },
-    "water": {
-      "hp": -1,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "fluid": true,
-      "texture": "water_still",
-      "meta": "water",
-      "animated": true,
-      "frames": 16
-    },
-    "lava": {
-      "hp": -1,
-      "collision": false,
-      "background": false,
-      "fluid": true,
-      "texture": "lava_still",
-      "meta": "lava",
-      "animated": true,
-      "frames": 16
-    },
-    "sand": {
-      "hp": 45
-    },
-    "gravel": {
-      "hp": 54
-    },
-    "gold_ore": {
-      "hp": 900
-    },
-    "iron_ore": {
-      "hp": 900
-    },
-    "coal_ore": {
-      "hp": 900,
-      "drop": "coal"
-    },
-    "log_oak": {
-      "hp": 180
-    },
-    "leaves_oak": {
-      "hp": 21,
-      "transparent": true
-    },
-    "sponge": {
-      "hp": 54
-    },
-    "glass": {
-      "hp": 27,
-      "drop": "none",
-      "background": false,
-      "transparent": true
-    },
-    "lapis_ore": {
-      "hp": 900,
-      "drop": "lapis"
-    },
-    "lapis_block": {
-      "hp": 900
-    },
-    "sandstone": {
-      "hp": 240
-    },
-    "noteblock": {
-      "hp": 75
-    },
-    "bed_l": {
-      "hp": 21,
-      "drop": "none",
-      "collision": false,
-      "background": true,
-      "transparent": true
-    },
-    "bed_r": {
-      "hp": 21,
-      "drop": "none",
-      "collision": false,
-      "background": true,
-      "transparent": true
-    },
-    "web": {
-      "hp": 1200,
-      "collision": false,
-      "background": false,
-      "transparent": true
-    },
-    "tallgrass": {
-      "hp": 0,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "block_required": true
-    },
-    "deadbush": {
-      "hp": 0,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "block_required": true
-    },
-    "bricks": {
-      "hp": 600
-    },
-    "dandelion": {
-      "hp": 0,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "block_required": true
-    },
-    "rose": {
-      "hp": 0,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "block_required": true
-    },
-    "mushroom_brown": {
-      "hp": 0,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "block_required": true
-    },
-    "mushroom_red": {
-      "hp": 0,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "block_required": true
-    },
-    "wool_colored_white": {
-      "hp": 75
-    },
-    "wool_colored_orange": {
-      "hp": 75
-    },
-    "wool_colored_magenta": {
-      "hp": 75
-    },
-    "wool_colored_light_blue": {
-      "hp": 75
-    },
-    "wool_colored_yellow": {
-      "hp": 75
-    },
-    "wool_colored_lime": {
-      "hp": 75
-    },
-    "wool_colored_pink": {
-      "hp": 75
-    },
-    "wool_colored_gray": {
-      "hp": 75
-    },
-    "wool_colored_silver": {
-      "hp": 75
-    },
-    "wool_colored_cyan": {
-      "hp": 75
-    },
-    "wool_colored_purple": {
-      "hp": 75
-    },
-    "wool_colored_blue": {
-      "hp": 75
-    },
-    "wool_colored_brown": {
-      "hp": 75
-    },
-    "wool_colored_green": {
-      "hp": 75
-    },
-    "wool_colored_red": {
-      "hp": 75
-    },
-    "wool_colored_black": {
-      "hp": 75
-    },
-    "gold_block": {
-      "hp": 900
-    },
-    "iron_block": {
-      "hp": 1500
-    },
-    "stone_slab": {
-      "top": 8,
-      "hp": 600,
-      "transparent": true,
-      "sprite_top": 8
-    },
-    "double_stone_slab": {
-      "hp": 600,
-      "drop": "stone_slab",
-      "texture": "stone_slab"
-    },
-    "sandstone_slab": {
-      "top": 8,
-      "hp": 600,
-      "transparent": true,
-      "texture": "sandstone"
-    },
-    "oak_slab": {
-      "top": 8,
-      "hp": 180,
-      "transparent": true,
-      "texture": "planks_oak"
-    },
-    "cobblestone_slab": {
-      "top": 8,
-      "hp": 600,
-      "transparent": true,
-      "texture": "cobblestone"
-    },
-    "brick_slab": {
-      "top": 8,
-      "hp": 600,
-      "transparent": true,
-      "texture": "bricks"
-    },
-    "stonebrick": {
-      "hp": 450
-    },
-    "stonebrick_slab": {
-      "top": 8,
-      "hp": 450,
-      "transparent": true,
-      "texture": "stonebrick"
-    },
-    "cactus": {
-      "left": 1,
-      "right": 1,
-      "hp": 39,
-      "transparent": true,
-      "block_required": true
-    },
-    "water_16": {
-      "hp": -1,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "fluid": true,
-      "texture": "water_flow",
-      "meta": "water",
-      "animated": true,
-      "frames": 16
-    },
-    "water_12": {
-      "top": 4,
-      "hp": -1,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "fluid": true,
-      "texture": "water_flow",
-      "meta": "water",
-      "animated": true,
-      "frames": 16,
-      "sprite_top": 4
-    },
-    "water_8": {
-      "top": 8,
-      "hp": -1,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "fluid": true,
-      "texture": "water_flow",
-      "meta": "water",
-      "animated": true,
-      "frames": 16,
-      "sprite_top": 8
-    },
-    "water_4": {
-      "top": 12,
-      "hp": -1,
-      "collision": false,
-      "background": false,
-      "transparent": true,
-      "fluid": true,
-      "texture": "water_flow",
-      "meta": "water",
-      "animated": true,
-      "frames": 16,
-      "sprite_top": 12
-    },
-    "lava_16": {
-      "hp": -1,
-      "collision": false,
-      "background": false,
-      "fluid": true,
-      "texture": "lava_flow",
-      "meta": "lava",
-      "animated": true,
-      "frames": 16
-    },
-    "lava_12": {
-      "top": 4,
-      "hp": -1,
-      "collision": false,
-      "background": false,
-      "fluid": true,
-      "texture": "lava_flow",
-      "meta": "lava",
-      "animated": true,
-      "frames": 16,
-      "sprite_top": 4
-    },
-    "lava_8": {
-      "top": 8,
-      "hp": -1,
-      "collision": false,
-      "background": false,
-      "fluid": true,
-      "texture": "lava_flow",
-      "meta": "lava",
-      "animated": true,
-      "frames": 16,
-      "sprite_top": 8
-    },
-    "lava_4": {
-      "top": 12,
-      "hp": -1,
-      "collision": false,
-      "background": false,
-      "fluid": true,
-      "texture": "lava_flow",
-      "meta": "lava",
-      "animated": true,
-      "frames": 16,
-      "sprite_top": 12
-    },
-    "obsidian": {
-      "hp": 1500
-    }
-  },
-  "items": {
-    "none": {
-      "name": "",
-      "type": "block"
-    },
-    "stone": {
-      "name": "Stone",
-      "type": "block"
-    },
-    "grass": {
-      "name": "Grass",
-      "type": "block"
-    },
-    "dirt": {
-      "name": "Dirt",
-      "type": "block"
-    },
-    "cobblestone": {
-      "name": "Cobblestone",
-      "type": "block"
-    },
-    "planks_oak": {
-      "name": "Oak Planks",
-      "type": "block"
-    },
-    "sapling_oak": {
-      "name": "Oak Sapling",
-      "type": "block"
-    },
-    "bedrock": {
-      "name": "Bedrock",
-      "type": "block"
-    },
-    "water": {
-      "name": "Water",
-      "type": "block"
-    },
-    "lava": {
-      "name": "Lava",
-      "type": "block"
-    },
-    "sand": {
-      "name": "Sand",
-      "type": "block"
-    },
-    "gravel": {
-      "name": "Gravel",
-      "type": "block"
-    },
-    "gold_ore": {
-      "name": "Golden Ore",
-      "type": "block"
-    },
-    "iron_ore": {
-      "name": "Iron Ore",
-      "type": "block"
-    },
-    "coal_ore": {
-      "name": "Coal Ore",
-      "type": "block"
-    },
-    "log_oak": {
-      "name": "Wood",
-      "type": "block"
-    },
-    "leaves_oak": {
-      "name": "Leaves",
-      "type": "block"
-    },
-    "glass": {
-      "name": "Glass",
-      "type": "block"
-    },
-    "lapis_ore": {
-      "name": "Lapis Ore",
-      "type": "block"
-    },
-    "lapis_block": {
-      "name": "Lapis Block",
-      "type": "block"
-    },
-    "sandstone": {
-      "name": "Sandstone",
-      "type": "block"
-    },
-    "web": {
-      "name": "Cobweb",
-      "type": "block"
-    },
-    "tallgrass": {
-      "name": "Tall Grass",
-      "type": "block"
-    },
-    "deadbush": {
-      "name": "Dead Bush",
-      "type": "block"
-    },
-    "bricks": {
-      "name": "Bricks",
-      "type": "block"
-    },
-    "dandelion": {
-      "name": "Dandelion",
-      "type": "block"
-    },
-    "rose": {
-      "name": "Rose",
-      "type": "block"
-    },
-    "mushroom_brown": {
-      "name": "Mushroom",
-      "type": "block"
-    },
-    "mushroom_red": {
-      "name": "Mushroom",
-      "type": "block"
-    },
-    "wool_colored_white": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_orange": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_magenta": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_light_blue": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_yellow": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_lime": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_pink": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_gray": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_silver": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_cyan": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_purple": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_blue": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_brown": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_green": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_red": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "wool_colored_black": {
-      "name": "Wool",
-      "type": "block"
-    },
-    "gold_block": {
-      "name": "Gold Block",
-      "type": "block"
-    },
-    "iron_block": {
-      "name": "Iron Block",
-      "type": "block"
-    },
-    "stone_slab": {
-      "name": "Stone Slab",
-      "type": "block",
-      "meta": "slab"
-    },
-    "sandstone_slab": {
-      "name": "Sandstone Slab",
-      "type": "block",
-      "meta": "slab"
-    },
-    "oak_slab": {
-      "name": "Oak Slab",
-      "type": "block",
-      "meta": "slab"
-    },
-    "cobblestone_slab": {
-      "name": "Cobblestone Slab",
-      "type": "block",
-      "meta": "slab"
-    },
-    "brick_slab": {
-      "name": "Brick Slab",
-      "type": "block",
-      "meta": "slab"
-    },
-    "stonebrick": {
-      "name": "Stone Brick",
-      "type": "block"
-    },
-    "stonebrick_slab": {
-      "name": "Stone Brick Slab",
-      "type": "block",
-      "meta": "slab"
-    },
-    "cactus": {
-      "name": "Cactus",
-      "type": "block"
-    },
-    "obsidian": {
-      "name": "Obsidian",
-      "type": "block"
-    },
-    "wood_sword": {
-      "name": "Wooden Sword",
-      "type": "tool"
-    },
-    "stone_sword": {
-      "name": "Stone Sword",
-      "type": "tool"
-    },
-    "iron_sword": {
-      "name": "Iron Sword",
-      "type": "tool"
-    },
-    "diamond_sword": {
-      "name": "Diamond Sword",
-      "type": "tool"
-    },
-    "gold_sword": {
-      "name": "Golden Sword",
-      "type": "tool"
-    },
-    "wood_shovel": {
-      "name": "Wooden Shovel",
-      "type": "tool"
-    },
-    "stone_shovel": {
-      "name": "Stone Shovel",
-      "type": "tool"
-    },
-    "iron_shovel": {
-      "name": "Iron Shovel",
-      "type": "tool"
-    },
-    "diamond_shovel": {
-      "name": "Diamond Shovel",
-      "type": "tool"
-    },
-    "gold_shovel": {
-      "name": "Golden Shovel",
-      "type": "tool"
-    },
-    "bucket_empty": {
-      "name": "Empty Bucket",
-      "type": "tool"
-    },
-    "bucket_water": {
-      "name": "Water Bucket",
-      "type": "tool"
-    },
-    "bucket_lava": {
-      "name": "Lava Bucket",
-      "type": "tool"
-    }
-  }
-}
\ No newline at end of file
diff --git a/android/assets/json/touch_buttons.json b/android/assets/json/touch_buttons.json
deleted file mode 100644 (file)
index cddaf53..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-{
-  "up": {
-    "x": 26,
-    "y": -52,
-    "w": 26,
-    "h": 26,
-    "key": "W"
-  },
-  "down": {
-    "x": 26,
-    "y": -26,
-    "w": 26,
-    "h": 26,
-    "key": "S"
-  },
-  "left": {
-    "x": 0,
-    "y": -26,
-    "w": 26,
-    "h": 26,
-    "key": "A"
-  },
-  "right": {
-    "x": 52,
-    "y": -26,
-    "w": 26,
-    "h": 26,
-    "key": "D"
-  },
-  "alt": {
-    "x": 78,
-    "y": -26,
-    "w": 26,
-    "h": 26,
-    "key": "L-Alt"
-  },
-  "lmb": {
-    "x": -52,
-    "y": -26,
-    "w": 26,
-    "h": 26,
-    "mouse": true,
-    "key": "Left"
-  },
-  "rmb": {
-    "x": -26,
-    "y": -26,
-    "w": 26,
-    "h": 26,
-    "mouse": true,
-    "key": "Right"
-  }
-}
\ No newline at end of file
diff --git a/android/assets/textures/blocks/furnace_off.png b/android/assets/textures/blocks/furnace_off.png
deleted file mode 100644 (file)
index 6d9b405..0000000
Binary files a/android/assets/textures/blocks/furnace_off.png and /dev/null differ
diff --git a/android/assets/textures/blocks/furnace_on.png b/android/assets/textures/blocks/furnace_on.png
deleted file mode 100644 (file)
index ecbc607..0000000
Binary files a/android/assets/textures/blocks/furnace_on.png and /dev/null differ
diff --git a/android/assets/textures/blocks/lapis_ore.png b/android/assets/textures/blocks/lapis_ore.png
deleted file mode 100644 (file)
index 04a4630..0000000
Binary files a/android/assets/textures/blocks/lapis_ore.png and /dev/null 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
diff --git a/android/assets/textures/blocks/leaves_oak.png b/android/assets/textures/blocks/leaves_oak.png
deleted file mode 100644 (file)
index c2a54ee..0000000
Binary files a/android/assets/textures/blocks/leaves_oak.png and /dev/null 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/touch_gui.png b/android/assets/touch_gui.png
deleted file mode 100644 (file)
index b194413..0000000
Binary files a/android/assets/touch_gui.png and /dev/null differ
diff --git a/android/build.gradle b/android/build.gradle
deleted file mode 100644 (file)
index a5ed087..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-android {
-    buildToolsVersion "28.0.3"
-    compileSdkVersion 29
-    sourceSets {
-        main {
-            manifest.srcFile 'AndroidManifest.xml'
-            java.srcDirs = ['src']
-            aidl.srcDirs = ['src']
-            renderscript.srcDirs = ['src']
-            res.srcDirs = ['res']
-            assets.srcDirs = ['assets']
-            jniLibs.srcDirs = ['libs']
-        }
-        debug {
-            res.srcDirs = ['debug/res']
-        }
-    }
-    compileOptions {
-        sourceCompatibility 1.8
-        targetCompatibility 1.8
-    }
-    packagingOptions {
-        exclude 'META-INF/robovm/ios/robovm.xml'
-    }
-    defaultConfig {
-        applicationId "ru.deadsoftware.cavedroid"
-        minSdkVersion 24
-        targetSdkVersion 29
-        versionCode 10
-        versionName "alpha0.4"
-    }
-    applicationVariants.all { variant ->
-        variant.outputs.all {
-            outputFileName = "android-${versionName}.apk"
-        }
-    }
-    buildTypes {
-        release {
-            minifyEnabled false
-        }
-        debug {
-            applicationIdSuffix ".debug"
-        }
-    }
-
-}
-
-
-// called every time gradle gets executed, takes the native dependencies of
-// the natives configuration, and extracts them to the proper libs/ folders
-// so they get packed with the APK.
-task copyAndroidNatives {
-    doFirst {
-        file("libs/armeabi/").mkdirs()
-        file("libs/armeabi-v7a/").mkdirs()
-        file("libs/arm64-v8a/").mkdirs()
-        file("libs/x86_64/").mkdirs()
-        file("libs/x86/").mkdirs()
-
-        configurations.natives.files.each { jar ->
-            def outputDir = null
-            if (jar.name.endsWith("natives-arm64-v8a.jar")) outputDir = file("libs/arm64-v8a")
-            if (jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a")
-            if(jar.name.endsWith("natives-armeabi.jar")) outputDir = file("libs/armeabi")
-            if(jar.name.endsWith("natives-x86_64.jar")) outputDir = file("libs/x86_64")
-            if(jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86")
-            if(outputDir != null) {
-                copy {
-                    from zipTree(jar)
-                    into outputDir
-                    include "*.so"
-                }
-            }
-        }
-    }
-}
-
-tasks.whenTaskAdded { packageTask ->
-    if (packageTask.name.contains("package")) {
-        packageTask.dependsOn 'copyAndroidNatives'
-    }
-}
-
-task run(type: Exec) {
-    def path
-    def localProperties = project.file("../local.properties")
-    if (localProperties.exists()) {
-        Properties properties = new Properties()
-        localProperties.withInputStream { instr ->
-            properties.load(instr)
-        }
-        def sdkDir = properties.getProperty('sdk.dir')
-        if (sdkDir) {
-            path = sdkDir
-        } else {
-            path = "$System.env.ANDROID_HOME"
-        }
-    } else {
-        path = "$System.env.ANDROID_HOME"
-    }
-
-    def adb = path + "/platform-tools/adb"
-    commandLine "$adb", 'shell', 'am', 'start', '-n', 'ru.deadsoftware.cavedroid/ru.deadsoftware.cavedroid.AndroidLauncher'
-}
\ No newline at end of file
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
new file mode 100644 (file)
index 0000000..40e158e
--- /dev/null
@@ -0,0 +1,131 @@
+import java.io.FileInputStream
+import java.util.Properties
+
+private val natives by configurations.creating
+
+plugins {
+    id("com.android.application")
+    id("kotlin-android")
+}
+
+private val keystorePropertiesFile = rootProject.file("keystore.properties")
+private val keystoreProperties = if (keystorePropertiesFile.exists()) {
+    Properties().apply {
+        load(FileInputStream(keystorePropertiesFile))
+    }
+} else {
+    null
+}
+
+
+android {
+    namespace = ApplicationInfo.packageName
+    compileSdk = 34
+
+    sourceSets {
+
+        named("main") {
+            jniLibs.srcDir("libs")
+        }
+
+        named("debug") {
+            res.srcDir("src/debug/res")
+        }
+
+    }
+
+    compileOptions {
+        sourceCompatibility = ApplicationInfo.sourceCompatibility
+        targetCompatibility = ApplicationInfo.sourceCompatibility
+    }
+
+    defaultConfig {
+        applicationId = ApplicationInfo.packageName
+        minSdk = 19
+        targetSdk = 34
+
+        versionCode = ApplicationInfo.versionCode
+        versionName = ApplicationInfo.versionName
+
+        multiDexEnabled = true
+    }
+
+    applicationVariants.asSequence()
+        .flatMap { variant -> variant.outputs.asSequence() }
+        .mapNotNull { output -> output as? com.android.build.gradle.internal.api.BaseVariantOutputImpl }
+        .forEach { output -> output.outputFileName = "android-${ApplicationInfo.versionName}.apk" }
+
+    val releaseConfig = signingConfigs.create("release_config")
+    with(releaseConfig) {
+        storeFile = keystoreProperties?.get("releaseKeystorePath")?.let(::file)
+        storePassword = keystoreProperties?.get("releaseKeystorePassword")?.toString()
+        keyAlias = keystoreProperties?.get("releaseKeyAlias")?.toString()
+        keyPassword = keystoreProperties?.get("releaseKeyPassword")?.toString()
+    }
+
+    buildTypes {
+        release {
+            isMinifyEnabled = false
+            signingConfig = releaseConfig
+        }
+
+        debug {
+            applicationIdSuffix = ".debug"
+        }
+    }
+
+    buildFeatures {
+        buildConfig = true
+    }
+}
+
+// called every time gradle gets executed, takes the native dependencies of
+// the natives configuration, and extracts them to the proper libs/ folders
+// so they get packed with the APK.
+task("copyAndroidNatives") {
+    doFirst {
+        val armeabiV7Dir = file("libs/armeabi-v7a/").apply { mkdirs() }
+        val arm64Dir = file("libs/arm64-v8a/").apply { mkdirs() }
+        val x86Dir = file("libs/x86/").apply { mkdirs() }
+        val amd64Dir = file("libs/x86_64/").apply { mkdirs() }
+
+        natives.files.forEach { jar ->
+            val outputDir = when {
+                jar.name.endsWith("natives-armeabi-v7a.jar") -> armeabiV7Dir
+                jar.name.endsWith("natives-arm64-v8a.jar") -> arm64Dir
+                jar.name.endsWith("natives-x86.jar") -> x86Dir
+                jar.name.endsWith("natives-x86_64.jar") -> amd64Dir
+                else -> null
+            }
+
+            if (outputDir != null) {
+                copy {
+                    from(zipTree(jar))
+                    into(outputDir)
+                    include("*.so")
+                }
+            }
+        }
+    }
+}
+
+tasks.whenTaskAdded {
+    if (name.contains("package")) {
+        dependsOn("copyAndroidNatives")
+    }
+}
+
+dependencies {
+    implementation((project(":core")))
+    implementation(platform(Dependencies.Kotlin.bom))
+
+    implementation(Dependencies.LibGDX.gdx)
+    implementation(Dependencies.LibGDX.Android.backend)
+
+    natives(Dependencies.LibGDX.Android.Natives.armeabi)
+    natives(Dependencies.LibGDX.Android.Natives.arm64)
+    natives(Dependencies.LibGDX.Android.Natives.x86)
+    natives(Dependencies.LibGDX.Android.Natives.x86_64)
+
+    configurations["implementation"].exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
+}
\ No newline at end of file
diff --git a/android/debug/res/drawable-anydpi-v26/ic_launcher_foreground.xml b/android/debug/res/drawable-anydpi-v26/ic_launcher_foreground.xml
deleted file mode 100644 (file)
index 4535da3..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="512dp"
-    android:height="512dp"
-    android:viewportWidth="512"
-    android:viewportHeight="512">
-  <path
-      android:pathData="M160,160L160,176L144,176L144,224L160,224L160,240L176,240L176,256L192,256L192,272L208,272L208,256L192,256L192,240L176,240L176,224L160,224L160,176L176,176L176,160L160,160z"
-      android:fillColor="#20180a"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M176,160L176,176L208,176L208,192L224,192L224,208L240,208L240,224L272,224L272,208L288,208L288,192L304,192L304,176L336,176L336,160L304,160L304,176L288,176L288,192L272,192L272,208L240,208L240,192L224,192L224,176L208,176L208,160L176,160z"
-      android:fillColor="#372910"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M336,160L336,176L352,176L352,224L336,224L336,240L320,240L320,256L304,256L304,272L320,272L320,256L336,256L336,240L352,240L352,224L368,224L368,176L352,176L352,160L336,160z"
-      android:fillColor="#20180a"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M160,176L160,192L176,192L176,208L192,208L192,224L208,224L208,240L224,240L224,224L208,224L208,208L192,208L192,192L176,192L176,176L160,176z"
-      android:fillColor="#6b511f"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M176,176L176,192L208,192L208,208L224,208L224,224L240,224L240,208L224,208L224,192L208,192L208,176L176,176M304,176L304,192L288,192L288,208L272,208L272,224L288,224L288,208L304,208L304,192L336,192L336,224L320,224L320,240L304,240L304,256L320,256L320,240L336,240L336,224L352,224L352,192L336,192L336,176L304,176z"
-      android:fillColor="#866526"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M336,176L336,192L352,192L352,176L336,176z"
-      android:fillColor="#6b511f"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M160,192L160,224L176,224L176,240L192,240L192,256L208,256L208,240L192,240L192,224L176,224L176,192L160,192z"
-      android:fillColor="#866526"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M192,192L192,208L208,208L208,192L192,192M304,192L304,208L288,208L288,224L304,224L304,240L320,240L320,224L336,224L336,208L320,208L320,192L304,192z"
-      android:fillColor="#755821"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M320,192L320,208L336,208L336,192L320,192z"
-      android:fillColor="#6b511f"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M176,208L176,224L192,224L192,208L176,208M208,208L208,224L224,224L224,208L208,208z"
-      android:fillColor="#755821"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M304,208L304,224L320,224L320,208L304,208z"
-      android:fillColor="#6b511f"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M192,224L192,240L208,240L208,224L192,224z"
-      android:fillColor="#755821"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M224,224L224,240L240,240L240,256L256,256L256,272L272,272L272,288L288,288L288,304L304,304L304,320L320,320L320,336L336,336L336,352L352,352L352,320L320,320L320,304L304,304L304,288L288,288L288,272L272,272L272,240L288,240L288,224L272,224L272,240L240,240L240,224L224,224z"
-      android:fillColor="#493615"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M240,224L240,240L272,240L272,224L240,224z"
-      android:fillColor="#000000"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M288,224L288,240L304,240L304,224L288,224z"
-      android:fillColor="#6b511f"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M208,240L208,256L224,256L224,272L240,272L240,304L224,304L224,320L208,320L208,336L192,336L192,352L176,352L176,368L208,368L208,336L224,336L224,320L240,320L240,304L272,304L272,320L288,320L288,336L304,336L304,368L336,368L336,352L320,352L320,336L304,336L304,320L288,320L288,304L272,304L272,288L256,288L256,272L240,272L240,256L224,256L224,240L208,240z"
-      android:fillColor="#281e0b"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M224,240L224,256L240,256L240,240L224,240M272,240L272,256L288,256L288,240L272,240z"
-      android:fillColor="#896727"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M288,240L288,256L304,256L304,240L288,240z"
-      android:fillColor="#281e0b"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M240,256L240,272L256,272L256,256L240,256z"
-      android:fillColor="#896727"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M272,256L272,272L288,272L288,256L272,256z"
-      android:fillColor="#281e0b"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M224,272L224,288L208,288L208,304L192,304L192,320L160,320L160,352L176,352L176,336L192,336L192,320L208,320L208,304L224,304L224,288L240,288L240,272L224,272z"
-      android:fillColor="#493615"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M256,272L256,288L272,288L272,272L256,272z"
-      android:fillColor="#684e1e"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M224,288L224,304L240,304L240,288L224,288M272,288L272,304L288,304L288,288L272,288z"
-      android:fillColor="#896727"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M208,304L208,320L224,320L224,304L208,304M288,304L288,320L304,320L304,304L288,304z"
-      android:fillColor="#684e1e"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M192,320L192,336L208,336L208,320L192,320M304,320L304,336L320,336L320,320L304,320M176,336L176,352L192,352L192,336L176,336M320,336L320,352L336,352L336,336L320,336z"
-      android:fillColor="#896727"
-      android:strokeColor="#00000000"/>
-</vector>
diff --git a/android/debug/res/drawable-hdpi/ic_launcher.png b/android/debug/res/drawable-hdpi/ic_launcher.png
deleted file mode 100644 (file)
index 9c5bde7..0000000
Binary files a/android/debug/res/drawable-hdpi/ic_launcher.png and /dev/null differ
diff --git a/android/debug/res/drawable-mdpi/ic_launcher.png b/android/debug/res/drawable-mdpi/ic_launcher.png
deleted file mode 100644 (file)
index 7e8a490..0000000
Binary files a/android/debug/res/drawable-mdpi/ic_launcher.png and /dev/null differ
diff --git a/android/debug/res/drawable-xhdpi/ic_launcher.png b/android/debug/res/drawable-xhdpi/ic_launcher.png
deleted file mode 100644 (file)
index 88f3a21..0000000
Binary files a/android/debug/res/drawable-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/debug/res/drawable-xxhdpi/ic_launcher.png b/android/debug/res/drawable-xxhdpi/ic_launcher.png
deleted file mode 100644 (file)
index ca5ae82..0000000
Binary files a/android/debug/res/drawable-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/debug/res/drawable-xxxhdpi/ic_launcher.png b/android/debug/res/drawable-xxxhdpi/ic_launcher.png
deleted file mode 100644 (file)
index 89ce105..0000000
Binary files a/android/debug/res/drawable-xxxhdpi/ic_launcher.png and /dev/null differ
index f6a837e7495b0b09b20084b62ccce3af13546625..9d5fc15cdfa1fbbd7b48aff3c039d25ddb73bfa2 100644 (file)
Binary files a/android/ic_launcher-web.png and b/android/ic_launcher-web.png differ
diff --git a/android/project.properties b/android/project.properties
deleted file mode 100644 (file)
index 3fefa92..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-# This file is used by the Eclipse ADT plugin. It is unnecessary for IDEA and Android Studio projects, which
-# configure Proguard and the Android target via the build.gradle file.
-
-# To enable ProGuard to work with Eclipse ADT, uncomment this (available properties: sdk.dir, user.home)
-# and ensure proguard.jar in the Android SDK is up to date (or alternately reduce the android target to 23 or lower):
-# proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-rules.pro
-
-# Project target.
-target=android-19
diff --git a/android/res/drawable-anydpi-v26/ic_launcher_background.xml b/android/res/drawable-anydpi-v26/ic_launcher_background.xml
deleted file mode 100644 (file)
index b398624..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-<vector android:height="512dp" android:viewportHeight="256"
-    android:viewportWidth="256" android:width="512dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M0,0L0,64L80,64L80,48L16,48L16,32L32,32L32,16L16,16L16,0L0,0z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M16,0L16,16L80,16L80,0L16,0z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M80,0L80,16L96,16L96,32L112,32L112,16L128,16L128,0L80,0z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M128,0L128,16L144,16L144,0L128,0z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M144,0L144,16L176,16L176,0L144,0z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M176,0L176,16L240,16L240,32L256,32L256,16L240,16L240,0L176,0z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M240,0L240,16L256,16L256,0L240,0z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M32,16L32,32L96,32L96,16L32,16z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M112,16L112,32L80,32L80,48L128,48L128,32L144,32L144,48L160,48L160,64L48,64L48,80L80,80L80,112L48,112L48,128L128,128L128,112L96,112L96,96L128,96L128,80L160,80L160,64L240,64L240,48L160,48L160,32L144,32L144,16L112,16z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M144,16L144,32L192,32L192,16L144,16z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M192,16L192,32L208,32L208,16L192,16z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M208,16L208,32L160,32L160,48L208,48L208,32L224,32L224,48L240,48L240,80L256,80L256,32L240,32L240,16L208,16z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M16,32L16,48L32,48L32,32L16,32z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M32,32L32,48L64,48L64,32L32,32z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M64,32L64,48L80,48L80,32L64,32z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M128,32L128,48L144,48L144,64L160,64L160,48L144,48L144,32L128,32z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M208,32L208,48L224,48L224,32L208,32z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M80,48L80,64L96,64L96,48L80,48z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M96,48L96,64L112,64L112,48L96,48z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M112,48L112,64L144,64L144,48L112,48M0,64L0,80L48,80L48,64L0,64M160,64L160,80L240,80L240,64L160,64z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M0,80L0,96L32,96L32,80L0,80z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M32,80L32,96L0,96L0,112L32,112L32,128L48,128L48,96L80,96L80,80L32,80M128,80L128,96L144,96L144,80L128,80z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M144,80L144,96L160,96L160,80L144,80z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M160,80L160,96L192,96L192,112L208,112L208,128L192,128L192,144L208,144L208,128L256,128L256,112L224,112L224,96L192,96L192,80L160,80z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M192,80L192,96L224,96L224,112L240,112L240,96L256,96L256,80L192,80z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M48,96L48,112L80,112L80,96L48,96z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M96,96L96,112L112,112L112,96L96,96z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M112,96L112,112L192,112L192,96L112,96z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M240,96L240,112L256,112L256,96L240,96z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M0,112L0,128L32,128L32,112L0,112z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M128,112L128,128L176,128L176,112L128,112z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M176,112L176,128L208,128L208,112L176,112z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M0,128L0,176L16,176L16,192L0,192L0,240L48,240L48,224L16,224L16,192L64,192L64,176L32,176L32,160L64,160L64,144L32,144L32,160L16,160L16,128L0,128z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M16,128L16,144L48,144L48,128L16,128z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M48,128L48,144L64,144L64,128L48,128z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M64,128L64,144L80,144L80,128L64,128z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M80,128L80,144L112,144L112,160L128,160L128,144L144,144L144,160L176,160L176,144L144,144L144,128L80,128z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M144,128L144,144L192,144L192,128L144,128M208,128L208,144L224,144L224,160L240,160L240,144L256,144L256,128L208,128z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M16,144L16,160L32,160L32,144L16,144M64,144L64,160L112,160L112,176L128,176L128,160L144,160L144,176L160,176L160,160L144,160L144,144L128,144L128,160L112,160L112,144L64,144M176,144L176,160L224,160L224,144L176,144z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M240,144L240,160L208,160L208,176L160,176L160,192L128,192L128,176L80,176L80,192L128,192L128,208L160,208L160,192L208,192L208,176L256,176L256,144L240,144z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M32,160L32,176L112,176L112,160L32,160z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M128,160L128,176L144,176L144,160L128,160z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M160,160L160,176L208,176L208,160L160,160z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M0,176L0,192L16,192L16,176L0,176z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M64,176L64,192L80,192L80,176L64,176z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M128,176L128,192L160,192L160,176L128,176M208,176L208,192L240,192L240,176L208,176z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M240,176L240,192L256,192L256,176L240,176z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M16,192L16,208L48,208L48,192L16,192z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M48,192L48,208L96,208L96,192L48,192z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M96,192L96,208L112,208L112,224L48,224L48,240L0,240L0,256L64,256L64,240L128,240L128,224L160,224L160,208L176,208L176,224L192,224L192,208L240,208L240,192L160,192L160,208L128,208L128,192L96,192z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M240,192L240,208L256,208L256,192L240,192z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M16,208L16,224L96,224L96,208L16,208z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M96,208L96,224L112,224L112,208L96,208z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M160,208L160,224L176,224L176,208L160,208z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M192,208L192,240L208,240L208,224L224,224L224,208L192,208z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M224,208L224,224L256,224L256,208L224,208z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M128,224L128,240L112,240L112,256L176,256L176,240L160,240L160,224L128,224z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M160,224L160,240L192,240L192,224L160,224z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#747474"
-        android:pathData="M208,224L208,240L176,240L176,256L224,256L224,240L240,240L240,224L208,224z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#7f7f7f"
-        android:pathData="M240,224L240,240L224,240L224,256L256,256L256,224L240,224z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#686868"
-        android:pathData="M64,240L64,256L80,256L80,240L64,240z" android:strokeColor="#00000000"/>
-    <path android:fillColor="#8f8f8f"
-        android:pathData="M80,240L80,256L112,256L112,240L80,240z" android:strokeColor="#00000000"/>
-</vector>
diff --git a/android/res/drawable-anydpi-v26/ic_launcher_foreground.xml b/android/res/drawable-anydpi-v26/ic_launcher_foreground.xml
deleted file mode 100644 (file)
index ce92ea1..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="512dp"
-    android:height="512dp"
-    android:viewportWidth="512"
-    android:viewportHeight="512">
-  <path
-      android:pathData="M160,160L160,176L144,176L144,224L160,224L160,240L176,240L176,256L192,256L192,272L208,272L208,256L192,256L192,240L176,240L176,224L160,224L160,176L176,176L176,160L160,160z"
-      android:fillColor="#17172d"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M176,160L176,176L208,176L208,192L224,192L224,208L240,208L240,224L272,224L272,208L288,208L288,192L304,192L304,176L336,176L336,160L304,160L304,176L288,176L288,192L272,192L272,208L240,208L240,192L224,192L224,176L208,176L208,160L176,160z"
-      android:fillColor="#0e3f36"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M336,160L336,176L352,176L352,224L336,224L336,240L320,240L320,256L304,256L304,272L320,272L320,256L336,256L336,240L352,240L352,224L368,224L368,176L352,176L352,160L336,160z"
-      android:fillColor="#17172d"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M160,176L160,192L176,192L176,208L192,208L192,224L208,224L208,240L224,240L224,224L208,224L208,208L192,208L192,192L176,192L176,176L160,176z"
-      android:fillColor="#27b29a"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M176,176L176,192L208,192L208,208L224,208L224,224L240,224L240,208L224,208L224,192L208,192L208,176L176,176M304,176L304,192L288,192L288,208L272,208L272,224L288,224L288,208L304,208L304,192L336,192L336,224L320,224L320,240L304,240L304,256L320,256L320,240L336,240L336,224L352,224L352,192L336,192L336,176L304,176z"
-      android:fillColor="#33ebcb"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M336,176L336,192L352,192L352,176L336,176z"
-      android:fillColor="#27b29a"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M160,192L160,224L176,224L176,240L192,240L192,256L208,256L208,240L192,240L192,224L176,224L176,192L160,192z"
-      android:fillColor="#33ebcb"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M192,192L192,208L208,208L208,192L192,192M304,192L304,208L288,208L288,224L304,224L304,240L320,240L320,224L336,224L336,208L320,208L320,192L304,192z"
-      android:fillColor="#2bc7ac"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M320,192L320,208L336,208L336,192L320,192z"
-      android:fillColor="#27b29a"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M176,208L176,224L192,224L192,208L176,208M208,208L208,224L224,224L224,208L208,208z"
-      android:fillColor="#2bc7ac"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M304,208L304,224L320,224L320,208L304,208z"
-      android:fillColor="#27b29a"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M192,224L192,240L208,240L208,224L192,224z"
-      android:fillColor="#2bc7ac"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M224,224L224,240L240,240L240,256L256,256L256,272L272,272L272,288L288,288L288,304L304,304L304,320L320,320L320,336L336,336L336,352L352,352L352,320L320,320L320,304L304,304L304,288L288,288L288,272L272,272L272,240L288,240L288,224L272,224L272,240L240,240L240,224L224,224z"
-      android:fillColor="#493615"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M240,224L240,240L272,240L272,224L240,224z"
-      android:fillColor="#000000"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M288,224L288,240L304,240L304,224L288,224z"
-      android:fillColor="#27b29a"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M208,240L208,256L224,256L224,272L240,272L240,304L224,304L224,320L208,320L208,336L192,336L192,352L176,352L176,368L208,368L208,336L224,336L224,320L240,320L240,304L272,304L272,320L288,320L288,336L304,336L304,368L336,368L336,352L320,352L320,336L304,336L304,320L288,320L288,304L272,304L272,288L256,288L256,272L240,272L240,256L224,256L224,240L208,240z"
-      android:fillColor="#281e0b"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M224,240L224,256L240,256L240,240L224,240M272,240L272,256L288,256L288,240L272,240z"
-      android:fillColor="#896727"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M288,240L288,256L304,256L304,240L288,240z"
-      android:fillColor="#281e0b"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M240,256L240,272L256,272L256,256L240,256z"
-      android:fillColor="#896727"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M272,256L272,272L288,272L288,256L272,256z"
-      android:fillColor="#281e0b"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M224,272L224,288L208,288L208,304L192,304L192,320L160,320L160,352L176,352L176,336L192,336L192,320L208,320L208,304L224,304L224,288L240,288L240,272L224,272z"
-      android:fillColor="#493615"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M256,272L256,288L272,288L272,272L256,272z"
-      android:fillColor="#684e1e"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M224,288L224,304L240,304L240,288L224,288M272,288L272,304L288,304L288,288L272,288z"
-      android:fillColor="#896727"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M208,304L208,320L224,320L224,304L208,304M288,304L288,320L304,320L304,304L288,304z"
-      android:fillColor="#684e1e"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M192,320L192,336L208,336L208,320L192,320M304,320L304,336L320,336L320,320L304,320M176,336L176,352L192,352L192,336L176,336M320,336L320,352L336,352L336,336L320,336z"
-      android:fillColor="#896727"
-      android:strokeColor="#00000000"/>
-</vector>
diff --git a/android/res/drawable-hdpi/ic_launcher.png b/android/res/drawable-hdpi/ic_launcher.png
deleted file mode 100644 (file)
index ace457e..0000000
Binary files a/android/res/drawable-hdpi/ic_launcher.png and /dev/null differ
diff --git a/android/res/drawable-mdpi/ic_launcher.png b/android/res/drawable-mdpi/ic_launcher.png
deleted file mode 100644 (file)
index ed09826..0000000
Binary files a/android/res/drawable-mdpi/ic_launcher.png and /dev/null differ
diff --git a/android/res/drawable-xhdpi/ic_launcher.png b/android/res/drawable-xhdpi/ic_launcher.png
deleted file mode 100644 (file)
index 190d16d..0000000
Binary files a/android/res/drawable-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/res/drawable-xxhdpi/ic_launcher.png b/android/res/drawable-xxhdpi/ic_launcher.png
deleted file mode 100644 (file)
index b58006b..0000000
Binary files a/android/res/drawable-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/res/drawable-xxxhdpi/ic_launcher.png b/android/res/drawable-xxxhdpi/ic_launcher.png
deleted file mode 100644 (file)
index a4b6775..0000000
Binary files a/android/res/drawable-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/src/debug/res/drawable-hdpi/ic_launcher.png b/android/src/debug/res/drawable-hdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..595b5c6
Binary files /dev/null and b/android/src/debug/res/drawable-hdpi/ic_launcher.png differ
diff --git a/android/src/debug/res/drawable-mdpi/ic_launcher.png b/android/src/debug/res/drawable-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..76e15c3
Binary files /dev/null and b/android/src/debug/res/drawable-mdpi/ic_launcher.png differ
diff --git a/android/src/debug/res/drawable-xhdpi/ic_launcher.png b/android/src/debug/res/drawable-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..1c55208
Binary files /dev/null and b/android/src/debug/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/android/src/debug/res/drawable-xxhdpi/ic_launcher.png b/android/src/debug/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..94f1374
Binary files /dev/null and b/android/src/debug/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/android/src/debug/res/drawable-xxxhdpi/ic_launcher.png b/android/src/debug/res/drawable-xxxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..88ddb67
Binary files /dev/null and b/android/src/debug/res/drawable-xxxhdpi/ic_launcher.png differ
diff --git a/android/src/debug/res/drawable/ic_launcher_foreground.xml b/android/src/debug/res/drawable/ic_launcher_foreground.xml
new file mode 100644 (file)
index 0000000..c963cce
--- /dev/null
@@ -0,0 +1,162 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="32"
+    android:viewportHeight="32">
+  <path
+      android:pathData="m19,10c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM19,11c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1zM21,12c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM21,13c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#715a47"/>
+  <path
+      android:pathData="m21,10c0,0.333 0,0.667 0,1 -0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 -0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 -0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.667,0 1.333,0 2,0 0,-0.333 0,-0.667 0,-1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 0.333,0 0.667,0 1,0 0,-0.667 0,-1.333 0,-2 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#5d493c"/>
+  <path
+      android:pathData="m18,12c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#41352b"/>
+  <path
+      android:pathData="m19,11c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#41352b"/>
+  <path
+      android:pathData="m21,9c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM22,10c0,1 0,2 0,3 0.333,0 0.667,0 1,0 0,-1 0,-2 0,-3 -0.333,0 -0.667,0 -1,0zM22,13c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1zM21,14c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1zM21,10c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1zM17,13c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM18,14c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m20,9c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM17,12c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM19,14c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#675244"/>
+  <path
+      android:pathData="m16,15c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM18,15c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM14,18c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#b49883"/>
+  <path
+      android:pathData="m17,14c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM15,18c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#d6ceb9"/>
+  <path
+      android:pathData="m17,15c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM14,17c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#e8e3d4"/>
+  <path
+      android:pathData="m17,16c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#c5b79e"/>
+  <path
+      android:pathData="m14,19c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM14,20c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1zM13,21c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1zM12,22c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1zM11,23c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#41352b"/>
+  <path
+      android:pathData="m9,22c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m10,21c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m11,20c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m12,19c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m13,18c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#5d493c"/>
+  <path
+      android:pathData="m13,19c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM13,20c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#715a47"/>
+  <path
+      android:pathData="m11,21c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM11,22c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1zM10,23c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#675244"/>
+  <path
+      android:pathData="m12,10c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM13,11c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM10,12c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM11,13c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#715a47"/>
+  <path
+      android:pathData="m10,10c0,0.667 0,1.333 0,2 0.333,0 0.667,0 1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,0.333 0,0.667 0,1 0.667,0 1.334,0 2,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#5d493c"/>
+  <path
+      android:pathData="m13,12c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#41352b"/>
+  <path
+      android:pathData="m12,11c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#41352b"/>
+  <path
+      android:pathData="m10,9c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM11,10c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM10,10c-0.333,0 -0.667,0 -1,0 0,1 0,2 0,3 0.333,0 0.667,0 1,0 0,-1 0,-2 0,-3zM10,13c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM11,14c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM14,13c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM14,14c-0.333,0 -0.667,0 -1,0 0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m11,9c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM14,12c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM12,14c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#675244"/>
+  <path
+      android:pathData="m13,15c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM15,15c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM16,17c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM17,18c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#b49883"/>
+  <path
+      android:pathData="m14,14c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM16,18c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#d6ceb9"/>
+  <path
+      android:pathData="m14,15c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM17,17c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#e8e3d4"/>
+  <path
+      android:pathData="m14,16c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#c5b79e"/>
+  <path
+      android:pathData="m15,16c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#715a47"/>
+  <path
+      android:pathData="m15,17c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m16,16c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#675244"/>
+  <path
+      android:pathData="m17,19c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM18,20c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM19,21c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM20,22c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM21,23c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#41352b"/>
+  <path
+      android:pathData="m22,22c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m21,21c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m20,20c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m19,19c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m18,18c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#5d493c"/>
+  <path
+      android:pathData="m18,19c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM19,20c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#715a47"/>
+  <path
+      android:pathData="m20,21c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM21,22c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0zM22,23c0,0.333 0,0.667 0,1 0.333,0 0.667,0 1,0 0,-0.333 0,-0.667 0,-1 -0.333,0 -0.667,0 -1,0z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#675244"/>
+</vector>
similarity index 91%
rename from android/AndroidManifest.xml
rename to android/src/main/AndroidManifest.xml
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"
 <?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"
 
     <application
             android:allowBackup="true"
@@ -9,6 +9,7 @@
             android:theme="@style/GdxTheme" tools:ignore="GoogleAppIndexingWarning"
             android:fullBackupContent="@xml/backup_descriptor">
         <activity
             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"
                 android:name="ru.deadsoftware.cavedroid.AndroidLauncher"
                 android:label="@string/app_name"
                 android:screenOrientation="sensorLandscape"
diff --git a/android/src/main/assets b/android/src/main/assets
new file mode 120000 (symlink)
index 0000000..2978ef3
--- /dev/null
@@ -0,0 +1 @@
+../../../assets
\ No newline at end of file
diff --git a/android/src/main/kotlin/ru/deadsoftware/cavedroid/AndroidLauncher.kt b/android/src/main/kotlin/ru/deadsoftware/cavedroid/AndroidLauncher.kt
new file mode 100644 (file)
index 0000000..b6fff78
--- /dev/null
@@ -0,0 +1,34 @@
+package ru.deadsoftware.cavedroid
+
+import android.os.Bundle
+import com.badlogic.gdx.backends.android.AndroidApplication
+import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
+
+class AndroidLauncher : AndroidApplication() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val gameDataDirectoryPath = packageManager.getPackageInfo(packageName, 0)
+            .applicationInfo.dataDir;
+
+        val config = AndroidApplicationConfiguration()
+        config.useImmersiveMode = true
+
+        initialize(
+            /* listener = */ CaveGame(
+                gameDataDirectoryPath = gameDataDirectoryPath,
+                isTouchScreen = true,
+                isDebug = BuildConfig.DEBUG,
+                preferencesStore = AndroidPreferencesStore(applicationContext)
+            ),
+            /* config = */ config
+        )
+    }
+
+    @Suppress("OVERRIDE_DEPRECATION")
+    override fun onBackPressed() {
+        // ignore
+    }
+
+}
\ No newline at end of file
diff --git a/android/src/main/kotlin/ru/deadsoftware/cavedroid/AndroidPreferencesStore.kt b/android/src/main/kotlin/ru/deadsoftware/cavedroid/AndroidPreferencesStore.kt
new file mode 100644 (file)
index 0000000..4b16659
--- /dev/null
@@ -0,0 +1,26 @@
+package ru.deadsoftware.cavedroid
+
+import android.content.Context
+import ru.deadsoftware.cavedroid.prefs.PreferencesStore
+
+class AndroidPreferencesStore(
+    private val context: Context
+) : PreferencesStore {
+
+    private val sharedPreferences by lazy { context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE) }
+
+    override fun getPreference(key: String): String? {
+        return sharedPreferences.getString(key, null)
+    }
+
+    override fun setPreference(key: String, value: String?) {
+        with(sharedPreferences.edit()) {
+            putString(key, value)
+            apply()
+        }
+    }
+
+    private companion object {
+        private const val SHARED_PREFS_NAME = "cavedroid_prefs"
+    }
+}
\ No newline at end of file
diff --git a/android/src/main/res/drawable-hdpi/ic_launcher.png b/android/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..2b9989e
Binary files /dev/null and b/android/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/android/src/main/res/drawable-mdpi/ic_launcher.png b/android/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..4657268
Binary files /dev/null and b/android/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/android/src/main/res/drawable-xhdpi/ic_launcher.png b/android/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..739b6ce
Binary files /dev/null and b/android/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/android/src/main/res/drawable-xxhdpi/ic_launcher.png b/android/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..1c1aa90
Binary files /dev/null and b/android/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/android/src/main/res/drawable-xxxhdpi/ic_launcher.png b/android/src/main/res/drawable-xxxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..0f64585
Binary files /dev/null and b/android/src/main/res/drawable-xxxhdpi/ic_launcher.png differ
diff --git a/android/src/main/res/drawable/ic_launcher_background.xml b/android/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644 (file)
index 0000000..bae566f
--- /dev/null
@@ -0,0 +1,54 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="16"
+    android:viewportHeight="16">
+  <path
+      android:pathData="m7,0v1h1L8,0ZM13,0v1h1,1L15,0L14,0ZM0,1L0,2 0,3L1,3L1,2 1,1ZM9,1L9,2h1L10,1ZM10,2v1h1v-1zM6,2v1h1v1h1v-1,-1h-1zM12,2v1,1h1v-1h1L14,2L13,2ZM0,4L0,5L1,5L1,4ZM4,4v1h1v1h1L6,5 6,4h-1zM9,4v1,1h1L10,5 10,4ZM4,7v1h1v1h1L6,8 6,7h-1zM8,7v1h-1v1h0.004,0.996 0.004,0.996L9,8 9,7ZM14,7v1,1h1L15,8 15,7ZM11,10v1,1h1L12,11 12,10ZM14,10v1,1h1L15,11 15,10ZM8,12v1h-1v1h1,1L9,13 9,12ZM3,13v1,1h1,1L5,14L4,14L4,13ZM11,14v1h1v-1zM7,15v1h1L8,15ZM4,11v1h1v-1zM2,11v1h1L3,11ZM1,10L1,11L2,11L2,10ZM2,10v1h1L3,10Z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#938983"/>
+  <path
+      android:pathData="M1,0L1,1L2,1L2,2L1,2v1L0,3v1L1,4L1,5L0,5v1,1L1,7L1,6L2,6L2,5 2,4 2,3h1L3,2 3,1 3,0L2,0ZM2,6v1,1h1v-1h1,1 1L6,6L5,6 4,6 3,6ZM4,1v1,1 1h1,1 1L7,3L6,3 5,3L5,2 5,1ZM10,1L10,2L11,2L11,1ZM10,2h-1v1,1h1,1L11,3L10,3ZM9,4L8,4L8,5L9,5ZM12,1L12,2h1,1 1L15,1L14,1 13,1ZM13,3v1h-1v1h1,0.996L13.996,4h0.004L14,3ZM7.002,6v1h0.998,1v1h1L10,7 10,6L9,6 8,6ZM10.998,6v1h0.002v1h1v-1h-0.002L11.998,6h-0.998zM12.998,6v1,1h0.002,0.998L13.998,7 13.998,6ZM14,6v1h1L15,6ZM15,7v1,1h-1v1h1,1v-1h-0.004L15.996,8h0.004L16,7ZM4,8v1,1h1,1L6,9L5,9L5,8ZM1,9v1h1,1v-1h-1zM7.004,9v1h-0.004v1h1,1L9,10h-0.996v-1h-0.004zM12,9v1,1L13,11L13,10 13,9ZM1,11v1,1 1L2,14L2,13h1,1 1L5,12L4,12 3,12 2,12v-1zM15,11v1h-1v1h1,1v-1,-1zM7,12v1h1v-1zM11,12v1,1h1,1L13,13h-1v-1zM11,14L10,14v1,1h1,1v-1h-1zM7,14v1h1v1L9,16L9,15 9,14L8,14ZM14,14v1h1v1h1L16,15 16,14L15,14ZM3,15v1h1,1L5,15L4,15ZM6,0L6,1L7,1L7,0Z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#87807c"/>
+  <path
+      android:pathData="M1,1L1,2L2,2L2,1ZM8,5v1h1v-1zM7,7v1h1v-1zM15,10v1h1L16,10ZM12,11v1h1v-1zM6,15v1L7,16L7,15ZM4,13v1L5,14L5,13Z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#a59790"/>
+  <path
+      android:pathData="m7,4v1h1L8,4ZM7,5v1h1L8,5ZM0,0L0,1L1,1L1,0ZM4,0L4,1L5,1L5,2h1,1 1v1,1L9,4L9,3 9,2 9,1h1,1L11,0L10,0 9,0 8,0L8,1L7,1 6,1L6,0L5,0ZM15,0v1,1 1,1 1h0.002v1h-0.002v1h1L16,6h0.002v-1h-0.002L16,4 16,3 16,2 16,1 16,0ZM11,3v1L10,4v1h1v1h0.998,0.002v1,1h-1v-1h-0.002,-0.998v1h-1v1h0.002v1,1h0.998,0.002L10.002,10h0.998,1v-1h1L13,8h-0.002L12.998,7 12.998,6h1v-1h-0.002,-0.996 -1L12,4 12,3ZM9,9L8.004,9v1L9,10ZM3,7v1,1 1,1 1h1v-1h1v1,1 1,1 1h1L6,15L7,15L7,14 7,13L6,13L6,12 6,11L7,11L7,10 7,9 7,8 7,7L6,7v1,1 1L5,10 4,10L4,9 4,8 4,7ZM0,11v1,1 1,1 1L1,16 2,16L2,15L1,15L1,14 1,13 1,12 1,11ZM8,11v1h1v-1zM13,11v1h-1v1h1v1h-1v1,1h1,1 1v-1h-1v-1h1,1L16,13h-1,-1v-1,-1zM10,12v1,1L11,14L11,13 11,12ZM2,13v1h1L3,13ZM9,15v1h1L10,15ZM4,5v1h1v-1zM3,5v1h1v-1zM2,5v1h1v-1zM2,4v1h1L3,4ZM0,7v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#7e7471"/>
+  <path
+      android:pathData="M3,1L3,2h1L4,1ZM13.996,4v1h0.004,0.996L14.996,4L14,4ZM6,5v1L7,6L7,5ZM1,8L1,9L2,9L2,8ZM1,9L0,9v1L1,10ZM9,13v1h1L10,13Z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#6b6161"/>
+  <path
+      android:pathData="m3,0v1h1L4,0ZM11,0v1,1 1h1L12,2 12,1L13,1L13,0L12,0ZM3,2v1h-1v1h1v1h1L4,4 4,3 4,2ZM5,2v1h1v-1zM14,2v1,1h0.996,0.004L15,3 15,2ZM6,4L6,5L7,5L7,4ZM10,5v1,1L10.998,7L10.998,6h0.002v-1zM14,5v1h1v-1h-0.004zM1,6v1,1L2,8L2,7 2,6ZM2,8v1h1L3,8ZM1,8L0,8L0,9L1,9ZM6,6L6,7L7,7L7,6ZM13,8v1,1 1h1L14,10h-0.002L13.998,9 13.998,8ZM0,10L0,11L1,11L1,10ZM10.002,10v1h-0.002,-0.998 -0.002v1,1h1L10,12L11,12L11,11 11,10ZM6,11v1,1h1v-1h1v-1h-1zM1,14v1L2,15v1h1v-1,-1h-1zM9,14v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#706c66"/>
+  <path
+      android:pathData="m7,0v1h1L8,0ZM13,0v1h1,1L15,0L14,0ZM0,1L0,2 0,3L1,3L1,2 1,1ZM9,1L9,2h1L10,1ZM10,2v1h1v-1zM6,2v1h1v1h1v-1,-1h-1zM12,2v1,1h1v-1h1L14,2L13,2ZM0,4L0,5L1,5L1,4ZM4,4v1h1v1h1L6,5 6,4h-1zM9,4v1,1h1L10,5 10,4ZM4,7v1h1v1h1L6,8 6,7h-1zM8,7v1h-1v1h0.004,0.996 0.004,0.996L9,8 9,7ZM14,7v1,1h1L15,8 15,7ZM11,10v1,1h1L12,11 12,10ZM14,10v1,1h1L15,11 15,10ZM8,12v1h-1v1h1,1L9,13 9,12ZM3,13v1,1h1,1L5,14L4,14L4,13ZM11,14v1h1v-1zM7,15v1h1L8,15ZM4,11v1h1v-1zM2,11v1h1L3,11ZM1,10L1,11L2,11L2,10ZM2,10v1h1L3,10Z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#938983"/>
+  <path
+      android:pathData="M1,0L1,1L2,1L2,2L1,2v1L0,3v1L1,4L1,5L0,5v1,1L1,7L1,6L2,6L2,5 2,4 2,3h1L3,2 3,1 3,0L2,0ZM2,6v1,1h1v-1h1,1 1L6,6L5,6 4,6 3,6ZM4,1v1,1 1h1,1 1L7,3L6,3 5,3L5,2 5,1ZM10,1L10,2L11,2L11,1ZM10,2h-1v1,1h1,1L11,3L10,3ZM9,4L8,4L8,5L9,5ZM12,1L12,2h1,1 1L15,1L14,1 13,1ZM13,3v1h-1v1h1,0.996L13.996,4h0.004L14,3ZM7.002,6v1h0.998,1v1h1L10,7 10,6L9,6 8,6ZM10.998,6v1h0.002v1h1v-1h-0.002L11.998,6h-0.998zM12.998,6v1,1h0.002,0.998L13.998,7 13.998,6ZM14,6v1h1L15,6ZM15,7v1,1h-1v1h1,1v-1h-0.004L15.996,8h0.004L16,7ZM4,8v1,1h1,1L6,9L5,9L5,8ZM1,9v1h1,1v-1h-1zM7.004,9v1h-0.004v1h1,1L9,10h-0.996v-1h-0.004zM12,9v1,1L13,11L13,10 13,9ZM1,11v1,1 1L2,14L2,13h1,1 1L5,12L4,12 3,12 2,12v-1zM15,11v1h-1v1h1,1v-1,-1zM7,12v1h1v-1zM11,12v1,1h1,1L13,13h-1v-1zM11,14L10,14v1,1h1,1v-1h-1zM7,14v1h1v1L9,16L9,15 9,14L8,14ZM14,14v1h1v1h1L16,15 16,14L15,14ZM3,15v1h1,1L5,15L4,15ZM6,0L6,1L7,1L7,0Z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#87807c"/>
+  <path
+      android:pathData="M1,1L1,2L2,2L2,1ZM8,5v1h1v-1zM7,7v1h1v-1zM15,10v1h1L16,10ZM12,11v1h1v-1zM6,15v1L7,16L7,15ZM4,13v1L5,14L5,13Z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#a59790"/>
+  <path
+      android:pathData="m7,4v1h1L8,4ZM7,5v1h1L8,5ZM0,0L0,1L1,1L1,0ZM4,0L4,1L5,1L5,2h1,1 1v1,1L9,4L9,3 9,2 9,1h1,1L11,0L10,0 9,0 8,0L8,1L7,1 6,1L6,0L5,0ZM15,0v1,1 1,1 1h0.002v1h-0.002v1h1L16,6h0.002v-1h-0.002L16,4 16,3 16,2 16,1 16,0ZM11,3v1L10,4v1h1v1h0.998,0.002v1,1h-1v-1h-0.002,-0.998v1h-1v1h0.002v1,1h0.998,0.002L10.002,10h0.998,1v-1h1L13,8h-0.002L12.998,7 12.998,6h1v-1h-0.002,-0.996 -1L12,4 12,3ZM9,9L8.004,9v1L9,10ZM3,7v1,1 1,1 1h1v-1h1v1,1 1,1 1h1L6,15L7,15L7,14 7,13L6,13L6,12 6,11L7,11L7,10 7,9 7,8 7,7L6,7v1,1 1L5,10 4,10L4,9 4,8 4,7ZM0,11v1,1 1,1 1L1,16 2,16L2,15L1,15L1,14 1,13 1,12 1,11ZM8,11v1h1v-1zM13,11v1h-1v1h1v1h-1v1,1h1,1 1v-1h-1v-1h1,1L16,13h-1,-1v-1,-1zM10,12v1,1L11,14L11,13 11,12ZM2,13v1h1L3,13ZM9,15v1h1L10,15ZM4,5v1h1v-1zM3,5v1h1v-1zM2,5v1h1v-1zM2,4v1h1L3,4ZM0,7v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#7e7471"/>
+  <path
+      android:pathData="M3,1L3,2h1L4,1ZM13.996,4v1h0.004,0.996L14.996,4L14,4ZM6,5v1L7,6L7,5ZM1,8L1,9L2,9L2,8ZM1,9L0,9v1L1,10ZM9,13v1h1L10,13Z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#6b6161"/>
+  <path
+      android:pathData="m3,0v1h1L4,0ZM11,0v1,1 1h1L12,2 12,1L13,1L13,0L12,0ZM3,2v1h-1v1h1v1h1L4,4 4,3 4,2ZM5,2v1h1v-1zM14,2v1,1h0.996,0.004L15,3 15,2ZM6,4L6,5L7,5L7,4ZM10,5v1,1L10.998,7L10.998,6h0.002v-1zM14,5v1h1v-1h-0.004zM1,6v1,1L2,8L2,7 2,6ZM2,8v1h1L3,8ZM1,8L0,8L0,9L1,9ZM6,6L6,7L7,7L7,6ZM13,8v1,1 1h1L14,10h-0.002L13.998,9 13.998,8ZM0,10L0,11L1,11L1,10ZM10.002,10v1h-0.002,-0.998 -0.002v1,1h1L10,12L11,12L11,11 11,10ZM6,11v1,1h1v-1h1v-1h-1zM1,14v1L2,15v1h1v-1,-1h-1zM9,14v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#706c66"/>
+</vector>
diff --git a/android/src/main/res/drawable/ic_launcher_foreground.xml b/android/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644 (file)
index 0000000..a30acde
--- /dev/null
@@ -0,0 +1,126 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="32"
+    android:viewportHeight="32">
+  <path
+      android:pathData="m22,10v1h1v-1zM22,11h-1v1h1zM21,12h-1v1h1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#457d9e"/>
+  <path
+      android:pathData="m20,9v1h1L21,9ZM20,10h-1v1h1zM19,11h-1v1h1zM23,12v1h1v-1zM23,13h-1v1h1zM22,14h-1v1h1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#dbeefc"/>
+  <path
+      android:pathData="m23,11v1h1v-1zM21,12v1,1h1v-1,-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#77cefb"/>
+  <path
+      android:pathData="m20,10v1h1v-1zM20,11h-1v1,1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#5397c1"/>
+  <path
+      android:pathData="m21,9v1h1L22,9ZM22,11v1,1h1v-1,-1zM18,12v1h1v-1zM20,14v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#a8e2ff"/>
+  <path
+      android:pathData="m22,9v1h1v1h1L24,10 24,9h-1zM22,10h-1v1h1zM21,11h-1v1h1zM20,13v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#5faed8"/>
+  <path
+      android:pathData="m17,15v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#675244"/>
+  <path
+      android:pathData="m16,15v1h1v-1zM14,17v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#5d493c"/>
+  <path
+      android:pathData="m14,18v1h1v-1zM14,19h-1v1h1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#715a47"/>
+  <path
+      android:pathData="m12,20v1h1v-1zM12,21L11,21v1L12,22ZM11,22h-1v1h1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#675244"/>
+  <path
+      android:pathData="m15,18v1h1v-1zM15,19h-1v1h1zM14,20h-1v1h1zM13,21L12,21v1h1zM12,22L11,22v1L12,23Z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#41352b"/>
+  <path
+      android:pathData="m17,14v1h1v-1zM18,15v1h1v-1zM18,16h-1v1h1zM13,18v1h1v-1zM13,19L12,19v1h1zM12,20L11,20v1L12,21ZM11,21h-1v1h1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m18,13v1h1v-1zM19,14v1h1v-1zM10,23v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#2c2c2c"/>
+  <path
+      android:pathData="m19,13v1h1v-1zM19,14h-1v1h1zM9,23v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#494949"/>
+  <path
+      android:pathData="m9,22v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#3d3d3d"/>
+  <path
+      android:pathData="m9,10v1h1v-1zM10,11v1h1v-1zM11,12v1L12,13v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#457d9e"/>
+  <path
+      android:pathData="M11,9L11,10L12,10L12,9ZM12,10v1h1v-1zM13,11v1h1v-1zM8,12v1h1v-1zM9,13v1h1v-1zM10,14v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#dbeefc"/>
+  <path
+      android:pathData="m8,11v1h1v-1zM10,12v1,1h1v-1,-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#77cefb"/>
+  <path
+      android:pathData="m11,10v1L12,11v-1zM12,11v1,1h1v-1,-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#5397c1"/>
+  <path
+      android:pathData="m10,9v1h1L11,9ZM9,11v1,1h1v-1,-1zM13,12v1h1v-1zM11,14v1L12,15v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#a8e2ff"/>
+  <path
+      android:pathData="m8,9v1,1h1v-1h1L10,9h-1zM10,10v1h1v-1zM11,11v1L12,12v-1zM11,13v1L12,14v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#5faed8"/>
+  <path
+      android:pathData="m14,15v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#675244"/>
+  <path
+      android:pathData="m15,15v1h1v-1zM16,16v1h1v-1zM17,17v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#5d493c"/>
+  <path
+      android:pathData="m15,16v1h1v-1zM16,17v1h1v-1zM17,18v1h1v-1zM18,19v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#715a47"/>
+  <path
+      android:pathData="m19,20v1h1v-1zM20,21v1h1v-1zM21,22v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#675244"/>
+  <path
+      android:pathData="m16,18v1h1v-1zM17,19v1h1v-1zM18,20v1h1v-1zM19,21v1h1v-1zM20,22v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#41352b"/>
+  <path
+      android:pathData="m14,14v1h1v-1zM14,15h-1v1h1zM14,16v1h1v-1zM15,17v1h1v-1zM18,18v1h1v-1zM19,19v1h1v-1zM20,20v1h1v-1zM21,21v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#514035"/>
+  <path
+      android:pathData="m13,13v1h1v-1zM13,14L12,14v1h1zM21,23v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#2c2c2c"/>
+  <path
+      android:pathData="m12,13v1h1v-1zM13,14v1h1v-1zM22,23v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#494949"/>
+  <path
+      android:pathData="m22,22v1h1v-1z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#3d3d3d"/>
+</vector>
diff --git a/android/src/ru/deadsoftware/cavedroid/AndroidLauncher.java b/android/src/ru/deadsoftware/cavedroid/AndroidLauncher.java
deleted file mode 100644 (file)
index fcf0831..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-package ru.deadsoftware.cavedroid;
-
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import com.badlogic.gdx.backends.android.AndroidApplication;
-import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
-
-public class AndroidLauncher extends AndroidApplication {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
-        config.hideStatusBar = true;
-        config.useImmersiveMode = true;
-        String gameFolder = "";
-        try {
-            gameFolder = getPackageManager().getPackageInfo(getPackageName(), 0).applicationInfo.dataDir;
-        } catch (PackageManager.NameNotFoundException e) {
-            e.printStackTrace();
-            exit();
-        }
-        CaveGame caveGame = new CaveGame(gameFolder, true);
-        caveGame.setDebug(BuildConfig.DEBUG);
-        initialize(caveGame, config);
-    }
-
-    @Override
-    public void onBackPressed() {
-    }
-}
similarity index 100%
rename from android/assets/font.fnt
rename to assets/font.fnt
similarity index 100%
rename from android/assets/font.png
rename to assets/font.png
diff --git a/assets/icons/icon128.png b/assets/icons/icon128.png
new file mode 100644 (file)
index 0000000..08d0b74
Binary files /dev/null and b/assets/icons/icon128.png differ
diff --git a/assets/icons/icon256.png b/assets/icons/icon256.png
new file mode 100644 (file)
index 0000000..4e36964
Binary files /dev/null and b/assets/icons/icon256.png differ
diff --git a/assets/icons/icon512.png b/assets/icons/icon512.png
new file mode 100644 (file)
index 0000000..35aa45b
Binary files /dev/null and b/assets/icons/icon512.png differ
diff --git a/assets/joy_background.png b/assets/joy_background.png
new file mode 100644 (file)
index 0000000..b1ea80b
Binary files /dev/null and b/assets/joy_background.png differ
diff --git a/assets/joy_stick.png b/assets/joy_stick.png
new file mode 100644 (file)
index 0000000..525dd7f
Binary files /dev/null and b/assets/joy_stick.png differ
diff --git a/assets/json/crafting.json b/assets/json/crafting.json
new file mode 100644 (file)
index 0000000..9a67e01
--- /dev/null
@@ -0,0 +1,110 @@
+{
+  "planks_oak": {
+    "input": ["log_oak", "none", "none", "none", "none", "none", "none", "none", "none"],
+    "count": 4
+  },
+  "planks_spruce": {
+    "input": ["log_spruce", "none", "none", "none", "none", "none", "none", "none", "none"],
+    "count": 4
+  },
+  "stick": {
+    "input": ["planks.*", "none", "none", "planks.*", "none", "none", "none", "none", "none"],
+    "count": 4
+  },
+  "wood_pickaxe": {
+    "input": ["planks.*", "planks.*", "planks.*", "none", "stick", "none", "none", "stick", "none"],
+    "count": 59
+  },
+  "wood_axe": {
+    "input": ["planks.*", "planks.*", "none", "planks.*", "stick", "none", "none", "stick", "none"],
+    "count": 60
+  },
+  "wood_sword": {
+    "input": ["planks.*", "none", "none", "planks.*", "none", "none", "stick", "none", "none"],
+    "count": 60
+  },
+  "wood_shovel": {
+    "input": ["planks.*", "none", "none", "stick", "none", "none", "stick", "none", "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": ["cobblestone", "none", "none", "cobblestone", "none", "none", "stick", "none", "none"],
+    "count": 132
+  },
+  "stone_shovel": {
+    "input": ["cobblestone", "none", "none", "stick", "none", "none", "stick", "none", "none"],
+    "count": 131
+  },
+  "iron_pickaxe": {
+    "input": ["iron_ingot", "iron_ingot", "iron_ingot", "none", "stick", "none", "none", "stick", "none"],
+    "count": 251
+  },
+  "iron_axe": {
+    "input": ["iron_ingot", "iron_ingot", "none", "iron_ingot", "stick", "none", "none", "stick", "none"],
+    "count": 250
+  },
+  "iron_sword": {
+    "input": ["iron_ingot", "none", "none", "iron_ingot", "none", "none", "stick", "none", "none"],
+    "count": 251
+  },
+  "iron_shovel": {
+    "input": ["iron_ingot", "none", "none", "stick", "none", "none", "stick", "none", "none"],
+    "count": 250
+  },
+  "gold_pickaxe": {
+    "input": ["gold_ingot", "gold_ingot", "gold_ingot", "none", "stick", "none", "none", "stick", "none"],
+    "count": 33
+  },
+  "gold_axe": {
+    "input": ["gold_ingot", "gold_ingot", "none", "gold_ingot", "stick", "none", "none", "stick", "none"],
+    "count": 32
+  },
+  "gold_sword": {
+    "input": ["gold_ingot", "none", "none", "gold_ingot", "none", "none", "stick", "none", "none"],
+    "count": 33
+  },
+  "gold_shovel": {
+    "input": ["gold_ingot", "none", "none", "stick", "none", "none", "stick", "none", "none"],
+    "count": 32
+  },
+  "diamond_pickaxe": {
+    "input": ["diamond", "diamond", "diamond", "none", "stick", "none", "none", "stick", "none"],
+    "count": 1562
+  },
+  "diamond_axe": {
+    "input": ["diamond", "diamond", "none", "diamond", "stick", "none", "none", "stick", "none"],
+    "count": 1561
+  },
+  "diamond_sword": {
+    "input": ["diamond", "none", "none", "diamond", "none", "none", "stick", "none", "none"],
+    "count": 1562
+  },
+  "diamond_shovel": {
+    "input": ["diamond", "none", "none", "stick", "none", "none", "stick", "none", "none"],
+    "count": 1561
+  },
+  "snow_block": {
+    "input": ["snowball", "snowball", "none", "snowball", "snowball", "none", "none", "none", "none"],
+    "count": 1
+  },
+  "crafting_table": {
+    "input": ["planks.*", "planks.*", "none", "planks.*", "planks.*", "none", "none", "none", "none"]
+  },
+  "furnace": {
+    "input": ["cobblestone", "cobblestone", "cobblestone", "cobblestone", "none", "cobblestone", "cobblestone", "cobblestone", "cobblestone"]
+  },
+  "chest": {
+    "input": ["planks_.*", "planks_.*", "planks_.*", "planks_.*", "none", "planks_.*", "planks_.*", "planks_.*", "planks_.*"]
+  },
+  "bed": {
+    "input": ["wool_.*", "wool_.*", "wool_.*", "planks_.*", "planks_.*", "planks_.*", "none", "none", "none"]
+  }
+}
\ No newline at end of file
diff --git a/assets/json/game_items.json b/assets/json/game_items.json
new file mode 100644 (file)
index 0000000..349daf3
--- /dev/null
@@ -0,0 +1,1439 @@
+{
+  "blocks": {
+    "none": {
+      "collision": false,
+      "transparent": true,
+      "drop": "none",
+      "texture": "none",
+      "meta": "none"
+    },
+    "stone": {
+      "hp": 450,
+      "drop": "cobblestone",
+      "texture": "stone",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "grass": {
+      "hp": 54,
+      "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,
+      "drop": "dirt",
+      "texture": "dirt",
+      "tool_level": 0,
+      "tool_type": "shovel"
+    },
+    "cobblestone": {
+      "hp": 600,
+      "drop": "cobblestone",
+      "texture": "cobblestone",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "planks_oak": {
+      "hp": 180,
+      "drop": "planks_oak",
+      "texture": "planks_oak",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "planks_spruce": {
+      "hp": 180,
+      "drop": "planks_spruce",
+      "texture": "planks_spruce",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "crafting_table": {
+      "hp": 180,
+      "drop": "crafting_table",
+      "texture": "crafting_table",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "sapling_oak": {
+      "collision": false,
+      "transparent": true,
+      "block_required": true,
+      "drop": "sapling_oak",
+      "texture": "sapling_oak",
+      "hp": 0
+    },
+    "sapling_spruce": {
+      "collision": false,
+      "transparent": true,
+      "block_required": true,
+      "drop": "sapling_spruce",
+      "texture": "sapling_spruce",
+      "hp": 0
+    },
+    "bedrock": {
+      "drop": "bedrock",
+      "texture": "bedrock"
+    },
+    "water": {
+      "collision": false,
+      "transparent": true,
+      "drop": "water",
+      "meta": "water",
+      "texture": "water_still",
+      "animated": true,
+      "frames": 16,
+      "state": 0
+    },
+    "lava": {
+      "collision": false,
+      "drop": "lava",
+      "meta": "lava",
+      "texture": "lava_still",
+      "animated": true,
+      "frames": 16,
+      "state": 0,
+      "damage": 4
+    },
+    "sand": {
+      "hp": 45,
+      "drop": "sand",
+      "texture": "sand",
+      "tool_level": 0,
+      "tool_type": "shovel",
+      "fallable": true
+    },
+    "gravel": {
+      "hp": 54,
+      "drop": "gravel",
+      "texture": "gravel",
+      "tool_level": 0,
+      "tool_type": "shovel",
+      "fallable": true
+    },
+    "gold_ore": {
+      "hp": 900,
+      "drop": "gold_ore",
+      "texture": "gold_ore",
+      "tool_level": 3,
+      "tool_type": "pickaxe"
+    },
+    "iron_ore": {
+      "hp": 900,
+      "drop": "iron_ore",
+      "texture": "iron_ore",
+      "tool_level": 2,
+      "tool_type": "pickaxe"
+    },
+    "coal_ore": {
+      "hp": 900,
+      "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,
+      "drop": "log_oak",
+      "texture": "log_oak",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "leaves_oak": {
+      "hp": 21,
+      "drop": "leaves_oak",
+      "texture": "leaves_oak",
+      "tool_level": 1,
+      "tool_type": "shears",
+      "tint": "#5AC557"
+    },
+    "log_spruce": {
+      "hp": 180,
+      "drop": "log_spruce",
+      "texture": "log_spruce",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "leaves_spruce": {
+      "hp": 21,
+      "drop": "leaves_spruce",
+      "texture": "leaves_spruce",
+      "tool_level": 1,
+      "tool_type": "shears",
+      "tint": "#486D4E"
+    },
+    "sponge": {
+      "hp": 54,
+      "drop": "sponge",
+      "texture": "sponge",
+      "tool_level": 0,
+      "tool_type": "hoe"
+    },
+    "glass": {
+      "hp": 27,
+      "transparent": true,
+      "drop": "none",
+      "texture": "glass"
+    },
+    "lapis_ore": {
+      "hp": 900,
+      "drop": "lapis",
+      "texture": "lapis_ore",
+      "tool_level": 2,
+      "tool_type": "pickaxe"
+    },
+    "lapis_block": {
+      "hp": 900,
+      "drop": "lapis_block",
+      "texture": "lapis_block",
+      "tool_level": 2,
+      "tool_type": "pickaxe"
+    },
+    "sandstone": {
+      "hp": 240,
+      "drop": "sandstone",
+      "texture": "sandstone",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "noteblock": {
+      "hp": 75,
+      "drop": "noteblock",
+      "texture": "noteblock",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "bed_l": {
+      "hp": 21,
+      "collision": false,
+      "background": true,
+      "transparent": true,
+      "drop": "bed",
+      "texture": "bed_l",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "bed_r": {
+      "hp": 21,
+      "collision": false,
+      "background": true,
+      "transparent": true,
+      "drop": "bed",
+      "texture": "bed_r",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "web": {
+      "hp": 1200,
+      "collision": false,
+      "transparent": true,
+      "drop": "web",
+      "texture": "web",
+      "tool_level": 1,
+      "tool_type": "shears"
+    },
+    "tallgrass": {
+      "collision": false,
+      "transparent": true,
+      "block_required": true,
+      "drop": "tallgrass",
+      "texture": "tallgrass",
+      "tool_level": 1,
+      "tool_type": "shears",
+      "tint": "#5AC557",
+      "hp": 0
+    },
+    "deadbush": {
+      "collision": false,
+      "transparent": true,
+      "block_required": true,
+      "drop": "deadbush",
+      "texture": "deadbush",
+      "tool_level": 1,
+      "tool_type": "shears",
+      "hp": 0
+    },
+    "bricks": {
+      "hp": 600,
+      "drop": "bricks",
+      "texture": "bricks",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "dandelion": {
+      "collision": false,
+      "transparent": true,
+      "block_required": true,
+      "drop": "dandelion",
+      "texture": "dandelion",
+      "hp": 0
+    },
+    "rose": {
+      "collision": false,
+      "transparent": true,
+      "block_required": true,
+      "drop": "rose",
+      "texture": "rose",
+      "hp": 0
+    },
+    "mushroom_brown": {
+      "collision": false,
+      "transparent": true,
+      "block_required": true,
+      "drop": "mushroom_brown",
+      "texture": "mushroom_brown",
+      "hp": 0
+    },
+    "mushroom_red": {
+      "collision": false,
+      "transparent": true,
+      "block_required": true,
+      "drop": "mushroom_red",
+      "texture": "mushroom_red",
+      "hp": 0
+    },
+    "wool_colored_white": {
+      "hp": 75,
+      "drop": "wool_colored_white",
+      "texture": "wool_colored_white",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_orange": {
+      "hp": 75,
+      "drop": "wool_colored_orange",
+      "texture": "wool_colored_orange",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_magenta": {
+      "hp": 75,
+      "drop": "wool_colored_magenta",
+      "texture": "wool_colored_magenta",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_light_blue": {
+      "hp": 75,
+      "drop": "wool_colored_light_blue",
+      "texture": "wool_colored_light_blue",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_yellow": {
+      "hp": 75,
+      "drop": "wool_colored_yellow",
+      "texture": "wool_colored_yellow",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_lime": {
+      "hp": 75,
+      "drop": "wool_colored_lime",
+      "texture": "wool_colored_lime",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_pink": {
+      "hp": 75,
+      "drop": "wool_colored_pink",
+      "texture": "wool_colored_pink",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_gray": {
+      "hp": 75,
+      "drop": "wool_colored_gray",
+      "texture": "wool_colored_gray",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_silver": {
+      "hp": 75,
+      "drop": "wool_colored_silver",
+      "texture": "wool_colored_silver",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_cyan": {
+      "hp": 75,
+      "drop": "wool_colored_cyan",
+      "texture": "wool_colored_cyan",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_purple": {
+      "hp": 75,
+      "drop": "wool_colored_purple",
+      "texture": "wool_colored_purple",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_blue": {
+      "hp": 75,
+      "drop": "wool_colored_blue",
+      "texture": "wool_colored_blue",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_brown": {
+      "hp": 75,
+      "drop": "wool_colored_brown",
+      "texture": "wool_colored_brown",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_green": {
+      "hp": 75,
+      "drop": "wool_colored_green",
+      "texture": "wool_colored_green",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_red": {
+      "hp": 75,
+      "drop": "wool_colored_red",
+      "texture": "wool_colored_red",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "wool_colored_black": {
+      "hp": 75,
+      "drop": "wool_colored_black",
+      "texture": "wool_colored_black",
+      "tool_level": 0,
+      "tool_type": "shears"
+    },
+    "gold_block": {
+      "hp": 900,
+      "drop": "gold_block",
+      "texture": "gold_block",
+      "tool_level": 3,
+      "tool_type": "pickaxe"
+    },
+    "iron_block": {
+      "hp": 1500,
+      "drop": "iron_block",
+      "texture": "iron_block",
+      "tool_level": 2,
+      "tool_type": "pickaxe"
+    },
+    "stone_slab_bottom": {
+      "top": 8,
+      "sprite_top": 8,
+      "hp": 600,
+      "transparent": true,
+      "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",
+      "drop_count": 2,
+      "texture": "stone_slab",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "sandstone_slab_bottom": {
+      "top": 8,
+      "sprite_top": 8,
+      "hp": 600,
+      "transparent": true,
+      "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,
+      "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"
+    },
+    "spruce_slab_bottom": {
+      "top": 8,
+      "sprite_top": 8,
+      "hp": 180,
+      "transparent": true,
+      "drop": "spruce_slab",
+      "meta": "slab",
+      "texture": "planks_spruce",
+      "full_block": "planks_spruce",
+      "other_part": "spruce_slab_top",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "spruce_slab_top": {
+      "bottom": 8,
+      "sprite_bottom": 8,
+      "hp": 180,
+      "transparent": true,
+      "drop": "spruce_slab",
+      "meta": "slab",
+      "texture": "planks_spruce",
+      "full_block": "planks_spruce",
+      "other_part": "spruce_slab_bottom",
+      "tool_level": 0,
+      "tool_type": "axe"
+    },
+    "cobblestone_slab_bottom": {
+      "top": 8,
+      "sprite_top": 8,
+      "hp": 600,
+      "transparent": true,
+      "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,
+      "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,
+      "drop": "stonebrick",
+      "texture": "stonebrick",
+      "tool_level": 1,
+      "tool_type": "pickaxe"
+    },
+    "stonebrick_slab_bottom": {
+      "top": 8,
+      "sprite_top": 8,
+      "hp": 450,
+      "transparent": true,
+      "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,
+      "drop": "cactus",
+      "texture": "cactus",
+      "damage": 1
+    },
+    "water_16": {
+      "collision": false,
+      "transparent": true,
+      "drop": "water_16",
+      "meta": "water",
+      "texture": "water_flow",
+      "animated": true,
+      "frames": 16,
+      "state": 1
+    },
+    "water_12": {
+      "top": 4,
+      "sprite_top": 4,
+      "collision": false,
+      "transparent": true,
+      "drop": "water_12",
+      "meta": "water",
+      "texture": "water_flow",
+      "animated": true,
+      "frames": 16,
+      "state": 2
+    },
+    "water_8": {
+      "top": 8,
+      "sprite_top": 8,
+      "collision": false,
+      "transparent": true,
+      "drop": "water_8",
+      "meta": "water",
+      "texture": "water_flow",
+      "animated": true,
+      "frames": 16,
+      "state": 3
+    },
+    "water_4": {
+      "top": 12,
+      "sprite_top": 12,
+      "collision": false,
+      "transparent": true,
+      "drop": "water_4",
+      "meta": "water",
+      "texture": "water_flow",
+      "animated": true,
+      "frames": 16,
+      "state": 4
+    },
+    "lava_16": {
+      "collision": false,
+      "drop": "lava_16",
+      "meta": "lava",
+      "texture": "lava_flow",
+      "animated": true,
+      "frames": 16,
+      "state": 1,
+      "damage": 4
+    },
+    "lava_12": {
+      "top": 4,
+      "sprite_top": 4,
+      "collision": false,
+      "transparent": true,
+      "drop": "lava_12",
+      "meta": "lava",
+      "texture": "lava_flow",
+      "animated": true,
+      "frames": 16,
+      "state": 2,
+      "damage": 4
+    },
+    "lava_8": {
+      "top": 8,
+      "sprite_top": 8,
+      "collision": false,
+      "transparent": true,
+      "drop": "lava_8",
+      "meta": "lava",
+      "texture": "lava_flow",
+      "animated": true,
+      "frames": 16,
+      "state": 3,
+      "damage": 4
+    },
+    "lava_4": {
+      "top": 12,
+      "sprite_top": 12,
+      "collision": false,
+      "transparent": true,
+      "drop": "lava_4",
+      "meta": "lava",
+      "texture": "lava_flow",
+      "animated": true,
+      "frames": 16,
+      "state": 4,
+      "damage": 4
+    },
+    "obsidian": {
+      "hp": 15000,
+      "drop": "obsidian",
+      "texture": "obsidian",
+      "tool_level": 4,
+      "tool_type": "pickaxe"
+    },
+    "snow": {
+      "top": 14,
+      "sprite_top": 14,
+      "collision": false,
+      "transparent": true,
+      "drop": "snowball",
+      "texture": "snow",
+      "hp": 6,
+      "tool_level": 1,
+      "tool_type": "shovel",
+      "block_required": true
+    },
+    "snow_block": {
+      "collision": true,
+      "transparent": true,
+      "drop": "snowball",
+      "drop_count": 4,
+      "texture": "snow",
+      "hp": 60,
+      "tool_level": 1,
+      "tool_type": "shovel"
+    },
+    "furnace": {
+      "hp": 1050,
+      "collision": true,
+      "transparent": false,
+      "drop": "furnace",
+      "texture": "furnace",
+      "animated": true,
+      "frames": 2,
+      "tool_level": 1,
+      "tool_type": "pickaxe",
+      "meta": "furnace"
+    },
+    "chest": {
+      "hp": 180,
+      "collision": true,
+      "transparent": true,
+      "drop": "chest",
+      "texture": "chest",
+      "tool_level": 0,
+      "tool_type": "axe",
+      "meta": "chest",
+      "top": 2,
+      "left": 1,
+      "right": 1,
+      "sprite_top": 2,
+      "sprite_left": 1,
+      "sprite_right": 1
+    }
+  },
+  "items": {
+    "none": {
+      "name": "",
+      "type": "none",
+      "texture": "none"
+    },
+    "stone": {
+      "name": "Stone",
+      "type": "block",
+      "texture": "stone"
+    },
+    "grass": {
+      "name": "Grass",
+      "type": "block",
+      "texture": "grass"
+    },
+    "dirt": {
+      "name": "Dirt",
+      "type": "block",
+      "texture": "dirt"
+    },
+    "cobblestone": {
+      "name": "Cobblestone",
+      "type": "block",
+      "texture": "cobblestone",
+      "smelt_product": "stone"
+    },
+    "planks_oak": {
+      "name": "Oak Planks",
+      "type": "block",
+      "texture": "planks_oak",
+      "burning_time": 15000
+    },
+    "sapling_oak": {
+      "name": "Oak Sapling",
+      "type": "block",
+      "texture": "sapling_oak",
+      "burning_time": 5000
+    },
+    "planks_spruce": {
+      "name": "Spruce Planks",
+      "type": "block",
+      "texture": "planks_spruce",
+      "burning_time": 15000
+    },
+    "crafting_table": {
+      "name": "Crafting Table",
+      "type": "block",
+      "texture": "crafting_table",
+      "burning_time": 15000
+    },
+    "furnace": {
+      "name": "Furnace",
+      "type": "block",
+      "texture": "furnace_off"
+    },
+    "chest": {
+      "name": "Chest",
+      "type": "block",
+      "texture": "chest"
+    },
+    "sapling_spruce": {
+      "name": "Spruce Sapling",
+      "type": "block",
+      "texture": "sapling_spruce",
+      "burning_time": 5000
+    },
+    "bedrock": {
+      "name": "Bedrock",
+      "type": "block",
+      "texture": "bedrock"
+    },
+    "sand": {
+      "name": "Sand",
+      "type": "block",
+      "texture": "sand",
+      "smelt_product": "glass"
+    },
+    "gravel": {
+      "name": "Gravel",
+      "type": "block",
+      "texture": "gravel"
+    },
+    "gold_ore": {
+      "name": "Golden Ore",
+      "type": "block",
+      "texture": "gold_ore",
+      "smelt_product": "gold_ingot"
+    },
+    "iron_ore": {
+      "name": "Iron Ore",
+      "type": "block",
+      "texture": "iron_ore",
+      "smelt_product": "iron_ingot"
+    },
+    "coal_ore": {
+      "name": "Coal Ore",
+      "type": "block",
+      "texture": "coal_ore",
+      "smelt_product": "coal"
+    },
+    "diamond_ore": {
+      "name": "Diamond Ore",
+      "type": "block",
+      "texture": "diamond_ore",
+      "smelt_product": "diamond"
+    },
+    "log_oak": {
+      "name": "Wood",
+      "type": "block",
+      "texture": "log_oak",
+      "burning_time": 15000,
+      "smelt_product": "charcoal"
+    },
+    "leaves_oak": {
+      "name": "Leaves",
+      "type": "block",
+      "texture": "leaves_oak"
+    },
+    "log_spruce": {
+      "name": "Spruce",
+      "type": "block",
+      "texture": "log_spruce",
+      "burning_time": 15000,
+      "smelt_product": "charcoal"
+    },
+    "leaves_spruce": {
+      "name": "Spruce Leaves",
+      "type": "block",
+      "texture": "leaves_spruce"
+    },
+    "glass": {
+      "name": "Glass",
+      "type": "block",
+      "texture": "glass"
+    },
+    "lapis_ore": {
+      "name": "Lapis Ore",
+      "type": "block",
+      "texture": "lapis_ore",
+      "smelt_product": "lapis_lazuli"
+    },
+    "lapis_block": {
+      "name": "Lapis Block",
+      "type": "block",
+      "texture": "lapis_block"
+    },
+    "sandstone": {
+      "name": "Sandstone",
+      "type": "block",
+      "texture": "sandstone"
+    },
+    "web": {
+      "name": "Cobweb",
+      "type": "block",
+      "texture": "web"
+    },
+    "tallgrass": {
+      "name": "Tall Grass",
+      "type": "block",
+      "texture": "tallgrass",
+      "origin_x": 0.5
+    },
+    "deadbush": {
+      "name": "Dead Bush",
+      "type": "block",
+      "texture": "deadbush",
+      "origin_x": 0.5,
+      "burning_time": 5000
+    },
+    "bricks": {
+      "name": "Bricks",
+      "type": "block",
+      "texture": "bricks"
+    },
+    "dandelion": {
+      "name": "Dandelion",
+      "type": "block",
+      "texture": "dandelion",
+      "origin_x": 0.5
+    },
+    "rose": {
+      "name": "Rose",
+      "type": "block",
+      "texture": "rose",
+      "origin_x": 0.5
+    },
+    "mushroom_brown": {
+      "name": "Mushroom",
+      "type": "block",
+      "texture": "mushroom_brown",
+      "origin_x": 0.5
+    },
+    "mushroom_red": {
+      "name": "Mushroom",
+      "type": "block",
+      "texture": "mushroom_red",
+      "origin_x": 0.5
+    },
+    "wool_colored_white": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_white"
+    },
+    "wool_colored_orange": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_orange"
+    },
+    "wool_colored_magenta": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_magenta"
+    },
+    "wool_colored_light_blue": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_light_blue"
+    },
+    "wool_colored_yellow": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_yellow"
+    },
+    "wool_colored_lime": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_lime"
+    },
+    "wool_colored_pink": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_pink"
+    },
+    "wool_colored_gray": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_gray"
+    },
+    "wool_colored_silver": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_silver"
+    },
+    "wool_colored_cyan": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_cyan"
+    },
+    "wool_colored_purple": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_purple"
+    },
+    "wool_colored_blue": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_blue"
+    },
+    "wool_colored_brown": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_brown"
+    },
+    "wool_colored_green": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_green"
+    },
+    "wool_colored_red": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_red"
+    },
+    "wool_colored_black": {
+      "name": "Wool",
+      "type": "block",
+      "texture": "wool_colored_black"
+    },
+    "gold_block": {
+      "name": "Gold Block",
+      "type": "block",
+      "texture": "gold_block"
+    },
+    "iron_block": {
+      "name": "Iron Block",
+      "type": "block",
+      "texture": "iron_block"
+    },
+    "diamond_block": {
+      "name": "Diamond Block",
+      "type": "block",
+      "texture": "diamond_block"
+    },
+    "stone_slab": {
+      "name": "Stone Slab",
+      "type": "slab",
+      "texture": "stone_slab",
+      "top_slab_block": "stone_slab_top",
+      "bottom_slab_block": "stone_slab_bottom"
+    },
+    "sandstone_slab": {
+      "name": "Sandstone Slab",
+      "type": "slab",
+      "texture": "sandstone_slab",
+      "top_slab_block": "sandstone_slab_top",
+      "bottom_slab_block": "sandstone_slab_bottom"
+    },
+    "oak_slab": {
+      "name": "Oak Slab",
+      "type": "slab",
+      "texture": "oak_slab",
+      "top_slab_block": "oak_slab_top",
+      "bottom_slab_block": "oak_slab_bottom",
+      "burn_time": 7500
+    },
+    "spruce_slab": {
+      "name": "Spruce Slab",
+      "type": "slab",
+      "texture": "spruce_slab",
+      "top_slab_block": "spruce_slab_top",
+      "bottom_slab_block": "spruce_slab_bottom",
+      "burn_time": 7500
+    },
+    "cobblestone_slab": {
+      "name": "Cobblestone Slab",
+      "type": "slab",
+      "texture": "cobblestone_slab",
+      "top_slab_block": "cobblestone_slab_top",
+      "bottom_slab_block": "cobblestone_slab_bottom"
+    },
+    "brick_slab": {
+      "name": "Brick Slab",
+      "type": "slab",
+      "texture": "brick_slab",
+      "top_slab_block": "brick_slab_top",
+      "bottom_slab_block": "brick_slab_bottom"
+    },
+    "stonebrick": {
+      "name": "Stone Brick",
+      "type": "block",
+      "texture": "stonebrick"
+    },
+    "stonebrick_slab": {
+      "name": "Stone Brick Slab",
+      "type": "slab",
+      "texture": "stonebrick_slab",
+      "top_slab_block": "stonebrick_slab_top",
+      "bottom_slab_block": "stonebrick_slab_bottom"
+    },
+    "cactus": {
+      "name": "Cactus",
+      "type": "block",
+      "texture": "cactus"
+    },
+    "obsidian": {
+      "name": "Obsidian",
+      "type": "block",
+      "texture": "obsidian"
+    },
+    "snow_block": {
+      "name": "Snow Block",
+      "type": "block",
+      "texture": "snow"
+    },
+    "stick": {
+      "name": "Stick",
+      "texture": "stick",
+      "burning_time": 5000
+    },
+    "wood_sword": {
+      "name": "Wooden Sword",
+      "type": "sword",
+      "texture": "wood_sword",
+      "origin_x": 0.125,
+      "tool_level": 1,
+      "max_stack": 60,
+      "burning_time": 10000
+    },
+    "stone_sword": {
+      "name": "Stone Sword",
+      "type": "sword",
+      "texture": "stone_sword",
+      "origin_x": 0.125,
+      "tool_level": 2,
+      "max_stack": 132
+    },
+    "iron_sword": {
+      "name": "Iron Sword",
+      "type": "sword",
+      "texture": "iron_sword",
+      "origin_x": 0.125,
+      "tool_level": 3,
+      "max_stack": 251
+    },
+    "diamond_sword": {
+      "name": "Diamond Sword",
+      "type": "sword",
+      "texture": "diamond_sword",
+      "origin_x": 0.125,
+      "tool_level": 4,
+      "max_stack": 1562
+    },
+    "gold_sword": {
+      "name": "Golden Sword",
+      "type": "sword",
+      "texture": "gold_sword",
+      "origin_x": 0.125,
+      "tool_level": 1,
+      "max_stack": 33
+    },
+    "wood_shovel": {
+      "name": "Wooden Shovel",
+      "type": "shovel",
+      "texture": "wood_shovel",
+      "origin_x": 0.125,
+      "tool_level" : 1,
+      "max_stack": 59
+    },
+    "stone_shovel": {
+      "name": "Stone Shovel",
+      "type": "shovel",
+      "texture": "stone_shovel",
+      "origin_x": 0.125,
+      "tool_level" : 2,
+      "max_stack": 131
+    },
+    "iron_shovel": {
+      "name": "Iron Shovel",
+      "type": "shovel",
+      "texture": "iron_shovel",
+      "origin_x": 0.125,
+      "tool_level" : 3,
+      "max_stack": 250
+    },
+    "diamond_shovel": {
+      "name": "Diamond Shovel",
+      "type": "shovel",
+      "texture": "diamond_shovel",
+      "origin_x": 0.125,
+      "tool_level" : 4,
+      "max_stack": 1561
+    },
+    "gold_shovel": {
+      "name": "Golden Shovel",
+      "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,
+      "burning_time": 10000
+    },
+    "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,
+      "burning_time": 10000
+    },
+    "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": "usable",
+      "texture": "bucket_empty",
+      "origin_x": 0.25,
+      "action_key": "use_empty_bucket",
+      "max_stack": 1
+    },
+    "bucket_water": {
+      "name": "Water Bucket",
+      "type": "usable",
+      "texture": "bucket_water",
+      "origin_x": 0.25,
+      "action_key": "use_water_bucket",
+      "max_stack": 1
+    },
+    "bucket_lava": {
+      "name": "Lava Bucket",
+      "type": "usable",
+      "texture": "bucket_lava",
+      "origin_x": 0.25,
+      "action_key": "use_lava_bucket",
+      "max_stack": 1,
+      "burning_time": 1000000
+    },
+    "snowball": {
+      "name": "Snowball",
+      "type": "usable",
+      "texture": "snowball",
+      "action_key": "use_snow_ball_action",
+      "max_stack": 16
+    },
+    "coal": {
+      "name": "Coal",
+      "texture": "coal",
+      "burning_time": 80000,
+      "origin_x": 0.25
+    },
+    "charcoal": {
+      "name": "Charcoal",
+      "texture": "charcoal",
+      "burning_time": 80000,
+      "origin_x": 0.25
+    },
+    "iron_ingot": {
+      "name": "Iron Ingot",
+      "texture": "iron_ingot",
+      "origin_x": 0.5
+    },
+    "gold_ingot": {
+      "name": "Gold Ingot",
+      "texture": "gold_ingot",
+      "origin_x": 0.5
+    },
+    "diamond": {
+      "name": "Diamond",
+      "texture": "diamond",
+      "origin_x": 0.5
+    },
+    "lapis_lazuli": {
+      "name": "Lapis Lazuli",
+      "texture": "lapis_lazuli",
+      "origin_x": 0.5
+    },
+    "bed": {
+      "name": "Bed",
+      "type": "usable",
+      "texture": "bed",
+      "action_key": "use_bed_action"
+    },
+    "spawn_egg_pig": {
+      "name": "Pig Spawn Egg",
+      "type": "usable",
+      "texture": "spawn_egg",
+      "tint": "#EDBFB4",
+      "action_key": "use_spawn_egg_pig",
+      "origin_x": 0.5
+    },
+    "porkchop_raw": {
+      "name": "Raw Porkchop",
+      "type": "food",
+      "texture": "porkchop_raw",
+      "heal": 3,
+      "smelt_product": "porkchop_cooked"
+    },
+    "porkchop_cooked": {
+      "name": "Raw Porkchop",
+      "type": "food",
+      "texture": "porkchop_cooked",
+      "heal": 8
+    }
+  }
+}
similarity index 76%
rename from android/assets/json/menu_main_buttons.json
rename to assets/json/menu_main_buttons.json
index c52f47ca32db60d012a7b8609b89477d03713a80..97d0938b4bdf4820636d4cf1fdd955b14deafa64 100644 (file)
@@ -6,6 +6,9 @@
     "label": "Load Game",
     "type": 0
   },
     "label": "Load Game",
     "type": 0
   },
+  "options": {
+    "label": "Settings"
+  },
   "quit": {
     "label": "Quit"
   }
   "quit": {
     "label": "Quit"
   }
similarity index 72%
rename from android/assets/json/menu_new_game_buttons.json
rename to assets/json/menu_new_game_buttons.json
index c61adc3d36f8310355683f53d179402b6d56ddd0..ca4c4042bfa178fe69579c238ebdc8bf7f7c41f2 100644 (file)
@@ -3,8 +3,7 @@
     "label": "Creative"
   },
   "survival": {
     "label": "Creative"
   },
   "survival": {
-    "label": "Survival",
-    "type": 0
+    "label": "Survival"
   },
   "back": {
     "label": "Back"
   },
   "back": {
     "label": "Back"
diff --git a/assets/json/menu_options_buttons.json b/assets/json/menu_options_buttons.json
new file mode 100644 (file)
index 0000000..bb956cd
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "dyncam": {
+    "option_type": "boolean",
+    "label": "Dynamic Camera: %%value%%"
+  },
+  "fullscreen": {
+    "option_type": "boolean",
+    "label": "Fullscreen: %%value%%",
+    "visible_on_android": false
+  },
+  "back": {
+    "label": "Back"
+  }
+}
\ No newline at end of file
similarity index 56%
rename from android/assets/json/texture_regions.json
rename to assets/json/texture_regions.json
index e1f3e48adc14ebf25d3578a590cfc545826017c8..3c6e7af2d8a356a88ce9b6d77979d4bcc2e8a0b7 100644 (file)
       "y": 26,
       "w": 26,
       "h": 26
       "y": 26,
       "w": 26,
       "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
+    "inv": {
+      "x": 78,
+      "y": 26,
+      "w": 26,
+      "h": 26
     },
     },
-    "break_9": {
-      "x": 144,
-      "w": 16,
-      "h": 16
+    "pause": {
+      "x": 104,
+      "w": 26,
+      "h": 26
     }
   },
     }
   },
-  "allitems": {
+  "pp/allitems": {
     "creative": {
       "w": 176,
       "h": 136
     "creative": {
       "w": 176,
       "h": 136
       "h": 15
     }
   },
       "h": 15
     }
   },
-  "buttons": {
+  "pp/inventory": {
+    "survival": {
+      "w": 176,
+      "h": 166
+    }
+  },
+  "pp/crafting_table": {
+    "crafting_table": {
+      "w": 176,
+      "h": 166
+    }
+  },
+  "pp/furnace": {
+    "furnace": {
+      "w": 176,
+      "h": 166
+    },
+    "furnace_burn": {
+      "x": 176,
+      "w": 14,
+      "h": 14
+    },
+    "furnace_progress": {
+      "x": 176,
+      "y": 14,
+      "w": 24,
+      "h": 14
+    }
+  },
+  "pp/chest": {
+    "chest": {
+      "w": 176,
+      "h": 168
+    }
+  },
+  "pp/buttons": {
     "button_0": {
       "w": 200,
       "h": 20
     "button_0": {
       "w": 200,
       "h": 20
       "h": 20
     }
   },
       "h": 20
     }
   },
-  "gui": {
+  "pp/gui": {
     "hotbar": {
       "y": 16,
       "w": 182,
     "hotbar": {
       "y": 16,
       "w": 182,
       "h": 16
     }
   },
       "h": 16
     }
   },
-  "shade": {},
+  "pp/shade": {},
   "gamelogo": {},
   "gamelogo": {},
-  "background": {}
+  "pp/background": {},
+  "pp/health":{
+    "heart_whole": {
+      "w": 9
+    },
+    "heart_half": {
+      "x": 9,
+      "w": 9
+    },
+    "heart_empty": {
+      "x": 18,
+      "w": 9
+    }
+  }
 }
\ No newline at end of file
 }
\ No newline at end of file
diff --git a/assets/json/touch_buttons.json b/assets/json/touch_buttons.json
new file mode 100644 (file)
index 0000000..cbb9a15
--- /dev/null
@@ -0,0 +1,23 @@
+{
+  "alt": {
+    "x": -48,
+    "y": -48,
+    "w": 48,
+    "h": 48,
+    "key": "L-Alt"
+  },
+  "inv": {
+    "x": -96,
+    "y": -48,
+    "w": 48,
+    "h": 48,
+    "key": "E"
+  },
+  "pause": {
+    "x": -48,
+    "y": 0,
+    "w": 48,
+    "h": 48,
+    "key": "Escape"
+  }
+}
\ No newline at end of file
similarity index 100%
rename from android/assets/break.png
rename to assets/pp/break.png
diff --git a/assets/pp/chest.png b/assets/pp/chest.png
new file mode 100644 (file)
index 0000000..2c6d0c6
Binary files /dev/null and b/assets/pp/chest.png differ
diff --git a/assets/pp/chest_large.png b/assets/pp/chest_large.png
new file mode 100644 (file)
index 0000000..961891f
Binary files /dev/null and b/assets/pp/chest_large.png differ
diff --git a/assets/pp/crafting_table.png b/assets/pp/crafting_table.png
new file mode 100644 (file)
index 0000000..e019e39
Binary files /dev/null and b/assets/pp/crafting_table.png differ
diff --git a/assets/pp/furnace.png b/assets/pp/furnace.png
new file mode 100644 (file)
index 0000000..fc04793
Binary files /dev/null and b/assets/pp/furnace.png differ
similarity index 100%
rename from android/assets/gui.png
rename to assets/pp/gui.png
diff --git a/assets/pp/health.png b/assets/pp/health.png
new file mode 100644 (file)
index 0000000..99011d5
Binary files /dev/null and b/assets/pp/health.png differ
diff --git a/assets/pp/inventory.png b/assets/pp/inventory.png
new file mode 100644 (file)
index 0000000..8bc6cf7
Binary files /dev/null and b/assets/pp/inventory.png differ
similarity index 100%
rename from android/assets/shade.png
rename to assets/pp/shade.png
diff --git a/assets/pp/textures/blocks/chest.png b/assets/pp/textures/blocks/chest.png
new file mode 100644 (file)
index 0000000..a2ca371
Binary files /dev/null and b/assets/pp/textures/blocks/chest.png differ
diff --git a/assets/pp/textures/blocks/furnace.png b/assets/pp/textures/blocks/furnace.png
new file mode 100644 (file)
index 0000000..080d8ad
Binary files /dev/null and b/assets/pp/textures/blocks/furnace.png differ
diff --git a/assets/pp/textures/blocks/grass_snowed.png b/assets/pp/textures/blocks/grass_snowed.png
new file mode 100644 (file)
index 0000000..730996d
Binary files /dev/null and b/assets/pp/textures/blocks/grass_snowed.png differ
diff --git a/assets/pp/textures/blocks/lapis_ore.png b/assets/pp/textures/blocks/lapis_ore.png
new file mode 100644 (file)
index 0000000..44fcd7f
Binary files /dev/null and b/assets/pp/textures/blocks/lapis_ore.png differ
diff --git a/assets/pp/textures/blocks/leaves_oak.png b/assets/pp/textures/blocks/leaves_oak.png
new file mode 100644 (file)
index 0000000..d902752
Binary files /dev/null and b/assets/pp/textures/blocks/leaves_oak.png differ
diff --git a/assets/pp/textures/blocks/leaves_spruce.png b/assets/pp/textures/blocks/leaves_spruce.png
new file mode 100644 (file)
index 0000000..0be1897
Binary files /dev/null and b/assets/pp/textures/blocks/leaves_spruce.png differ
diff --git a/assets/pp/textures/blocks/snow.png b/assets/pp/textures/blocks/snow.png
new file mode 100644 (file)
index 0000000..20a836e
Binary files /dev/null and b/assets/pp/textures/blocks/snow.png differ
diff --git a/assets/pp/textures/items/bed.png b/assets/pp/textures/items/bed.png
new file mode 100644 (file)
index 0000000..0e1565d
Binary files /dev/null and b/assets/pp/textures/items/bed.png differ
diff --git a/assets/pp/textures/items/charcoal.png b/assets/pp/textures/items/charcoal.png
new file mode 100644 (file)
index 0000000..9fe49a7
Binary files /dev/null and b/assets/pp/textures/items/charcoal.png differ
diff --git a/assets/pp/textures/items/coal.png b/assets/pp/textures/items/coal.png
new file mode 100644 (file)
index 0000000..c94ebad
Binary files /dev/null and b/assets/pp/textures/items/coal.png differ
diff --git a/assets/pp/textures/items/diamond.png b/assets/pp/textures/items/diamond.png
new file mode 100644 (file)
index 0000000..eff11e0
Binary files /dev/null and b/assets/pp/textures/items/diamond.png differ
diff --git a/assets/pp/textures/items/diamond_axe.png b/assets/pp/textures/items/diamond_axe.png
new file mode 100644 (file)
index 0000000..ac81a23
Binary files /dev/null and b/assets/pp/textures/items/diamond_axe.png differ
diff --git a/assets/pp/textures/items/diamond_hoe.png b/assets/pp/textures/items/diamond_hoe.png
new file mode 100644 (file)
index 0000000..f0ad2c6
Binary files /dev/null and b/assets/pp/textures/items/diamond_hoe.png differ
diff --git a/assets/pp/textures/items/diamond_pickaxe.png b/assets/pp/textures/items/diamond_pickaxe.png
new file mode 100644 (file)
index 0000000..75823f6
Binary files /dev/null and b/assets/pp/textures/items/diamond_pickaxe.png differ
diff --git a/assets/pp/textures/items/gold_axe.png b/assets/pp/textures/items/gold_axe.png
new file mode 100644 (file)
index 0000000..d939170
Binary files /dev/null and b/assets/pp/textures/items/gold_axe.png differ
diff --git a/assets/pp/textures/items/gold_hoe.png b/assets/pp/textures/items/gold_hoe.png
new file mode 100644 (file)
index 0000000..da32a5d
Binary files /dev/null and b/assets/pp/textures/items/gold_hoe.png differ
diff --git a/assets/pp/textures/items/gold_ingot.png b/assets/pp/textures/items/gold_ingot.png
new file mode 100644 (file)
index 0000000..b159455
Binary files /dev/null and b/assets/pp/textures/items/gold_ingot.png differ
diff --git a/assets/pp/textures/items/gold_pickaxe.png b/assets/pp/textures/items/gold_pickaxe.png
new file mode 100644 (file)
index 0000000..5b4e6d6
Binary files /dev/null and b/assets/pp/textures/items/gold_pickaxe.png differ
diff --git a/assets/pp/textures/items/iron_axe.png b/assets/pp/textures/items/iron_axe.png
new file mode 100644 (file)
index 0000000..52113da
Binary files /dev/null and b/assets/pp/textures/items/iron_axe.png differ
diff --git a/assets/pp/textures/items/iron_hoe.png b/assets/pp/textures/items/iron_hoe.png
new file mode 100644 (file)
index 0000000..d56485d
Binary files /dev/null and b/assets/pp/textures/items/iron_hoe.png differ
diff --git a/assets/pp/textures/items/iron_ingot.png b/assets/pp/textures/items/iron_ingot.png
new file mode 100644 (file)
index 0000000..1ff5c69
Binary files /dev/null and b/assets/pp/textures/items/iron_ingot.png differ
diff --git a/assets/pp/textures/items/iron_pickaxe.png b/assets/pp/textures/items/iron_pickaxe.png
new file mode 100644 (file)
index 0000000..0746cc0
Binary files /dev/null and b/assets/pp/textures/items/iron_pickaxe.png differ
diff --git a/assets/pp/textures/items/lapis_lazuli.png b/assets/pp/textures/items/lapis_lazuli.png
new file mode 100644 (file)
index 0000000..84bdcd4
Binary files /dev/null and b/assets/pp/textures/items/lapis_lazuli.png differ
diff --git a/assets/pp/textures/items/porkchop_cooked.png b/assets/pp/textures/items/porkchop_cooked.png
new file mode 100644 (file)
index 0000000..dbd02c5
Binary files /dev/null and b/assets/pp/textures/items/porkchop_cooked.png differ
diff --git a/assets/pp/textures/items/porkchop_raw.png b/assets/pp/textures/items/porkchop_raw.png
new file mode 100644 (file)
index 0000000..6c83dd0
Binary files /dev/null and b/assets/pp/textures/items/porkchop_raw.png differ
diff --git a/assets/pp/textures/items/shears.png b/assets/pp/textures/items/shears.png
new file mode 100644 (file)
index 0000000..bfa815c
Binary files /dev/null and b/assets/pp/textures/items/shears.png differ
diff --git a/assets/pp/textures/items/snowball.png b/assets/pp/textures/items/snowball.png
new file mode 100644 (file)
index 0000000..56a11e9
Binary files /dev/null and b/assets/pp/textures/items/snowball.png differ
diff --git a/assets/pp/textures/items/spawn_egg.png b/assets/pp/textures/items/spawn_egg.png
new file mode 100644 (file)
index 0000000..8733a83
Binary files /dev/null and b/assets/pp/textures/items/spawn_egg.png differ
diff --git a/assets/pp/textures/items/stick.png b/assets/pp/textures/items/stick.png
new file mode 100644 (file)
index 0000000..81c915e
Binary files /dev/null and b/assets/pp/textures/items/stick.png differ
diff --git a/assets/pp/textures/items/stone_axe.png b/assets/pp/textures/items/stone_axe.png
new file mode 100644 (file)
index 0000000..5f42de6
Binary files /dev/null and b/assets/pp/textures/items/stone_axe.png differ
diff --git a/assets/pp/textures/items/stone_hoe.png b/assets/pp/textures/items/stone_hoe.png
new file mode 100644 (file)
index 0000000..2118229
Binary files /dev/null and b/assets/pp/textures/items/stone_hoe.png differ
diff --git a/assets/pp/textures/items/stone_pickaxe.png b/assets/pp/textures/items/stone_pickaxe.png
new file mode 100644 (file)
index 0000000..13bdd75
Binary files /dev/null and b/assets/pp/textures/items/stone_pickaxe.png differ
diff --git a/assets/pp/textures/items/wood_axe.png b/assets/pp/textures/items/wood_axe.png
new file mode 100644 (file)
index 0000000..b0cbde6
Binary files /dev/null and b/assets/pp/textures/items/wood_axe.png differ
diff --git a/assets/pp/textures/items/wood_hoe.png b/assets/pp/textures/items/wood_hoe.png
new file mode 100644 (file)
index 0000000..3744c1b
Binary files /dev/null and b/assets/pp/textures/items/wood_hoe.png differ
diff --git a/assets/pp/textures/items/wood_pickaxe.png b/assets/pp/textures/items/wood_pickaxe.png
new file mode 100644 (file)
index 0000000..5932623
Binary files /dev/null and b/assets/pp/textures/items/wood_pickaxe.png differ
diff --git a/assets/touch_gui.png b/assets/touch_gui.png
new file mode 100644 (file)
index 0000000..57dffc4
Binary files /dev/null and b/assets/touch_gui.png differ
diff --git a/build.gradle b/build.gradle
deleted file mode 100644 (file)
index ed846ef..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-buildscript {
-
-    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"
-    }
-}
-
-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'
-    }
-
-    repositories {
-        mavenLocal()
-        mavenCentral()
-        jcenter()
-        google()
-        maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
-        maven { url "https://oss.sonatype.org/content/repositories/releases/" }
-    }
-}
-
-project(":desktop") {
-    apply plugin: "java-library"
-
-    dependencies {
-        implementation project(":core")
-        api "com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion"
-        api "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
-    }
-}
-
-project(":android") {
-    apply plugin: "android"
-
-    configurations { natives }
-
-    dependencies {
-        implementation project(":core")
-        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"
-    }
-}
-
-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'
-    }
-}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644 (file)
index 0000000..203acec
--- /dev/null
@@ -0,0 +1,30 @@
+buildscript {
+    repositories {
+        mavenLocal()
+        mavenCentral()
+        google()
+        maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") }
+        maven { url = uri("https://oss.sonatype.org/content/repositories/releases/") }
+        maven { url = uri("https://jitpack.io") }
+        maven { url = uri("https://mvn.fredboy.ru/releases/") }
+    }
+
+    dependencies {
+        classpath(Dependencies.androidGradlePlugin)
+        classpath(Dependencies.Kotlin.gradlePlugin)
+    }
+}
+
+allprojects {
+    version = ApplicationInfo.versionName
+
+    repositories {
+        mavenLocal()
+        mavenCentral()
+        google()
+        maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") }
+        maven { url = uri("https://oss.sonatype.org/content/repositories/releases/") }
+        maven { url = uri("https://jitpack.io") }
+        maven { url = uri("https://mvn.fredboy.ru/releases/") }
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
new file mode 100644 (file)
index 0000000..876c922
--- /dev/null
@@ -0,0 +1,7 @@
+plugins {
+    `kotlin-dsl`
+}
+
+repositories {
+    mavenCentral()
+}
diff --git a/buildSrc/src/main/kotlin/ApplicationInfo.kt b/buildSrc/src/main/kotlin/ApplicationInfo.kt
new file mode 100644 (file)
index 0000000..05676c4
--- /dev/null
@@ -0,0 +1,11 @@
+import org.gradle.api.JavaVersion
+
+object ApplicationInfo {
+    const val name = "CaveDroid"
+    const val versionName = "alpha0.9.2"
+    const val versionCode = 25
+
+    const val packageName = "ru.deadsoftware.cavedroid"
+
+    val sourceCompatibility = JavaVersion.VERSION_17
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt
new file mode 100644 (file)
index 0000000..bfe2c54
--- /dev/null
@@ -0,0 +1,49 @@
+object Dependencies {
+
+    object LibGDX {
+        const val gdx = "com.badlogicgames.gdx:gdx:${Versions.gdx}"
+
+        object Android {
+            const val backend = "com.badlogicgames.gdx:gdx-backend-android:${Versions.gdx}"
+
+            object Natives {
+                const val armeabi = "com.badlogicgames.gdx:gdx-platform:${Versions.gdx}:natives-armeabi-v7a"
+                const val arm64 = "com.badlogicgames.gdx:gdx-platform:${Versions.gdx}:natives-arm64-v8a"
+                const val x86 = "com.badlogicgames.gdx:gdx-platform:${Versions.gdx}:natives-x86"
+                const val x86_64 = "com.badlogicgames.gdx:gdx-platform:${Versions.gdx}:natives-x86_64"
+            }
+        }
+
+        object Desktop {
+            const val backend = "com.badlogicgames.gdx:gdx-backend-lwjgl3:${Versions.gdx}"
+            const val natives = "com.badlogicgames.gdx:gdx-platform:${Versions.gdx}:natives-desktop"
+        }
+    }
+
+    object Dagger {
+        const val dagger = "com.google.dagger:dagger:${Versions.dagger}"
+        const val compiler = "com.google.dagger:dagger-compiler:${Versions.dagger}"
+    }
+
+    object Kotlin {
+        const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
+        const val bom = "org.jetbrains.kotlin:kotlin-bom:${Versions.kotlin}"
+        const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}"
+
+        object Serialization {
+            const val json = "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerialization}"
+            const val protobuf = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf:${Versions.kotlinxSerialization}"
+        }
+    }
+
+    object Automultibind {
+        const val annotations = "ru.fredboy:automultibind-annotations:${Versions.automultibind}"
+        const val ksp = "ru.fredboy:automultibind-ksp:${Versions.automultibind}"
+    }
+
+    const val androidGradlePlugin = "com.android.tools.build:gradle:${Versions.agp}"
+
+    // TODO: Remove after complete kotlin migration
+    const val jetbrainsAnnotations = "org.jetbrains:annotations:${Versions.jetbrainsAnnotations}"
+
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt
new file mode 100644 (file)
index 0000000..69bfd29
--- /dev/null
@@ -0,0 +1,63 @@
+object Versions {
+
+    /**
+     * Android gradle plugin version
+     */
+    const val agp = "8.2.2"
+
+    /**
+     * LibGDX version
+     *
+     * [Source](https://github.com/libgdx/libgdx)
+     */
+    const val gdx = "1.12.0"
+
+    /**
+     * Dagger version
+     *
+     * [Source](https://github.com/google/dagger)
+     */
+    const val dagger = "2.51.1"
+
+    /**
+     * Kotlin version
+     *
+     * [Source](https://github.com/JetBrains/kotlin)
+     */
+    const val kotlin = "1.9.24"
+
+    /**
+     * Kotlinx serialization version
+     *
+     * [Source](https://github.com/Kotlin/kotlinx.serialization/)
+     */
+    const val kotlinxSerialization = "1.6.3"
+
+    /**
+     * Kotlin Symbol Processing version
+     *
+     * [Source](https://github.com/google/ksp)
+     */
+    const val ksp = "1.9.24-1.0.20"
+
+    /**
+     * Kotlin poet version
+     *
+     * [Source](https://github.com/square/kotlinpoet)
+     */
+    const val kotlinPoetKsp = "1.16.0"
+
+    /**
+     * JetBrains annotations version
+     *
+     * [Source](https://github.com/JetBrains/java-annotations)
+     */
+    const val jetbrainsAnnotations = "23.1.0"
+
+    /**
+     * Automultibind version
+     *
+     * [Source](https://github.com/fredboy/automultibind)
+     */
+    const val automultibind = "1.0.0"
+}
\ No newline at end of file
diff --git a/core/build.gradle b/core/build.gradle
deleted file mode 100644 (file)
index f17b102..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-plugins {
-    id "org.jetbrains.kotlin.jvm"
-    id "java"
-    id "idea"
-    id "net.ltgt.apt" version "0.21"
-}
-
-sourceCompatibility = 1.8
-
-[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
-
-sourceSets.main.java.srcDirs = [ "src/" ]
\ No newline at end of file
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
new file mode 100644 (file)
index 0000000..276f49d
--- /dev/null
@@ -0,0 +1,26 @@
+plugins {
+    id("java-library")
+    id("org.jetbrains.kotlin.jvm")
+    id("kotlin")
+    id("idea")
+    id("org.jetbrains.kotlin.plugin.serialization") version Versions.kotlin
+    id ("com.google.devtools.ksp") version Versions.ksp
+}
+
+java.sourceCompatibility = ApplicationInfo.sourceCompatibility
+java.targetCompatibility = ApplicationInfo.sourceCompatibility
+
+dependencies {
+    implementation(Dependencies.Automultibind.annotations)
+    ksp(Dependencies.Automultibind.ksp)
+
+    implementation(Dependencies.LibGDX.gdx)
+    implementation(Dependencies.Dagger.dagger)
+
+    implementation(Dependencies.jetbrainsAnnotations)
+    implementation(Dependencies.Kotlin.stdlib)
+    implementation(Dependencies.Kotlin.Serialization.json)
+    implementation(Dependencies.Kotlin.Serialization.protobuf)
+
+    ksp(Dependencies.Dagger.compiler)
+}
\ No newline at end of file
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/MainConfig.java b/core/src/main/java/ru/deadsoftware/cavedroid/MainConfig.java
new file mode 100644 (file)
index 0000000..61cef0a
--- /dev/null
@@ -0,0 +1,170 @@
+package ru.deadsoftware.cavedroid;
+
+import org.jetbrains.annotations.Nullable;
+import ru.deadsoftware.cavedroid.game.GameUiWindow;
+import ru.deadsoftware.cavedroid.game.input.Joystick;
+import ru.deadsoftware.cavedroid.prefs.PreferencesStore;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.HashMap;
+
+@Singleton
+public class MainConfig {
+
+    private final HashMap<String, String> mPreferencesCache = new HashMap<>();
+
+    @Nullable
+    private FullscreenToggleListener mFullscreenToggleListener = null;
+
+    private final CaveGame mCaveGame;
+    private final PreferencesStore mPreferencesStore;
+
+    @Nullable
+    private MainComponent mMainComponent;
+
+    @Nullable
+    private Joystick mJoystick;
+
+    private GameUiWindow mGameUiWindow;
+    private String mGameFolder;
+
+    private boolean mTouch;
+    private boolean mShowInfo;
+    private boolean mShowMap;
+
+    private float mWidth;
+    private float mHeight;
+
+    @Nullable
+    private String mAssetsPackPath = null;
+
+    @Inject
+    public MainConfig(CaveGame caveGame, PreferencesStore preferencesStore) {
+        mCaveGame = caveGame;
+        mPreferencesStore = preferencesStore;
+
+        mGameUiWindow = GameUiWindow.NONE;
+        mGameFolder = "";
+    }
+
+    public CaveGame getCaveGame() {
+        return mCaveGame;
+    }
+
+    public MainComponent getMainComponent() {
+        assert mMainComponent != null;
+        return mMainComponent;
+    }
+
+    public void setMainComponent(MainComponent mainComponent) {
+        mMainComponent = mainComponent;
+    }
+
+    public boolean checkGameUiWindow(GameUiWindow gameUiWindow) {
+        return mGameUiWindow == gameUiWindow;
+    }
+
+    public void setGameUiWindow(GameUiWindow gameUiWindow) {
+        mGameUiWindow = gameUiWindow;
+    }
+
+    public String getGameFolder() {
+        return mGameFolder;
+    }
+
+    public void setGameFolder(String gameFolder) {
+        mGameFolder = gameFolder;
+    }
+
+    public boolean isTouch() {
+        return mTouch;
+    }
+
+    public void setTouch(boolean touch) {
+        mTouch = touch;
+    }
+
+    public float getWidth() {
+        return mWidth;
+    }
+
+    public void setWidth(float width) {
+        mWidth = width;
+    }
+
+    public float getHeight() {
+        return mHeight;
+    }
+
+    public void setHeight(float height) {
+        mHeight = height;
+    }
+
+    public boolean isShowInfo() {
+        return mShowInfo;
+    }
+
+    public void setShowInfo(boolean showInfo) {
+        mShowInfo = showInfo;
+    }
+
+    public boolean isShowMap() {
+        return mShowMap;
+    }
+
+    public void setShowMap(boolean showMap) {
+        mShowMap = showMap;
+    }
+
+    @Nullable
+    public String getAssetsPackPath() {
+        return mAssetsPackPath;
+    }
+
+    public void setAssetsPackPath(@Nullable String assetsPackPath) {
+        mAssetsPackPath = assetsPackPath;
+    }
+
+    @Nullable
+    public Joystick getJoystick() {
+        return mJoystick;
+    }
+
+    public void setJoystick(@Nullable Joystick joystick) {
+        mJoystick = joystick;
+    }
+
+    @Nullable
+    public String getPreference(String key) {
+        if (mPreferencesCache.containsKey(key)) {
+            return mPreferencesCache.get(key);
+        }
+
+        String value = mPreferencesStore.getPreference(key);
+        mPreferencesCache.put(key, value);
+
+        return value;
+    }
+
+    public void setPreference(String key, String value) {
+        mPreferencesCache.put(key, value);
+        mPreferencesStore.setPreference(key, value);
+
+        if (mFullscreenToggleListener != null && key.equals("fullscreen")) {
+            mFullscreenToggleListener.onFullscreenToggled(Boolean.parseBoolean(value));
+        }
+    }
+
+    public void setFullscreenToggleListener(@Nullable FullscreenToggleListener fullscreenToggleListener) {
+        mFullscreenToggleListener = fullscreenToggleListener;
+    }
+
+    public boolean isUseDynamicCamera() {
+        return Boolean.parseBoolean(getPreference("dyncam"));
+    }
+
+    public interface FullscreenToggleListener {
+        void onFullscreenToggled(boolean value);
+    }
+}
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/game/GameComponent.java b/core/src/main/java/ru/deadsoftware/cavedroid/game/GameComponent.java
new file mode 100644 (file)
index 0000000..94d37d4
--- /dev/null
@@ -0,0 +1,22 @@
+package ru.deadsoftware.cavedroid.game;
+
+import dagger.Component;
+import ru.deadsoftware.cavedroid.MainComponent;
+import ru.deadsoftware.cavedroid.generated.module.*;
+
+@GameScope
+@Component(dependencies = MainComponent.class,
+        modules = {GameModule.class,
+                UseItemActionsModule.class,
+                UpdateBlockActionsModule.class,
+                PlaceBlockActionsModule.class,
+                RenderModule.class,
+                KeyboardInputHandlersModule.class,
+                MouseInputHandlersModule.class,
+                UseBlockActionsModule.class
+        })
+public interface GameComponent {
+    GameProc getGameProc();
+
+    GameItemsHolder getGameItemsHolder();
+}
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/game/GameModule.java b/core/src/main/java/ru/deadsoftware/cavedroid/game/GameModule.java
new file mode 100644 (file)
index 0000000..6c71d78
--- /dev/null
@@ -0,0 +1,94 @@
+package ru.deadsoftware.cavedroid.game;
+
+import dagger.Module;
+import dagger.Provides;
+import org.jetbrains.annotations.Nullable;
+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.drop.DropController;
+import ru.deadsoftware.cavedroid.game.objects.container.ContainerController;
+import ru.deadsoftware.cavedroid.game.save.GameSaveData;
+import ru.deadsoftware.cavedroid.game.save.GameSaveLoader;
+import ru.deadsoftware.cavedroid.game.ui.TooltipManager;
+import ru.deadsoftware.cavedroid.game.world.GameWorld;
+
+@Module
+public class GameModule {
+
+    @Nullable
+    private static GameSaveData data;
+
+    public static boolean loaded = false;
+
+    private static void load(MainConfig mainConfig, GameItemsHolder gameItemsHolder, TooltipManager tooltipManager) {
+        if (loaded) {
+            return;
+        }
+        data = GameSaveLoader.INSTANCE.load(mainConfig, gameItemsHolder, tooltipManager);
+        loaded = true;
+    }
+
+    private static void makeDataNullIfEmpty() {
+        if (data != null && data.isEmpty()) {
+            data = null;
+        }
+    }
+
+    @Provides
+    @GameScope
+    public static DropController provideDropController(MainConfig mainConfig,
+                                                       GameItemsHolder gameItemsHolder,
+                                                       TooltipManager tooltipManager) {
+        load(mainConfig, gameItemsHolder, tooltipManager);
+        DropController controller = data != null ? data.retrieveDropController() : new DropController();
+        makeDataNullIfEmpty();
+        controller.initDrops(gameItemsHolder);
+        return controller;
+    }
+
+    @Provides
+    @GameScope
+    public static ContainerController provideFurnaceController(MainConfig mainConfig,
+                                                               DropController dropController,
+                                                               GameItemsHolder gameItemsHolder,
+                                                               TooltipManager tooltipManager) {
+        load(mainConfig, gameItemsHolder, tooltipManager);
+        ContainerController controller = data != null
+                ? data.retrieveContainerController()
+                : new ContainerController(dropController, gameItemsHolder);
+        makeDataNullIfEmpty();
+        controller.init(dropController, gameItemsHolder);
+        return controller;
+    }
+
+    @Provides
+    @GameScope
+    public static MobsController provideMobsController(MainConfig mainConfig,
+                                                       GameItemsHolder gameItemsHolder,
+                                                       TooltipManager tooltipManager) {
+        load(mainConfig, gameItemsHolder, tooltipManager);
+        MobsController controller = data != null
+                ? data.retrieveMobsController()
+                : new MobsController(gameItemsHolder, tooltipManager);
+        makeDataNullIfEmpty();
+        controller.getPlayer().initInventory(gameItemsHolder, tooltipManager);
+        return controller;
+    }
+
+    @Provides
+    @GameScope
+    public static GameWorld provideGameWorld(MainConfig mainConfig,
+                                             DropController dropController,
+                                             MobsController mobsController,
+                                             GameItemsHolder gameItemsHolder,
+                                             ContainerController containerController,
+                                             TooltipManager tooltipManager) {
+        load(mainConfig, gameItemsHolder, tooltipManager);
+        Block[][] fm = data != null ? data.retrieveForeMap() : null;
+        Block[][] bm = data != null ? data.retrieveBackMap() : null;
+        makeDataNullIfEmpty();
+        return new GameWorld(dropController, mobsController, gameItemsHolder, containerController, fm, bm);
+    }
+
+}
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/game/GamePhysics.java b/core/src/main/java/ru/deadsoftware/cavedroid/game/GamePhysics.java
new file mode 100644 (file)
index 0000000..406ee34
--- /dev/null
@@ -0,0 +1,380 @@
+package ru.deadsoftware.cavedroid.game;
+
+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 org.jetbrains.annotations.Nullable;
+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.Player;
+import ru.deadsoftware.cavedroid.game.model.block.Block;
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem;
+import ru.deadsoftware.cavedroid.game.objects.drop.Drop;
+import ru.deadsoftware.cavedroid.game.objects.drop.DropController;
+import ru.deadsoftware.cavedroid.game.world.GameWorld;
+
+import javax.inject.Inject;
+import java.util.Iterator;
+
+
+@GameScope
+public class GamePhysics {
+
+    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, 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,
+                       GameItemsHolder gameItemsHolder) {
+        mGameWorld = gameWorld;
+        mMainConfig = mainConfig;
+        mMobsController = mobsController;
+        mDropController = dropController;
+        mGameItemsHolder = gameItemsHolder;
+    }
+
+    /**
+     * Checks if mob should jump
+     *
+     * @return true if mob should jump
+     */
+    private boolean checkJump(Mob mob) {
+        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);
+        Block block = mGameWorld.getForeMap(blX / 16, blY / 16);
+
+        if (checkColl(new Rectangle(blX, mob.getY() - 18, mob.getWidth(), mob.getHeight())) != null) {
+            return false;
+        }
+
+        return (block.toJump() &&
+                (mob.getY() + mob.getHeight()) - block.getRectangle(blX / 16, blY / 16).y > 8);
+    }
+
+    /**
+     * @return colliding rect or null if no collision
+     */
+    @Nullable
+    private Rectangle checkColl(Rectangle rect) {
+        int minX = (int) ((rect.x + rect.width / 2) / 16) - 4;
+        int minY = (int) ((rect.y + rect.height / 2) / 16) - 4;
+        int maxX = (int) ((rect.x + rect.width / 2) / 16) + 4;
+        int maxY = (int) ((rect.y + rect.height / 2) / 16) + 4;
+
+        if (minY < 0) {
+            minY = 0;
+        }
+
+        if (maxY > mGameWorld.getHeight()) {
+            maxY = mGameWorld.getHeight();
+        }
+
+        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.hasCollision()) {
+                    final Rectangle blockRect = block.getRectangle(x, y);
+                    if (Intersector.overlaps(rect, blockRect)) {
+                        return blockRect;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private Block getBlock(Rectangle rect) {
+        return mGameWorld.getForeMap((int) (rect.x + rect.width / 2) / 16,
+                (int) (rect.y + rect.height / 8 * 7) / 16);
+    }
+
+    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
+     */
+    @Nullable
+    private Rectangle getShiftedMagnetingPlayerRect(Drop drop) {
+        final Player player = mMobsController.getPlayer();
+
+        if (!player.inventory.canPickItem(drop)) {
+            return null;
+        }
+
+        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.inventory.pickDrop(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 {
+            dropVelocity.y += gravity.y * delta;
+        }
+
+        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) != null) {
+            dropVelocity.setZero();
+            do {
+                drop.y--;
+            } while (checkColl(drop) != null);
+        }
+
+        if (playerMagnetTarget != null) {
+            pickUpDropIfPossible(playerMagnetTarget, drop);
+        }
+    }
+
+    private void mobXColl(Mob mob) {
+        if (mob.getVelocity().x == 0f) {
+            return;
+        }
+
+        @Nullable Rectangle collidingRect = checkColl(mob);
+
+        if (collidingRect != null) {
+            if (mob.canJump() && !mob.isFlyMode() && collidingRect.y >= mob.y + mob.height - 8) {
+                mob.y = collidingRect.y - mob.height;
+                return;
+            }
+
+            collidingRect = checkColl(mob);
+
+            if (collidingRect != null) {
+                int d = 0;
+
+                if (mob.getVelocity().x < 0) {
+                    d = 1;
+                } else if (mob.getVelocity().x > 0) {
+                    d = -1;
+                }
+
+                if (d < 0) {
+                    mob.x = collidingRect.x - mob.width;
+                } else {
+                    mob.x = collidingRect.x + collidingRect.width;
+                }
+
+//                mob.x = MathUtils.round(mob.getX());
+//                while (checkColl(mob) != null) {
+//                    mob.x += d;
+//                }
+
+                if (mob.canJump()) {
+                    mob.changeDir();
+                }
+            }
+        }
+
+        mob.checkWorldBounds(mGameWorld);
+    }
+
+    private void mobYColl(Mob mob) {
+        @Nullable final Rectangle collidingRect = checkColl(mob);
+        if (collidingRect != null) {
+            int d = -1;
+
+            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);
+                }
+            }
+
+            if (d < 0) {
+                mob.y = collidingRect.y - mob.height;
+            } else {
+                mob.y = collidingRect.y + collidingRect.height;
+            }
+
+
+//            mob.y = MathUtils.round(mob.getY());
+//
+//            while (checkColl(mob)) {
+//                mob.y += d;
+//            }
+
+            mob.getVelocity().y = 0;
+
+        } else {
+            mob.y += 1;
+            mob.setCanJump(checkColl(mob) != null);
+            mob.y -= 1;
+        }
+
+        if (mob.getY() > mGameWorld.getHeightPx()) {
+            mob.kill();
+        }
+    }
+
+    private void playerPhy(Player player, float delta) {
+        if (player.isDead()) {
+            return;
+        }
+
+        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.getVelocity().y < 32f) {
+                    player.getVelocity().y += gravity.y * delta;
+                }
+                if (!player.isFlyMode() && player.getVelocity().y > 32f) {
+                    player.getVelocity().y -= player.getVelocity().y * 32f * delta;
+                }
+            } else {
+                player.getVelocity().y += PL_JUMP_VELOCITY * delta;
+                if (player.getVelocity().y < -player.getSpeed()) {
+                    player.getVelocity().y = -player.getSpeed();
+                }
+            }
+        } else {
+            if (!player.isFlyMode() && player.getVelocity().y < PL_TERMINAL_VELOCITY) {
+                player.getVelocity().y += gravity.y * delta;
+            }
+        }
+
+        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.getVelocity().x != 0 && checkJump(player)) {
+            player.jump();
+            player.setCanJump(false);
+        }
+    }
+
+    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.getVelocity().y += PL_JUMP_VELOCITY * delta;
+
+            if (mob.getVelocity().y < -mob.getSpeed()) {
+                mob.getVelocity().y = -mob.getSpeed();
+            }
+        } else if (!mob.isFlyMode() && mob.getVelocity().y < PL_TERMINAL_VELOCITY) {
+            mob.getVelocity().y += gravity.y * delta;
+        }
+
+        mob.y += mob.getVelocity().y * delta;
+        mobYColl(mob);
+
+        if (mob.isDead()) {
+            return;
+        }
+
+        mob.x += mob.getVelocity().x * delta;
+        mobXColl(mob);
+
+        if (mob.canJump() && mob.getVelocity().x != 0 && checkJump(mob)) {
+            mob.jump();
+            mob.setCanJump(false);
+        }
+    }
+
+    void update(float delta) {
+        Player player = mMobsController.getPlayer();
+
+        for (Iterator<Drop> it = mDropController.getIterator(); it.hasNext(); ) {
+            Drop drop = it.next();
+            dropPhy(drop, delta);
+            if (drop.getPickedUp()) {
+                it.remove();
+            }
+        }
+
+        for (Iterator<Mob> it = mMobsController.getMobs().iterator(); it.hasNext(); ) {
+            Mob mob = it.next();
+            mob.ai(mGameWorld, mGameItemsHolder, mMobsController, delta);
+            mobPhy(mob, delta);
+            if (mob.isDead()) {
+                for (InventoryItem invItem : mob.getDrop(mGameItemsHolder)) {
+                    mDropController.addDrop(mob.x, mob.y, invItem);
+                }
+
+                it.remove();
+            }
+        }
+
+        playerPhy(player, delta);
+        player.ai(mGameWorld, mGameItemsHolder, mMobsController, delta);
+        if (player.isDead()) {
+            for (InventoryItem invItem : player.inventory.getItems()) {
+                mDropController.addDrop(player.x, player.y, invItem);
+            }
+            player.inventory.clear();
+            player.respawn(mGameWorld, mGameItemsHolder);
+        }
+    }
+
+}
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/game/GameProc.java b/core/src/main/java/ru/deadsoftware/cavedroid/game/GameProc.java
new file mode 100644 (file)
index 0000000..29062a4
--- /dev/null
@@ -0,0 +1,81 @@
+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.Player;
+import ru.deadsoftware.cavedroid.game.objects.container.ContainerController;
+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 {
+
+    private final GamePhysics mGamePhysics;
+    private final GameRenderer mGameRenderer;
+    private final MobsController mMobsController;
+    private final ContainerController mContainerController;
+    private final GameItemsHolder mGameItemsHolder;
+    private final GameWorldFluidsLogicControllerTask mGameWorldFluidsLogicControllerTask;
+    private final GameWorldBlocksLogicControllerTask mGameWorldBlocksLogicControllerTask;
+    private final GameWorldMobDamageControllerTask mGameWorldMobDamageControllerTask;
+
+    private final Timer mWorldLogicTimer = new Timer();
+
+    @Inject
+    public GameProc(MainConfig mainConfig,
+                    GamePhysics gamePhysics,
+                    GameRenderer gameRenderer,
+                    MobsController mobsController,
+                    ContainerController containerController,
+                    GameItemsHolder gameItemsHolder,
+                    GameWorldFluidsLogicControllerTask gameWorldFluidsLogicControllerTask,
+                    GameWorldBlocksLogicControllerTask gameWorldBlocksLogicControllerTask,
+                    GameWorldMobDamageControllerTask gameWorldMobDamageControllerTask
+    ) {
+        mGamePhysics = gamePhysics;
+        mGameRenderer = gameRenderer;
+        mMobsController = mobsController;
+        mContainerController = containerController;
+        mGameItemsHolder = gameItemsHolder;
+        mGameWorldFluidsLogicControllerTask = gameWorldFluidsLogicControllerTask;
+        mGameWorldBlocksLogicControllerTask = gameWorldBlocksLogicControllerTask;
+        mGameWorldMobDamageControllerTask = gameWorldMobDamageControllerTask;
+
+        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(delta);
+        mGameRenderer.render(delta);
+        mContainerController.update();
+    }
+
+    public void show() {
+        Gdx.input.setInputProcessor(mGameRenderer);
+    }
+
+    @Override
+    public void dispose() {
+        mWorldLogicTimer.stop();
+        mGameWorldFluidsLogicControllerTask.cancel();
+        mGameWorldBlocksLogicControllerTask.cancel();
+        mGameWorldMobDamageControllerTask.cancel();
+    }
+}
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/game/GameRenderer.java b/core/src/main/java/ru/deadsoftware/cavedroid/game/GameRenderer.java
new file mode 100644 (file)
index 0000000..e063e09
--- /dev/null
@@ -0,0 +1,391 @@
+package ru.deadsoftware.cavedroid.game;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.Input;
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.GL20;
+import com.badlogic.gdx.math.Rectangle;
+import com.badlogic.gdx.math.Vector2;
+import com.badlogic.gdx.utils.ObjectMap;
+import com.badlogic.gdx.utils.TimeUtils;
+import org.jetbrains.annotations.Nullable;
+import ru.deadsoftware.cavedroid.MainConfig;
+import ru.deadsoftware.cavedroid.game.input.IKeyboardInputHandler;
+import ru.deadsoftware.cavedroid.game.input.IMouseInputHandler;
+import ru.deadsoftware.cavedroid.game.input.Joystick;
+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.Player;
+import ru.deadsoftware.cavedroid.game.objects.TouchButton;
+import ru.deadsoftware.cavedroid.game.render.IGameRenderer;
+import ru.deadsoftware.cavedroid.game.ui.TooltipManager;
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager;
+import ru.deadsoftware.cavedroid.game.world.GameWorld;
+import ru.deadsoftware.cavedroid.misc.Assets;
+import ru.deadsoftware.cavedroid.misc.Renderer;
+import ru.deadsoftware.cavedroid.misc.utils.MeasureUnitsUtilsKt;
+import ru.deadsoftware.cavedroid.misc.utils.RenderingUtilsKt;
+
+import javax.inject.Inject;
+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 float CAMERA_SPEED = 72f;
+    private static final float MAX_CAM_DISTANCE_FROM_PLAYER = 64f;
+    private static final float DRAG_THRESHOLD = 1f;
+    private static final TouchButton nullButton = new TouchButton(null, -1, true);
+
+    private final MainConfig mMainConfig;
+    private final MobsController mMobsController;
+    private final GameWorld mGameWorld;
+    private final List<IGameRenderer> mRenderers;
+    private final CursorMouseInputHandler mCursorMouseInputHandler;
+    private final MouseInputActionMapper mMouseInputActionMapper;
+    private final KeyboardInputActionMapper mKeyboardInputActionMapper;
+    private final Set<IMouseInputHandler> mMouseInputHandlers;
+    private final Set<IKeyboardInputHandler> mKeyboardInputHandlers;
+    private final GameWindowsManager mGameWindowsManager;
+    private final TooltipManager mTooltipManager;
+
+    private final TouchButton mouseLeftTouchButton, mouseRightTouchButton;
+
+    private final Vector2 mCamCenterToPlayer = new Vector2();
+
+    private float mTouchDownX, mTouchDownY;
+    private long mCameraDelayMs = 0L;
+
+    @Inject
+    GameRenderer(MainConfig mainConfig,
+                 MobsController mobsController,
+                 GameWorld gameWorld,
+                 Set<IGameRenderer> renderers,
+                 CursorMouseInputHandler cursorMouseInputHandler,
+                 MouseInputActionMapper mouseInputActionMapper,
+                 KeyboardInputActionMapper keyboardInputActionMapper,
+                 Set<IMouseInputHandler> mouseInputHandlers,
+                 Set<IKeyboardInputHandler> keyboardInputHandlers,
+                 GameWindowsManager gameWindowsManager,
+                 TooltipManager tooltipManager) {
+        super(mainConfig.getWidth(), mainConfig.getHeight());
+
+        mMainConfig = mainConfig;
+        mMobsController = mobsController;
+        mGameWorld = gameWorld;
+        mRenderers = new ArrayList<>(renderers);
+        kotlin.collections.CollectionsKt.sortWith(mRenderers, new Comparator<IGameRenderer>() {
+            @Override
+            public int compare(IGameRenderer o1, IGameRenderer o2) {
+                return o1.getRenderLayer() - o2.getRenderLayer();
+            }
+        });
+        mCursorMouseInputHandler = cursorMouseInputHandler;
+        mMouseInputActionMapper = mouseInputActionMapper;
+        mKeyboardInputActionMapper = keyboardInputActionMapper;
+        mMouseInputHandlers = mouseInputHandlers;
+        mKeyboardInputHandlers = keyboardInputHandlers;
+        mGameWindowsManager = gameWindowsManager;
+        mTooltipManager = tooltipManager;
+
+        mouseLeftTouchButton = new TouchButton(new Rectangle(getWidth() / 2, 0f, getWidth() / 2, getHeight() / 2), Input.Buttons.LEFT, true);
+        mouseRightTouchButton = new TouchButton(new Rectangle(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2), Input.Buttons.RIGHT, true);
+
+        mMainConfig.setJoystick(new Joystick(mMobsController.getPlayer().getSpeed()));
+
+        Gdx.gl.glClearColor(0f, .6f, .6f, 1f);
+    }
+
+    private void updateDynamicCameraPosition(float delta) {
+        Player player = mMobsController.getPlayer();
+
+        float plTargetX = player.getX() + player.getWidth() / 2;
+        float plTargetY = player.getY() + player.getHeight() / 2;
+
+        float camCenterX = getCamX() + getWidth() / 2;
+        float camCenterY = getCamY() + getHeight() / 2;
+
+        float camTargetX, camTargetY;
+
+        boolean followPlayer = player.controlMode == Player.ControlMode.WALK || !mMainConfig.isTouch();
+
+        if (followPlayer) {
+            camTargetX = plTargetX + Math.min(player.getVelocity().x * 2, getWidth() / 2);
+            camTargetY = plTargetY + player.getVelocity().y;
+        } else {
+            camTargetX = MeasureUnitsUtilsKt.getPx(player.cursorX) + MeasureUnitsUtilsKt.getPx(1) / 2;
+            camTargetY = MeasureUnitsUtilsKt.getPx(player.cursorY) + MeasureUnitsUtilsKt.getPx(1) / 2;
+        }
+
+        Vector2 moveVector = new Vector2(camTargetX - camCenterX, camTargetY - camCenterY);
+
+        if (followPlayer && player.getVelocity().isZero()) {
+            mCameraDelayMs = TimeUtils.millis();
+            mCamCenterToPlayer.x = plTargetX - camCenterX;
+            mCamCenterToPlayer.y = plTargetY - camCenterY;
+        }
+
+        if (TimeUtils.timeSinceMillis(mCameraDelayMs) < 500L && !player.getVelocity().isZero()) {
+            updateStaticCameraPosition(plTargetX - mCamCenterToPlayer.x,
+                    camCenterY + moveVector.y * delta * 2);
+            return;
+        }
+
+        float camX = getCamX();
+        float camY = getCamY();
+        float worldWidth = MeasureUnitsUtilsKt.getPx(mGameWorld.getWidth()) - getWidth() / 2;
+
+        if (moveVector.x >= worldWidth) {
+            camX += mGameWorld.getWidthPx();
+            moveVector.x -= mGameWorld.getWidthPx();
+        } else if (moveVector.x <= -worldWidth) {
+            camX -= mGameWorld.getWidthPx();
+            moveVector.x += mGameWorld.getWidthPx();
+        }
+
+        setCamPos(camX + moveVector.x * delta * 2, camY + moveVector.y * delta * 2);
+
+
+        camX = getCamX();
+        camY = getCamY();
+
+        if (camX + getWidth() / 2 > plTargetX + MAX_CAM_DISTANCE_FROM_PLAYER) {
+            camX = plTargetX + MAX_CAM_DISTANCE_FROM_PLAYER - getWidth() / 2;
+        }
+
+        if (camY + getHeight() / 2 > plTargetY + MAX_CAM_DISTANCE_FROM_PLAYER) {
+            camY = plTargetY + MAX_CAM_DISTANCE_FROM_PLAYER - getHeight() / 2;
+        }
+
+        if (camX + getWidth() / 2 < plTargetX - MAX_CAM_DISTANCE_FROM_PLAYER) {
+            camX = plTargetX - MAX_CAM_DISTANCE_FROM_PLAYER - getWidth() / 2;
+        }
+
+        if (camY + getHeight() / 2 < plTargetY - MAX_CAM_DISTANCE_FROM_PLAYER) {
+            camY = plTargetY - MAX_CAM_DISTANCE_FROM_PLAYER - getHeight() / 2;
+        }
+
+        setCamPos(camX, camY);
+    }
+
+    private void updateStaticCameraPosition(float targetX, float targetY) {
+        setCamPos(targetX - getWidth() / 2, targetY - getHeight() / 2);
+    }
+
+    private void updateStaticCameraPosition() {
+        Player player = mMobsController.getPlayer();
+
+        updateStaticCameraPosition(player.getX() + player.getWidth() / 2,
+                player.getY() + player.getHeight() / 2);
+    }
+
+    private void updateCameraPosition(float delta) {
+        if (mMainConfig.isUseDynamicCamera()) {
+            updateDynamicCameraPosition(delta);
+        } else {
+            updateStaticCameraPosition();
+        }
+    }
+
+    private float transformScreenX(int screenX) {
+        return getWidth() / Gdx.graphics.getWidth() * screenX;
+    }
+
+    private float transformScreenY(int screenY) {
+        return getHeight() / Gdx.graphics.getHeight() * screenY;
+    }
+
+    private void handleMousePosition() {
+        final Rectangle viewport = getCameraViewport();
+
+        final float screenX = transformScreenX(Gdx.input.getX());
+        final float screenY = transformScreenY(Gdx.input.getY());
+
+        final MouseInputAction action = new MouseInputAction(
+                screenX,
+                screenY,
+                MouseInputActionKey.None.INSTANCE,
+                viewport);
+
+        mCursorMouseInputHandler.handle(action);
+
+        if (!mTooltipManager.getCurrentMouseTooltip().isEmpty()) {
+            RenderingUtilsKt.drawString(spriter, mTooltipManager.getCurrentMouseTooltip(), screenX + 1, screenY + 1, Color.BLACK);
+            RenderingUtilsKt.drawString(spriter, mTooltipManager.getCurrentMouseTooltip(), screenX, screenY, Color.WHITE);
+        }
+    }
+
+    private boolean handleMouseAction(@Nullable MouseInputAction action) {
+        if (action == null) {
+            return false;
+        }
+
+        boolean anyProcessed = false;
+
+        for (IMouseInputHandler handler : mMouseInputHandlers) {
+            final boolean conditions = handler.checkConditions(action);
+            if (conditions) {
+                anyProcessed = true;
+                handler.handle(action);
+                break;
+            }
+//            anyProcessed = anyProcessed || conditions;
+        }
+        return anyProcessed;
+    }
+
+    private boolean onMouseActionEvent(int mouseX, int mouseY, int button, boolean touchUp, int pointer) {
+        @Nullable MouseInputAction action = mMouseInputActionMapper
+                .map((float) mouseX, (float) mouseY, getCameraViewport(), button, touchUp, pointer);
+        return handleMouseAction(action);
+    }
+
+    @Override
+    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
+        float touchX = transformScreenX(screenX);
+        float touchY = transformScreenY(screenY);
+
+        final Joystick joy = mMainConfig.getJoystick();
+
+        if (mMainConfig.isTouch()) {
+            if (joy != null && joy.getActive() && joy.getPointer() == pointer) {
+                return onMouseActionEvent(screenX, screenY, nullButton.getCode(), true, pointer);
+            }
+
+            TouchButton touchedKey = getTouchedKey(touchX, touchY);
+            if (touchedKey.isMouse()) {
+                return onMouseActionEvent(screenX, screenY, touchedKey.getCode(), true, pointer);
+            } else {
+                return keyUp(touchedKey.getCode());
+            }
+        }
+
+        return onMouseActionEvent(screenX, screenY, button, true, pointer);
+    }
+
+    private TouchButton getTouchedKey(float touchX, float touchY) {
+        if (mGameWindowsManager.getCurrentWindowType() != GameUiWindow.NONE) {
+            return nullButton;
+        }
+        for (ObjectMap.Entry<String, TouchButton> entry : Assets.guiMap) {
+            TouchButton button = entry.value;
+            if (button.getRect().contains(touchX, touchY)) {
+                return button;
+            }
+        }
+
+        if (mouseLeftTouchButton.getRect().contains(touchX, touchY)) {
+            return mouseLeftTouchButton;
+        }
+
+        if (mouseRightTouchButton.getRect().contains(touchX, touchY)) {
+            return mouseRightTouchButton;
+        }
+
+        return nullButton;
+    }
+
+    @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, pointer);
+            } else {
+                return keyDown(touchedKey.getCode());
+            }
+        }
+
+        return onMouseActionEvent(screenX, screenY, button, false, pointer);
+    }
+
+    @Override
+    public boolean touchDragged(int screenX, int screenY, int pointer) {
+        float touchX = transformScreenX(screenX);
+        float touchY = transformScreenY(screenY);
+
+        if (Math.abs(touchX - mTouchDownX) < 16 && Math.abs(touchY - mTouchDownY) < DRAG_THRESHOLD) {
+            return false;
+        }
+
+        @Nullable MouseInputAction action =
+                mMouseInputActionMapper.mapDragged(screenX, screenY, getCameraViewport(), pointer);
+        return handleMouseAction(action);
+    }
+
+    @Override
+    public boolean scrolled(float amountX, float amountY) {
+        @Nullable MouseInputAction action = mMouseInputActionMapper
+                .mapScrolled(Gdx.input.getX(), Gdx.input.getY(), amountX, amountY, getCameraViewport());
+        return handleMouseAction(action);
+    }
+
+    private boolean handleKeyboardAction(int keycode, boolean isKeyDown) {
+        @Nullable final KeyboardInputAction action = mKeyboardInputActionMapper
+                .map(keycode, isKeyDown);
+
+        if (action == null) {
+            return false;
+        }
+
+        boolean anyProcessed = false;
+
+        for (IKeyboardInputHandler handler : mKeyboardInputHandlers) {
+            final boolean conditions = handler.checkConditions(action);
+            if (conditions) {
+                anyProcessed = true;
+                handler.handle(action);
+                break;
+            }
+        }
+
+        return anyProcessed;
+    }
+
+    @Override
+    public boolean keyDown(int keycode) {
+        return handleKeyboardAction(keycode, true);
+    }
+
+    @Override
+    public boolean keyUp(int keycode) {
+        return handleKeyboardAction(keycode, false);
+    }
+
+    @Override
+    public void render(float delta) {
+        updateCameraPosition(delta);
+
+        if (mMainConfig.getJoystick() != null && mMainConfig.getJoystick().getActive()) {
+            mMainConfig.getJoystick().updateState(
+                    transformScreenX(Gdx.input.getX(mMainConfig.getJoystick().getPointer())),
+                    transformScreenY(Gdx.input.getY(mMainConfig.getJoystick().getPointer()))
+            );
+        }
+
+        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
+
+        spriter.begin();
+        for (IGameRenderer iGameRenderer : mRenderers) {
+            iGameRenderer.draw(spriter, shaper, getCameraViewport(), delta);
+        }
+        handleMousePosition();
+        spriter.end();
+
+    }
+
+}
similarity index 71%
rename from core/src/ru/deadsoftware/cavedroid/game/GameScreen.java
rename to core/src/main/java/ru/deadsoftware/cavedroid/game/GameScreen.java
index 370d8bd79066baf1db5c385f4d89556b53c6f740..d7546e98ad60758261240dfbd661425e92f90bc6 100644 (file)
@@ -1,10 +1,9 @@
 package ru.deadsoftware.cavedroid.game;
 
 package ru.deadsoftware.cavedroid.game;
 
-import com.badlogic.gdx.Gdx;
 import com.badlogic.gdx.Screen;
 import com.badlogic.gdx.Screen;
+import org.jetbrains.annotations.Nullable;
 import ru.deadsoftware.cavedroid.MainConfig;
 
 import ru.deadsoftware.cavedroid.MainConfig;
 
-import javax.annotation.CheckForNull;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
@@ -13,28 +12,29 @@ public class GameScreen implements Screen {
 
     private final MainConfig mMainConfig;
 
 
     private final MainConfig mMainConfig;
 
-    @CheckForNull
+    @Nullable
     private GameProc mGameProc;
     private GameProc mGameProc;
-    @CheckForNull
-    private GameInputProcessor mGameInputProcessor;
+    @Nullable
+    private GameItemsHolder mGameItemsHolder;
 
     @Inject
     public GameScreen(MainConfig mainConfig) {
         mMainConfig = mainConfig;
     }
 
 
     @Inject
     public GameScreen(MainConfig mainConfig) {
         mMainConfig = mainConfig;
     }
 
-    public void newGame() {
+    public void newGame(int gameMode) {
         if (mGameProc != null) {
             mGameProc.dispose();
         }
 
         if (mGameProc != null) {
             mGameProc.dispose();
         }
 
+        GameModule.loaded = true;
+
         GameComponent gameComponent = DaggerGameComponent.builder()
                 .mainComponent(mMainConfig.getMainComponent()).build();
 
         mGameProc = gameComponent.getGameProc();
         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() {
     }
 
     public void loadGame() {
@@ -42,15 +42,12 @@ public class GameScreen implements Screen {
             mGameProc.dispose();
         }
 
             mGameProc.dispose();
         }
 
-        GameModule.load(mMainConfig);
+        GameModule.loaded = false;
 
         GameComponent gameComponent = DaggerGameComponent.builder()
                 .mainComponent(mMainConfig.getMainComponent()).build();
 
         mGameProc = gameComponent.getGameProc();
 
         GameComponent gameComponent = DaggerGameComponent.builder()
                 .mainComponent(mMainConfig.getMainComponent()).build();
 
         mGameProc = gameComponent.getGameProc();
-        mGameInputProcessor = gameComponent.getGameInputProcessor();
-
-        Gdx.input.setInputProcessor(gameComponent.getGameInputProcessor());
     }
 
     @Override
     }
 
     @Override
@@ -60,7 +57,8 @@ public class GameScreen implements Screen {
 
     @Override
     public void show() {
 
     @Override
     public void show() {
-        Gdx.input.setInputProcessor(mGameInputProcessor);
+//        Gdx.input.setInputProcessor(mGameInputProcessor);
+        mGameProc.show();
     }
 
     @Override
     }
 
     @Override
@@ -85,6 +83,9 @@ public class GameScreen implements Screen {
 
     @Override
     public void dispose() {
 
     @Override
     public void dispose() {
+        if (mGameProc != null) {
+            mGameProc.dispose();
+        }
     }
 
 }
     }
 
 }
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/game/mobs/Mob.java b/core/src/main/java/ru/deadsoftware/cavedroid/game/mobs/Mob.java
new file mode 100644 (file)
index 0000000..3ee37d3
--- /dev/null
@@ -0,0 +1,356 @@
+package ru.deadsoftware.cavedroid.game.mobs;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.Color;
+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 com.badlogic.gdx.utils.Timer;
+import org.jetbrains.annotations.Nullable;
+import ru.deadsoftware.cavedroid.game.GameItemsHolder;
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto;
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem;
+import ru.deadsoftware.cavedroid.game.world.GameWorld;
+import ru.deadsoftware.cavedroid.misc.Saveable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Mob class.
+ */
+public abstract class Mob extends Rectangle implements  Saveable {
+
+    private static final float DAMAGE_TINT_TIMEOUT_S = 0.5f;
+    private static final Color DAMAGE_TINT_COLOR = new Color(0xff8080ff);
+
+    private static final float HIT_RANGE = 8f;
+
+    protected static int ANIMATION_SPEED = 360;
+
+    public enum Type {
+        MOB,
+        FALLING_BLOCK
+    }
+
+    public enum Direction {
+
+        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;
+        }
+    }
+
+    private class ResetTakeDamageTask extends Timer.Task {
+
+        @Override
+        public void run() {
+            mTakingDamage = false;
+        }
+    }
+
+    protected Vector2 mVelocity;
+    protected Type mType;
+    protected int mAnimDelta = ANIMATION_SPEED;
+    protected float mAnim;
+
+    protected Direction mDirection;
+    protected boolean mDead;
+    protected boolean mCanJump;
+    protected boolean mFlyMode;
+
+    protected int mMaxHealth;
+    protected int mHealth;
+
+    private boolean mTakingDamage = false;
+
+    @Nullable
+    private ResetTakeDamageTask mResetTakeDamageTask = null;
+
+    /**
+     * @param x          in pixels
+     * @param y          in pixels
+     * @param width      in pixels
+     * @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, int maxHealth) {
+        super(x, y, width, height);
+        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) {
+        final float velocityMultiplier = (Math.abs(getVelocity().x) / getSpeed());
+        final float animMultiplier = (velocityMultiplier == 0f ? 1f : velocityMultiplier) * delta;
+        final float maxAnim = 60f * (velocityMultiplier == 0f ? 1f : velocityMultiplier);
+
+        if (mVelocity.x != 0f || Math.abs(mAnim) > mAnimDelta * animMultiplier) {
+            mAnim += mAnimDelta * animMultiplier;
+        } else {
+            mAnim = 0;
+        }
+
+        if (mAnim > maxAnim) {
+            mAnim = maxAnim;
+            mAnimDelta = -ANIMATION_SPEED;
+        } else if (mAnim < -maxAnim) {
+            mAnim = -maxAnim;
+            mAnimDelta = ANIMATION_SPEED;
+        }
+
+        if (mVelocity.x == 0f && isAnimationIncreasing()) {
+            mAnimDelta = -mAnimDelta;
+        }
+    }
+
+    /**
+     * @return The X coordinate of a mob in blocks
+     */
+    public final int getMapX() {
+        return (int) (x + (getWidth() / 2)) / 16;
+    }
+
+    /**
+     * @return The Y coordinate of mob's upper edge in blocks
+     */
+    public final int getUpperMapY() {
+        return (int) (y / 16);
+    }
+
+    /**
+     * @return The Y coordinate if mob's vertical center in blocks
+     */
+    public final int getMiddleMapY() {
+        return (int) (y + (getHeight() / 2)) / 16;
+    }
+
+    /**
+     * @return The Y coordinate of mob's legs in blocks
+     */
+    public final int getLowerMapY() {
+        return (int) (y + getHeight()) / 16;
+    }
+
+    public final float getWidth() {
+        return width;
+    }
+
+    public final float getHeight() {
+        return height;
+    }
+
+    /**
+     * @return Integer representing a direction in which mob is looking, where 0 is left and 1 is right
+     */
+    public final Direction getDirection() {
+        return mDirection;
+    }
+
+    public final boolean looksLeft() {
+        return mDirection == Direction.LEFT;
+    }
+
+    public final boolean looksRight() {
+        return mDirection == Direction.RIGHT;
+    }
+
+    /**
+     * Switches direction in which mob is looking
+     */
+    protected final void switchDir() {
+        mDirection = looksLeft() ? Direction.RIGHT : Direction.LEFT;
+    }
+
+    public final boolean isDead() {
+        return mDead;
+    }
+
+    public final float getAnim() {
+        return mAnim;
+    }
+
+    /**
+     * Set's mob's dead variable to true and nothing else. It doesn't delete the
+     */
+    public void kill() {
+        mDead = true;
+    }
+
+    public final void move(float delta) {
+        x += mVelocity.x * delta;
+        y += mVelocity.y * delta;
+    }
+
+    public final Vector2 getVelocity() {
+        return mVelocity;
+    }
+
+    protected final void setVelocity(Vector2 velocity) {
+        mVelocity = velocity;
+    }
+
+    public final boolean canJump() {
+        return mCanJump;
+    }
+
+    public final void setCanJump(boolean canJump) {
+        this.mCanJump = canJump;
+    }
+
+    public final boolean isFlyMode() {
+        return mFlyMode;
+    }
+
+    public final void setFlyMode(boolean flyMode) {
+        this.mFlyMode = flyMode;
+    }
+
+    public final Type getType() {
+        return mType;
+    }
+
+    public final void checkWorldBounds(GameWorld gameWorld) {
+        if (x + width / 2 < 0) {
+            x += gameWorld.getWidthPx();
+        }
+        if (x + width / 2 > gameWorld.getWidthPx()) {
+            x -= gameWorld.getWidthPx();
+        }
+    }
+
+    public final int getHealth() {
+        return mHealth;
+    }
+
+    public final int getMaxHealth() {
+        return mMaxHealth;
+    }
+
+    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;
+        }
+
+        if (mHealth <= Integer.MIN_VALUE + damage) {
+            mHealth = Integer.MIN_VALUE + damage;
+        }
+
+        mHealth -= damage;
+        checkHealth();
+
+        setTakingDamage(true);
+    }
+
+    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 Rectangle getHitBox() {
+        return new Rectangle(x - HIT_RANGE, y - HIT_RANGE, width + HIT_RANGE, height + HIT_RANGE);
+    }
+
+    public boolean isTakingDamage() {
+        return mTakingDamage;
+    }
+
+    public void setTakingDamage(boolean takingDamage) {
+        mTakingDamage = takingDamage;
+
+        if (takingDamage) {
+            if (mResetTakeDamageTask != null && mResetTakeDamageTask.isScheduled()) {
+                mResetTakeDamageTask.cancel();
+            } else if (mResetTakeDamageTask == null) {
+                mResetTakeDamageTask = new ResetTakeDamageTask();
+            }
+
+            Timer.schedule(mResetTakeDamageTask, DAMAGE_TINT_TIMEOUT_S);
+        }
+    }
+
+    protected Color getTintColor() {
+        return isTakingDamage() ? DAMAGE_TINT_COLOR : Color.WHITE;
+    }
+
+    public List<InventoryItem> getDrop(GameItemsHolder gameItemsHolder) {
+        return Collections.emptyList();
+    }
+
+    public abstract void draw(SpriteBatch spriteBatch, float x, float y, float delta);
+
+    public abstract void ai(GameWorld gameWorld, GameItemsHolder gameItemsHolder, MobsController mobsController, float delta);
+
+    public abstract void changeDir();
+
+    public abstract float getSpeed();
+
+    public abstract void jump();
+
+    @Override
+    public abstract SaveDataDto.MobSaveDataDto getSaveData();
+
+    public static Mob fromSaveData(SaveDataDto.MobSaveDataDto saveData) {
+        return MobSaveDataMapperKt.fromSaveData(saveData);
+    }
+}
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/game/mobs/player/Player.java b/core/src/main/java/ru/deadsoftware/cavedroid/game/mobs/player/Player.java
new file mode 100644 (file)
index 0000000..9aa503f
--- /dev/null
@@ -0,0 +1,520 @@
+package ru.deadsoftware.cavedroid.game.mobs.player;
+
+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 org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import ru.deadsoftware.cavedroid.game.GameItemsHolder;
+import ru.deadsoftware.cavedroid.game.mobs.Mob;
+import ru.deadsoftware.cavedroid.game.mobs.MobsController;
+import ru.deadsoftware.cavedroid.game.model.block.Block;
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto;
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem;
+import ru.deadsoftware.cavedroid.game.model.item.Item;
+import ru.deadsoftware.cavedroid.game.objects.drop.Drop;
+import ru.deadsoftware.cavedroid.game.objects.drop.DropController;
+import ru.deadsoftware.cavedroid.game.ui.TooltipManager;
+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;
+
+public class Player extends Mob {
+
+    private static final int SAVE_DATA_VERSION = 1;
+
+    private static final float SPEED = 69.072f;
+    private static final float JUMP_VELOCITY = -133.332f;
+    private static final int SURVIVAL_CURSOR_RANGE = 4;
+
+    public static final int MAX_HEALTH = 20;
+    public static final int INVENTORY_SIZE = 36;
+    public static final int HOTBAR_SIZE = 9;
+
+    private boolean hitting = false, hittingWithDamage = false;
+    private float hitAnim = 0f;
+    private float hitAnimDelta = ANIMATION_SPEED;
+
+    public Inventory inventory;
+
+    public int gameMode;
+    public boolean swim;
+    public float headRotation = 0f;
+
+    public float blockDamage = 0f;
+    public int cursorX = 0;
+    public int cursorY = 0;
+
+    @Nullable
+    private Vector2 spawnPoint = null;
+
+    public ControlMode controlMode = ControlMode.WALK;
+
+    public enum ControlMode {
+        WALK,
+        CURSOR
+    }
+
+    public Player(GameItemsHolder gameItemsHolder, TooltipManager tooltipManager) {
+        super(0, 0, 4, 30, randomDir(), Type.MOB, MAX_HEALTH);
+        inventory = new Inventory(INVENTORY_SIZE, HOTBAR_SIZE, gameItemsHolder, tooltipManager);
+        swim = false;
+    }
+
+    public void initInventory(GameItemsHolder gameItemsHolder, TooltipManager tooltipManager) {
+        inventory.initItems(gameItemsHolder, tooltipManager);
+    }
+
+    public void respawn(GameWorld gameWorld, GameItemsHolder itemsHolder) {
+        Vector2 pos = getSpawnPoint(gameWorld, itemsHolder);
+        this.x = pos.x;
+        this.y = pos.y;
+        mVelocity.setZero();
+        mDead = false;
+        heal(MAX_HEALTH);
+    }
+
+    public void decreaseCurrentItemCount(GameItemsHolder gameItemsHolder) {
+        if (gameMode == 1) {
+            return;
+        }
+
+        final InventoryItem item = inventory.getActiveItem();
+        item.subtract();
+        if (item.getAmount() <= 0) {
+            setCurrentInventorySlotItem(gameItemsHolder.getFallbackItem());
+        }
+    }
+
+    public void dropCurrentItem(DropController dropController) {
+        final InventoryItem activeItem = inventory.getActiveItem();
+
+    }
+
+    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;
+            }
+        }
+        spawnPoint = new Vector2(x * 16 + 8 - getWidth() / 2, (float) y * 16 - getHeight());
+        return spawnPoint;
+    }
+
+    public void setDir(Direction dir) {
+        if (dir != getDirection()) {
+            switchDir();
+        }
+    }
+
+    public void setCurrentInventorySlotItem(Item item) {
+        inventory.getItems().set(inventory.getActiveSlot(), item.toInventoryItem());
+    }
+
+    @Override
+    public float getSpeed() {
+        return SPEED;
+    }
+
+    @Override
+    public void jump() {
+        if (!canJump()) {
+            if (gameMode == 1) {
+                if (isFlyMode()) {
+                    setFlyMode(false);
+                } else {
+                    getVelocity().y = 0f;
+                    setFlyMode(true);
+                }
+            }
+            return;
+        }
+        mVelocity.y = JUMP_VELOCITY;
+    }
+
+    private boolean checkBlockCanBeHit(Block block) {
+        return !block.isNone() && block.getParams().getHitPoints() >= 0;
+    }
+
+    /**
+     * @return true if any mob fas hit
+     */
+    private boolean hitMobs(GameItemsHolder gameItemsHolder, MobsController mobsController) {
+        if (!hitting || !hittingWithDamage) {
+            return false;
+        }
+
+        boolean result = false;
+        for (Mob mob : mobsController.getMobs()) {
+            if (overlaps(mob.getHitBox())) {
+                final Item activeItem = inventory.getActiveItem().getItem();
+                final Item.Tool tool = activeItem.isTool() ? (Item.Tool) activeItem : null;
+                if (tool != null) {
+                    decreaseCurrentItemCount(gameItemsHolder);
+                }
+                result = true;
+                mob.damage(MathUtils.floor(tool != null ? tool.getMobDamageMultiplier() : 1));
+            }
+        }
+        return result;
+    }
+
+    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 ((checkBlockCanBeHit(foregroundBlock)) ||
+                (foregroundBlock.isNone() && checkBlockCanBeHit(backgroundBlock))) {
+            if (gameMode == 0) {
+                if (!foregroundBlock.isNone()) {
+                    if (blockDamage >= foregroundBlock.getParams().getHitPoints()) {
+                        gameWorld.destroyForeMap(cursorX, cursorY);
+                        blockDamage = 0;
+                    }
+                } else if (!backgroundBlock.isNone()) {
+                    if (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, MobsController mobsController, float delta) {
+        updateAnimation(delta);
+
+        if (!hitMobs(gameItemsHolder, mobsController)) {
+            hitBlock(gameWorld, gameItemsHolder);
+        } else {
+            stopHitting();
+        }
+
+        if (gameMode == 1) {
+            return;
+        }
+
+        final Block foregroundBlock = gameWorld.getForeMap(cursorX, cursorY);
+        final Block backgroundBlock = gameWorld.getBackMap(cursorX, cursorY);
+        @Nullable final Block target;
+
+        if (checkBlockCanBeHit(foregroundBlock)) {
+            target = foregroundBlock;
+        } else if (checkBlockCanBeHit(backgroundBlock)) {
+            target = backgroundBlock;
+        } else {
+            target = null;
+        }
+
+        final boolean canHitBlock = target != null;
+
+        float multiplier = 1f;
+        final Item currentItem = inventory.getActiveItem().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
+    public void changeDir() {
+    }
+
+    @Override
+    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);
+    }
+
+    public void checkCursorBounds(GameWorld gameWorld) {
+        if (gameMode == 0) {
+            int minCursorX = getMapX() - SURVIVAL_CURSOR_RANGE;
+            int maxCursorX = getMapX() + SURVIVAL_CURSOR_RANGE;
+            int minCursorY = getMiddleMapY() - SURVIVAL_CURSOR_RANGE;
+            int maxCursorY = getMiddleMapY() + SURVIVAL_CURSOR_RANGE;
+
+            cursorX = MathUtils.clamp(cursorX, minCursorX, maxCursorX);
+            cursorY = MathUtils.clamp(cursorY, minCursorY, maxCursorY);
+        }
+
+        cursorY = MathUtils.clamp(cursorY, 0, gameWorld.getHeight() - 1);
+    }
+
+    private void drawItem(SpriteBatch spriteBatch, float x, float y, float anim) {
+        final Item item = inventory.getActiveItem().getItem();
+
+        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 {
+            backHandAnim = -mAnim;
+            frontHandAnim = -rightHandAnim;
+        }
+
+        backHand.setColor(getTintColor());
+        backLeg.setColor(getTintColor());
+        frontLeg.setColor(getTintColor());
+        head.setColor(getTintColor());
+        body.setColor(getTintColor());
+        frontHand.setColor(getTintColor());
+
+        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);
+    }
+
+    @NotNull
+    @Override
+    public SaveDataDto.PlayerSaveData getSaveData() {
+        return new SaveDataDto.PlayerSaveData(
+                SAVE_DATA_VERSION,
+                mType,
+                mAnimDelta,
+                mAnim,
+                mDirection,
+                mDead,
+                mCanJump,
+                mFlyMode,
+                mMaxHealth,
+                mHealth,
+                x,
+                y,
+                width,
+                height,
+                getVelocity().x,
+                getVelocity().y,
+                hitting,
+                hittingWithDamage,
+                hitAnim,
+                hitAnimDelta,
+                inventory.getSaveData(),
+                gameMode,
+                swim,
+                headRotation,
+                blockDamage,
+                cursorX,
+                cursorY,
+                spawnPoint != null ? spawnPoint.x : 0f,
+                spawnPoint != null ? spawnPoint.y : 0f,
+                controlMode
+        );
+    }
+
+    public static Player fromSaveData(
+            SaveDataDto.PlayerSaveData saveData,
+            GameItemsHolder gameItemsHolder,
+            TooltipManager tooltipManager
+    ) {
+        saveData.verifyVersion(SAVE_DATA_VERSION);
+
+        Player player = new Player(gameItemsHolder, tooltipManager);
+
+        player.mType = saveData.getType();
+        player.mAnimDelta = saveData.getAnimDelta();
+        player.mAnim = saveData.getAnim();
+        player.mDirection = saveData.getDirection();
+        player.mDead = saveData.getDead();
+        player.mCanJump = saveData.getCanJump();
+        player.mFlyMode = saveData.getFlyMode();
+        player.mMaxHealth = saveData.getMaxHealth();
+        player.mHealth = saveData.getHealth();
+        player.x = saveData.getX();
+        player.y = saveData.getY();
+        player.width = saveData.getWidth();
+        player.height = saveData.getHeight();
+        player.hitting = saveData.getHitting();
+        player.hittingWithDamage = saveData.getHittingWithDamage();
+        player.hitAnim = saveData.getHitAnim();
+        player.hitAnimDelta = saveData.getHitAnimDelta();
+        player.inventory = Inventory.Companion.fromSaveData(saveData.getInventory(), gameItemsHolder, tooltipManager);
+        player.gameMode = saveData.getGameMode();
+        player.swim = saveData.getSwim();
+        player.headRotation = saveData.getHeadRotation();
+        player.blockDamage = saveData.getBlockDamage();
+        player.cursorX = saveData.getCursorX();
+        player.cursorY = saveData.getCursorY();
+        player.spawnPoint = new Vector2(saveData.getSpawnPointX(), saveData.getSpawnPointY());
+        player.controlMode = saveData.getControlMode();
+
+        return player;
+    }
+}
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/game/objects/drop/DropController.java b/core/src/main/java/ru/deadsoftware/cavedroid/game/objects/drop/DropController.java
new file mode 100644 (file)
index 0000000..7b99676
--- /dev/null
@@ -0,0 +1,88 @@
+package ru.deadsoftware.cavedroid.game.objects.drop;
+
+import org.jetbrains.annotations.NotNull;
+import ru.deadsoftware.cavedroid.game.GameItemsHolder;
+import ru.deadsoftware.cavedroid.game.GameScope;
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto;
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem;
+import ru.deadsoftware.cavedroid.game.model.item.Item;
+import ru.deadsoftware.cavedroid.misc.Saveable;
+
+import javax.inject.Inject;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+@GameScope
+public class DropController implements Saveable {
+
+    private static final int SAVE_DATA_VERSION = 1;
+
+    public interface Callback {
+        void run(Drop drop);
+    }
+
+    private final LinkedList<Drop> mDrops = new LinkedList<>();
+
+    @Inject
+    public DropController() {
+    }
+
+    public void initDrops(GameItemsHolder gameItemsHolder) {
+        for (Drop drop : mDrops) {
+            drop.initItem(gameItemsHolder);
+        }
+    }
+
+    public void addDrop(float x, float y, Item item) {
+        addDrop(x, y, item, 1);
+    }
+
+    public void addDrop(float x, float y, Item item, int count) {
+        if (item.isNone()) {
+            return;
+        }
+        mDrops.add(new Drop(x, y, item, count));
+    }
+
+    public void addDrop(float x, float y, @NotNull InventoryItem invItem) {
+        addDrop(x, y, invItem.getItem(), invItem.getAmount());
+    }
+
+    public int getSize() {
+        return mDrops.size();
+    }
+
+    public void forEach(Callback callback) {
+        for (Drop drop : mDrops) {
+            callback.run(drop);
+        }
+    }
+
+    public Iterator<Drop> getIterator() {
+        return mDrops.iterator();
+    }
+
+    @Override
+    @NotNull
+    public SaveDataDto.DropControllerSaveData getSaveData() {
+        final LinkedList<SaveDataDto.DropSaveData> dropSaveData = new LinkedList<>();
+        for (Drop drop : mDrops) {
+            dropSaveData.add(drop.getSaveData());
+        }
+        return new SaveDataDto.DropControllerSaveData(SAVE_DATA_VERSION, dropSaveData);
+    }
+
+    public static DropController fromSaveData(
+            SaveDataDto.DropControllerSaveData saveData, GameItemsHolder gameItemsHolder) {
+        saveData.verifyVersion(SAVE_DATA_VERSION);
+
+        DropController controller = new DropController();
+
+        for (SaveDataDto.DropSaveData dropSaveData : saveData.getDrops()) {
+            controller.mDrops.add(Drop.Companion.fromSaveData(dropSaveData, gameItemsHolder));
+        }
+
+        return controller;
+    }
+
+}
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/game/world/GameWorld.java b/core/src/main/java/ru/deadsoftware/cavedroid/game/world/GameWorld.java
new file mode 100644 (file)
index 0000000..b53e0a6
--- /dev/null
@@ -0,0 +1,314 @@
+package ru.deadsoftware.cavedroid.game.world;
+
+import com.badlogic.gdx.math.MathUtils;
+import kotlin.Pair;
+import org.jetbrains.annotations.Nullable;
+import ru.deadsoftware.cavedroid.game.GameItemsHolder;
+import ru.deadsoftware.cavedroid.game.GameScope;
+import ru.deadsoftware.cavedroid.game.mobs.MobsController;
+import ru.deadsoftware.cavedroid.game.mobs.Pig;
+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.container.Container;
+import ru.deadsoftware.cavedroid.game.objects.drop.DropController;
+import ru.deadsoftware.cavedroid.game.objects.container.Furnace;
+import ru.deadsoftware.cavedroid.game.objects.container.ContainerController;
+import ru.deadsoftware.cavedroid.misc.utils.MeasureUnitsUtilsKt;
+
+import javax.inject.Inject;
+
+@GameScope
+public class GameWorld {
+
+    private static final int FOREGROUND_Z = 0;
+    private static final int BACKGROUND_Z = 1;
+
+    private final DropController mDropController;
+    private final MobsController mMobsController;
+    private final GameItemsHolder mGameItemsHolder;
+    private final ContainerController mContainerController;
+
+    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,
+                     ContainerController containerController,
+                     @Nullable Block[][] foreMap,
+                     @Nullable Block[][] backMap) {
+        mDropController = dropController;
+        mMobsController = mobsController;
+        mGameItemsHolder = gameItemsHolder;
+        mContainerController = containerController;
+
+        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();
+            spawnInitialMobs();
+            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();
+
+        if (y < 0 || y >= getHeight()) {
+            return map;
+        }
+
+        x = transformX(x);
+
+        if (x < 0 || x >= getWidth()) {
+            return map;
+        }
+
+        map = (layer == 0) ? mForeMap[x][y] : mBackMap[x][y];
+
+        return map;
+    }
+
+    private void setMap(int x, int y, int layer, Block value) {
+        if (y < 0 || y >= getHeight()) {
+            return;
+        }
+
+        x = transformX(x);
+
+        if (x < 0 || x >= getWidth()) {
+            return;
+        }
+
+        mContainerController.destroyContainer(x, y, layer, false);
+
+        if (value.isContainer()) {
+            mContainerController.addContainer(x, y, layer, (Class<? extends Block.Container>) value.getClass());
+        }
+
+        if (layer == 0) {
+            mForeMap[x][y] = value;
+        } else {
+            mBackMap[x][y] = value;
+        }
+    }
+
+    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, FOREGROUND_Z) != mGameItemsHolder.getFallbackBlock();
+    }
+
+    public boolean hasBackAt(int x, int y) {
+        return getMap(x, y, BACKGROUND_Z) != mGameItemsHolder.getFallbackBlock();
+    }
+
+    public Block getForeMap(int x, int y) {
+        return getMap(x, y, FOREGROUND_Z);
+    }
+
+    public void setForeMap(int x, int y, Block block) {
+        setMap(x, y, FOREGROUND_Z, block);
+    }
+
+    public void resetForeMap(int x, int y) {
+        setForeMap(x, y, mGameItemsHolder.getFallbackBlock());
+    }
+
+    public Block getBackMap(int x, int y) {
+        return getMap(x, y, BACKGROUND_Z);
+    }
+
+    public void setBackMap(int x, int y, Block block) {
+        setMap(x, y, BACKGROUND_Z, block);
+    }
+
+    public boolean canPlaceToForeground(int x, int y, Block value) {
+        return !hasForeAt(x, y) || value == mGameItemsHolder.getFallbackBlock() || !getForeMap(x, y).hasCollision();
+    }
+
+    public boolean placeToForeground(int x, int y, Block value) {
+        if (canPlaceToForeground(x, y, value)) {
+            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") || value.isChest() || value.isSlab())) {
+            setBackMap(x, y, value);
+            return true;
+        }
+        return false;
+    }
+
+    private void playerDurateTool() {
+        final InventoryItem playerCurrentItem = mMobsController.getPlayer().inventory.getActiveItem();
+        if (playerCurrentItem.getItem().isTool()) {
+            mMobsController.getPlayer().decreaseCurrentItemCount(mGameItemsHolder);
+        }
+    }
+
+    private boolean shouldDrop(Block block) {
+        final Item item = mMobsController.getPlayer().inventory.getActiveItem().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();
+    }
+
+    private void spawnInitialMobs() {
+        for (int x = 0; x < getWidth(); x++) {
+            int y = 0;
+            while (y < getWorldConfig().getSeaLevel()) {
+                if (getForeMap(x, y) == mGameItemsHolder.getBlock("grass")) {
+                    if (MathUtils.randomBoolean(.125f)) {
+                        mMobsController.addMob(new Pig(MeasureUnitsUtilsKt.getPx(x), MeasureUnitsUtilsKt.getPx(y)));
+                    }
+                    break;
+                }
+                y++;
+            }
+        }
+    }
+
+    public void destroyForeMap(int x, int y) {
+        Block block = getForeMap(x, y);
+        if (block.isContainer()) {
+            mContainerController.destroyContainer(x, y, FOREGROUND_Z, true);
+        }
+        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.isContainer()) {
+            mContainerController.destroyContainer(x, y, BACKGROUND_Z, true);
+        }
+        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());
+    }
+
+    @Nullable
+    private Container getContainerAt(int x, int y, int z) {
+        return mContainerController.getContainer(transformX(x), y, z);
+    }
+
+    @Nullable
+    public Container getForegroundContainer(int x, int y) {
+        return getContainerAt(x, y, FOREGROUND_Z);
+    }
+
+    @Nullable
+    public Container getBackgroundContainer(int x, int y) {
+        return getContainerAt(x, y, BACKGROUND_Z);
+    }
+
+    @Nullable
+    public Furnace getForegroundFurnace(int x, int y) {
+        @Nullable
+        final Container container = getForegroundContainer(x, y);
+
+        if (container instanceof Furnace) {
+            return (Furnace) container;
+        }
+
+        return null;
+    }
+
+    @Nullable
+    public Furnace getBackgroundFurnace(int x, int y) {
+        @Nullable
+        final Container container = getBackgroundContainer(x, y);
+
+        if (container instanceof Furnace) {
+            return (Furnace) container;
+        }
+
+        return null;
+    }
+}
\ No newline at end of file
similarity index 58%
rename from core/src/ru/deadsoftware/cavedroid/menu/MenuProc.java
rename to core/src/main/java/ru/deadsoftware/cavedroid/menu/MenuProc.java
index f96d4d42756545c2d13cfc20de9a74655f986c15..aa3888a5b0c26181b38f1102694b8344e6ba7b9b 100644 (file)
@@ -5,13 +5,15 @@ import com.badlogic.gdx.utils.ObjectMap;
 import ru.deadsoftware.cavedroid.CaveGame;
 import ru.deadsoftware.cavedroid.MainConfig;
 import ru.deadsoftware.cavedroid.menu.objects.Button;
 import ru.deadsoftware.cavedroid.CaveGame;
 import ru.deadsoftware.cavedroid.MainConfig;
 import ru.deadsoftware.cavedroid.menu.objects.Button;
-import ru.deadsoftware.cavedroid.menu.submenus.Menu;
-import ru.deadsoftware.cavedroid.menu.submenus.MenuMain;
-import ru.deadsoftware.cavedroid.menu.submenus.MenuNewGame;
+import ru.deadsoftware.cavedroid.menu.submenus.*;
 import ru.deadsoftware.cavedroid.misc.Renderer;
 
 import javax.inject.Inject;
 
 import ru.deadsoftware.cavedroid.misc.Renderer;
 
 import javax.inject.Inject;
 
+import java.lang.reflect.Method;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import static ru.deadsoftware.cavedroid.misc.Assets.*;
 
 @MenuScope
 import static ru.deadsoftware.cavedroid.misc.Assets.*;
 
 @MenuScope
@@ -19,7 +21,7 @@ public class MenuProc extends Renderer {
 
     public class Input {
         private void startNewGame(int gameMode) {
 
     public class Input {
         private void startNewGame(int gameMode) {
-            mMainConfig.getCaveGame().newGame();
+            mMainConfig.getCaveGame().newGame(gameMode);
         }
 
         public void newGameClicked() {
         }
 
         public void newGameClicked() {
@@ -30,6 +32,10 @@ public class MenuProc extends Renderer {
             mMainConfig.getCaveGame().loadGame();
         }
 
             mMainConfig.getCaveGame().loadGame();
         }
 
+        public void optionsClicked() {
+            mCurrentMenu = mMenuOptions;
+        }
+
         public void quitClicked() {
             Gdx.app.exit();
         }
         public void quitClicked() {
             Gdx.app.exit();
         }
@@ -45,35 +51,67 @@ public class MenuProc extends Renderer {
         public void backClicked() {
             mCurrentMenu = mMenuMain;
         }
         public void backClicked() {
             mCurrentMenu = mMenuMain;
         }
+
+        public void setPreference(String key, Object value) {
+            mMainConfig.setPreference(key, value.toString());
+        }
     }
 
     private final MainConfig mMainConfig;
 
     private final MenuMain mMenuMain;
     private final MenuNewGame mMenuNewGame;
     }
 
     private final MainConfig mMainConfig;
 
     private final MenuMain mMenuMain;
     private final MenuNewGame mMenuNewGame;
+    private final MenuOptions mMenuOptions;
 
     private Menu mCurrentMenu;
 
     @Inject
 
     private Menu mCurrentMenu;
 
     @Inject
-    public MenuProc(MainConfig mainConfig) {
+    public MenuProc(
+            MainConfig mainConfig,
+            MenusFactory menusFactory
+    ) {
         super(mainConfig.getWidth(), mainConfig.getHeight());
 
         mMainConfig = mainConfig;
 
         Input menuInput = new Input();
 
         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 = menusFactory.getMainMenu(getWidth(), getHeight(), this::drawButton, menuInput);
+        mMenuNewGame = menusFactory.getMenuNewGame(getWidth(), getHeight(), this::drawButton, menuInput);
+        mMenuOptions = menusFactory.getMenuOptions(getWidth(), getHeight(), this::drawButton, menuInput);
 
         mCurrentMenu = mMenuMain;
     }
 
 
         mCurrentMenu = mMenuMain;
     }
 
+    private String processVariables(String raw) {
+        final Pattern pattern = Pattern.compile("%%([A-Za-z]+)%%", Pattern.CASE_INSENSITIVE);
+        final Matcher matcher = pattern.matcher(raw);
+        while (matcher.find()) {
+            for (int i = 0; i < matcher.groupCount(); i++) {
+                try {
+                    final String group = matcher.group(i);
+                    final String name = group.replaceAll("%%", "");
+                    final Method method = mMainConfig.getClass().getMethod(name);
+                    final String value = method.invoke(mMainConfig).toString();
+                    raw = raw.replace(group, value);
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        return raw;
+    }
+
     private void drawButton(Button button) {
         spriter.draw(textureRegions.get("button_" + button.getType()), button.getX(), button.getY());
         setFontColor(255, 255, 255);
     private void drawButton(Button button) {
         spriter.draw(textureRegions.get("button_" + button.getType()), button.getX(), button.getY());
         setFontColor(255, 255, 255);
-        drawString(button.getLabel(),
-                (button.getX() + button.getWidth() / 2) - (float) getStringWidth(button.getLabel()) / 2,
-                (button.getY() + button.getHeight() / 2) - (float) getStringHeight(button.getLabel()) / 2);
+
+        String label = processVariables(button.getLabel());
+
+        drawString(label,
+                (button.getX() + button.getWidth() / 2) - (float) getStringWidth(label) / 2,
+                (button.getY() + button.getHeight() / 2) - (float) getStringHeight(label) / 2);
     }
 
     @Override
     }
 
     @Override
similarity index 72%
rename from core/src/ru/deadsoftware/cavedroid/menu/submenus/Menu.java
rename to core/src/main/java/ru/deadsoftware/cavedroid/menu/submenus/Menu.java
index 9f8df4c25d96d498c6019c1e123ef99a61e05c6e..7e793b7b1db1d77a21a85dbb2faf30e4437974bd 100644 (file)
@@ -1,17 +1,21 @@
 package ru.deadsoftware.cavedroid.menu.submenus;
 
 package ru.deadsoftware.cavedroid.menu.submenus;
 
+import com.badlogic.gdx.Application;
 import com.badlogic.gdx.Gdx;
 import com.badlogic.gdx.files.FileHandle;
 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 import com.badlogic.gdx.graphics.g2d.TextureRegion;
 import com.badlogic.gdx.utils.ArrayMap;
 import com.badlogic.gdx.utils.JsonValue;
 import com.badlogic.gdx.Gdx;
 import com.badlogic.gdx.files.FileHandle;
 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 import com.badlogic.gdx.graphics.g2d.TextureRegion;
 import com.badlogic.gdx.utils.ArrayMap;
 import com.badlogic.gdx.utils.JsonValue;
+import kotlin.text.StringsKt;
 import ru.deadsoftware.cavedroid.MainConfig;
 import ru.deadsoftware.cavedroid.menu.MenuProc;
 import ru.deadsoftware.cavedroid.MainConfig;
 import ru.deadsoftware.cavedroid.menu.MenuProc;
+import ru.deadsoftware.cavedroid.menu.objects.BooleanOptionButton;
 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.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;
 
 
 import java.util.HashMap;
 
@@ -19,6 +23,7 @@ public abstract class Menu {
 
     protected final MainConfig mMainConfig;
     protected final MenuProc.Input mMenuInput;
 
     protected final MainConfig mMainConfig;
     protected final MenuProc.Input mMenuInput;
+    protected final AssetLoader mAssetLoader;
 
     private final ButtonRenderer mButtonRenderer;
 
 
     private final ButtonRenderer mButtonRenderer;
 
@@ -35,12 +40,18 @@ public abstract class Menu {
      * @param height         Viewport height
      * @param buttonRenderer {@link ButtonRenderer} that will draw the buttons of this 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;
         mWidth = width;
         mHeight = height;
         mButtonRenderer = buttonRenderer;
         mMainConfig = mainConfig;
         mMenuInput = menuInput;
+        mAssetLoader = assetLoader;
         initButtons();
     }
 
         initButtons();
     }
 
@@ -68,13 +79,27 @@ public abstract class Menu {
         JsonValue json = Assets.jsonReader.parse(jsonFile);
         int y = (int) mHeight / 4;
         for (JsonValue key = json.child(); key != null; key = key.next(), y += Button.HEIGHT + 10) {
         JsonValue json = Assets.jsonReader.parse(jsonFile);
         int y = (int) mHeight / 4;
         for (JsonValue key = json.child(); key != null; key = key.next(), y += Button.HEIGHT + 10) {
-            buttons.put(key.name(),
-                    new Button(Assets.getStringFromJson(key, "label", ""),
-                            (int) mWidth / 2 - Button.WIDTH / 2,
-                            Assets.getIntFromJson(key, "y", y),
-                            Assets.getIntFromJson(key, "type", Button.NORMAL),
-                            eventListeners.containsKey(key.name()) ? eventListeners.get(key.name()) : () -> {
-                            }));
+
+            if (Gdx.app.getType() == Application.ApplicationType.Android &&
+                    !Assets.getBooleanFromJson(key, "visible_on_android", true)) {
+                continue;
+            }
+
+            String optionType = Assets.getStringFromJson(key, "option_type", "");
+            String label = Assets.getStringFromJson(key, "label", "");
+            int x = (int) mWidth / 2 - Button.WIDTH / 2;
+            int type = Assets.getIntFromJson(key, "type", Button.NORMAL);
+            String defaultValue = Assets.getStringFromJson(key, "default_value", "");
+
+
+            Button button = switch (optionType) {
+                case "boolean" ->
+                    new BooleanOptionButton(mMainConfig, key.name(), Boolean.parseBoolean(defaultValue), label, x, y, type);
+                default ->
+                    new Button(label, x, y, type, eventListeners.containsKey(key.name()) ? eventListeners.get(key.name()) : () -> {});
+            };
+
+            buttons.put(key.name(), button);
         }
     }
 
         }
     }
 
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/menu/submenus/MenuMain.java b/core/src/main/java/ru/deadsoftware/cavedroid/menu/submenus/MenuMain.java
new file mode 100644 (file)
index 0000000..29aeb8f
--- /dev/null
@@ -0,0 +1,60 @@
+package ru.deadsoftware.cavedroid.menu.submenus;
+
+import ru.deadsoftware.cavedroid.MainConfig;
+import ru.deadsoftware.cavedroid.game.save.GameSaveLoader;
+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,
+                    AssetLoader assetLoader) {
+        super(width, height, buttonRenderer, mainConfig, menuInput, assetLoader);
+    }
+
+    @Override
+    protected HashMap<String, ButtonEventListener> getButtonEventListeners() {
+        HashMap<String, ButtonEventListener> map = new HashMap<>();
+        map.put("new_game", mMenuInput::newGameClicked);
+        map.put("load_game", mMenuInput::loadGameClicked);
+        map.put("options", mMenuInput::optionsClicked);
+        map.put("quit", mMenuInput::quitClicked);
+        return map;
+    }
+
+    @Override
+    protected void initButtons() {
+        loadButtonsFromJson(mAssetLoader.getAssetHandle("json/menu_main_buttons.json"));
+        if (GameSaveLoader.INSTANCE.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);
+        }
+
+    }
+
+}
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/menu/submenus/MenuNewGame.java b/core/src/main/java/ru/deadsoftware/cavedroid/menu/submenus/MenuNewGame.java
new file mode 100644 (file)
index 0000000..33985ff
--- /dev/null
@@ -0,0 +1,53 @@
+package ru.deadsoftware.cavedroid.menu.submenus;
+
+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,
+                       AssetLoader assetLoader) {
+        super(width, height, buttonRenderer, mainConfig, menuInput, assetLoader);
+    }
+
+    @Override
+    protected HashMap<String, ButtonEventListener> getButtonEventListeners() {
+        HashMap<String, ButtonEventListener> map = new HashMap<>();
+        map.put("survival", mMenuInput::survivalClicked);
+        map.put("creative", mMenuInput::creativeClicked);
+        map.put("back", mMenuInput::backClicked);
+        return map;
+    }
+
+    @Override
+    protected void initButtons() {
+        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);
+        }
+
+    }
+}
diff --git a/core/src/main/java/ru/deadsoftware/cavedroid/misc/Assets.java b/core/src/main/java/ru/deadsoftware/cavedroid/misc/Assets.java
new file mode 100644 (file)
index 0000000..893414b
--- /dev/null
@@ -0,0 +1,260 @@
+package ru.deadsoftware.cavedroid.misc;
+
+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();
+    public static BitmapFont minecraftFont;
+
+    public static Map<String, Texture> blockTextures = new HashMap<>();
+    public static Map<String, Texture> itemTextures = new HashMap<>();
+
+    public static Sprite joyBackground;
+    public static Sprite joyStick;
+
+    public static Sprite furnaceBurn;
+    public static Sprite furnaceProgress;
+
+    public static void dispose() {
+        minecraftFont.dispose();
+        for (Texture texture : loadedTextures) {
+            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);
+    }
+
+    private static Sprite flippedSprite(Texture texture) {
+        Sprite sprite = new Sprite(texture);
+        sprite.flip(false, true);
+        return sprite;
+    }
+
+    private static Sprite flippedSprite(TextureRegion texture) {
+        Sprite sprite = new Sprite(texture);
+        sprite.flip(false, true);
+        return sprite;
+    }
+
+    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(loadTexture(
+                        assetLoader.getAssetHandle("pp/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("pp/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(AssetLoader assetLoader) {
+        JsonValue json = jsonReader.parse(assetLoader.getAssetHandle("json/texture_regions.json"));
+        for (JsonValue file = json.child(); file != null; file = file.next()) {
+            Texture texture = loadTexture(assetLoader.getAssetHandle(file.name() + ".png"));
+            final String[] pathSegments = file.name().split("/");
+            final String name = pathSegments[pathSegments.length - 1];
+            if (file.size == 0) {
+                textureRegions.put(name, flippedRegion(texture, 0, 0, texture.getWidth(), texture.getHeight()));
+            } else {
+                for (JsonValue key = file.child(); key != null; key = key.next()) {
+                    int x = getIntFromJson(key, "x", 0);
+                    int y = getIntFromJson(key, "y", 0);
+                    int w = getIntFromJson(key, "w", texture.getWidth());
+                    int h = getIntFromJson(key, "h", texture.getHeight());
+                    textureRegions.put(key.name(), flippedRegion(texture, x, y, w, h));
+                }
+            }
+        }
+    }
+
+    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, "pp/textures/items", itemTextures);
+    }
+
+    public static Texture resolveBlockTexture(AssetLoader assetLoader, String textureName) {
+        return resolveTexture(assetLoader, textureName, "pp/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("pp/textures/items");
+        loadAllPngsFromDirInto(itemsDir, itemTextures);
+    }
+
+    private static void loadBlocks(AssetLoader assetLoader) {
+        final FileHandle blocksDir = assetLoader.getAssetHandle("pp/textures/blocks");
+        loadAllPngsFromDirInto(blocksDir, blockTextures);
+    }
+
+    private static void loadJoystick(AssetLoader assetLoader) {
+        joyStick = new Sprite(loadTexture(assetLoader.getAssetHandle("joy_stick.png")));
+        joyBackground = new Sprite(loadTexture(assetLoader.getAssetHandle("joy_background.png")));
+    }
+
+    private static void loadFurnace(AssetLoader assetLoader) {
+        furnaceBurn = new Sprite(textureRegions.get("furnace_burn"));
+        furnaceProgress = new Sprite(textureRegions.get("furnace_progress"));
+    }
+
+    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);
+        loadJoystick(assetLoader);
+        loadFurnace(assetLoader);
+        setPlayerHeadOrigin();
+        minecraftFont = new BitmapFont(assetLoader.getAssetHandle("font.fnt"), true);
+        minecraftFont.getData().setScale(.375f);
+        minecraftFont.setUseIntegerPositions(false);
+    }
+
+    /**
+     * @param s string whose width you want to know
+     * @return A width of string written in {@link #minecraftFont} in pixels
+     */
+    public static int getStringWidth(String s) {
+        glyphLayout.setText(minecraftFont, s);
+        return (int) glyphLayout.width;
+    }
+
+    /**
+     * @param s string whose height you want to know
+     * @return A height of string written in {@link #minecraftFont} in pixels
+     */
+    public static int getStringHeight(String s) {
+        glyphLayout.setText(minecraftFont, s);
+        return (int) glyphLayout.height;
+    }
+
+    public static int getIntFromJson(JsonValue json, String name, int defaultValue) {
+        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;
+    }
+
+    public static boolean getBooleanFromJson(JsonValue json, String name, boolean defaultValue) {
+        return json.has(name) ? json.getBoolean(name) : defaultValue;
+    }
+
+}
similarity index 83%
rename from core/src/ru/deadsoftware/cavedroid/misc/Renderer.java
rename to core/src/main/java/ru/deadsoftware/cavedroid/misc/Renderer.java
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.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;
 
 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());
 
     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);
         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() {
     }
 
     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);
 
     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) {
     }
 
     public void setFontScale(float scale) {
@@ -101,8 +112,12 @@ public abstract class Renderer implements InputProcessor {
     }
 
     @Override
     }
 
     @Override
-    public boolean scrolled(int amount) {
+    public boolean scrolled(float amountX, float amountY) {
         return false;
     }
 
         return false;
     }
 
+    @Override
+    public boolean touchCancelled(int i, int i1, int i2, int i3) {
+        return false;
+    }
 }
 }
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/CaveGame.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/CaveGame.kt
new file mode 100644 (file)
index 0000000..225dd3a
--- /dev/null
@@ -0,0 +1,89 @@
+package ru.deadsoftware.cavedroid
+
+import com.badlogic.gdx.Application
+import com.badlogic.gdx.Game
+import com.badlogic.gdx.Gdx
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader
+import ru.deadsoftware.cavedroid.misc.utils.ratio
+import ru.deadsoftware.cavedroid.prefs.PreferencesStore
+
+class CaveGame(
+    private val gameDataDirectoryPath: String,
+    private val isTouchScreen: Boolean,
+    private val isDebug: Boolean,
+    private val preferencesStore: PreferencesStore,
+) : Game() {
+
+    private val mainComponent: MainComponent
+    private val mainConfig: MainConfig
+
+    private val assetLoader: AssetLoader
+
+    init {
+        mainComponent = DaggerMainComponent.builder()
+            .caveGame(this)
+            .preferencesStore(preferencesStore)
+            .build()
+
+        mainConfig = mainComponent.mainConfig
+        assetLoader = mainComponent.assetLoader
+    }
+
+    private fun initMainConfig() {
+        val width = DEFAULT_VIEWPORT_WIDTH
+        val height = width / Gdx.graphics.ratio
+
+        mainConfig.mainComponent = mainComponent
+        mainConfig.gameFolder = gameDataDirectoryPath
+        mainConfig.isTouch = isTouchScreen
+        mainConfig.width = width
+        mainConfig.height = height
+        mainConfig.isShowInfo = isDebug
+
+        Gdx.app.logLevel = if (isDebug) Application.LOG_DEBUG else Application.LOG_ERROR
+
+        mainConfig.setFullscreenToggleListener { isFullscreen ->
+            if (isFullscreen) {
+                Gdx.graphics.setFullscreenMode(Gdx.graphics.displayMode);
+            } else {
+                Gdx.graphics.setWindowedMode(width.toInt(), height.toInt());
+            }
+        }
+    }
+
+    fun newGame(gameMode: Int) {
+        setScreen(mainComponent.gameScreen.apply { newGame(gameMode) })
+    }
+
+    fun loadGame() {
+        setScreen(mainComponent.gameScreen.apply { loadGame() })
+    }
+
+    fun quitGame() {
+        screen?.dispose()
+        setScreen(mainComponent.menuScreen)
+    }
+
+    override fun create() {
+        Gdx.files.absolute(gameDataDirectoryPath).mkdirs()
+        initMainConfig()
+
+        Assets.load(assetLoader)
+        setScreen(mainComponent.menuScreen)
+    }
+
+    override fun dispose() {
+        screen?.dispose()
+        Assets.dispose()
+    }
+
+
+    companion object {
+        private const val TAG = "CaveGame"
+        private const val DEFAULT_VIEWPORT_WIDTH = 480f
+
+        const val VERSION = "alpha 0.9.2"
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/MainComponent.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/MainComponent.kt
new file mode 100644 (file)
index 0000000..b2a77ee
--- /dev/null
@@ -0,0 +1,22 @@
+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 ru.deadsoftware.cavedroid.prefs.PreferencesStore
+import javax.inject.Singleton
+
+@Singleton
+@Component(dependencies = [CaveGame::class, PreferencesStore::class])
+interface MainComponent {
+
+    val gameScreen: GameScreen
+
+    val menuScreen: MenuScreen
+
+    val mainConfig: MainConfig
+
+    val assetLoader: AssetLoader
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt
new file mode 100644 (file)
index 0000000..050ae6f
--- /dev/null
@@ -0,0 +1,179 @@
+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(::Regex),
+                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? {
+        val startIndex = input.indexOfFirst { !it.isNone() }.takeIf { it >= 0 } ?: return null
+
+        return  try {
+            craftingRecipes.first { rec ->
+                for (i in rec.input.indices) {
+                    if (startIndex + i >= input.size) {
+                        return@first rec.input.subList(i, rec.input.size).all { it.matches("none") }
+                    }
+                    if (!input[startIndex + i].params.key.matches(rec.input[i])) {
+                        return@first false
+                    }
+                }
+                return@first true
+            }.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
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/CommonBlockActionUtils.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/actions/placeblock/IPlaceBlockAction.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToBackgroundAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToBackgroundAction.kt
new file mode 100644 (file)
index 0000000..7258301
--- /dev/null
@@ -0,0 +1,29 @@
+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 ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindPlaceBlockAction
+import javax.inject.Inject
+
+@GameScope
+@BindPlaceBlockAction(stringKey = PlaceBlockItemToBackgroundAction.ACTION_KEY)
+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/main/kotlin/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToForegroundAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToForegroundAction.kt
new file mode 100644 (file)
index 0000000..ec249fb
--- /dev/null
@@ -0,0 +1,34 @@
+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 ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindPlaceBlockAction
+import javax.inject.Inject
+
+@GameScope
+@BindPlaceBlockAction(stringKey = PlaceBlockItemToForegroundAction.ACTION_KEY)
+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/main/kotlin/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceSlabAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceSlabAction.kt
new file mode 100644 (file)
index 0000000..00c62d7
--- /dev/null
@@ -0,0 +1,44 @@
+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 ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindPlaceBlockAction
+import javax.inject.Inject
+
+@GameScope
+@BindPlaceBlockAction(stringKey = PlaceSlabAction.ACTION_KEY)
+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/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/IUpdateBlockAction.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedLeftAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedLeftAction.kt
new file mode 100644 (file)
index 0000000..c23b80f
--- /dev/null
@@ -0,0 +1,26 @@
+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 ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUpdateBlockAction
+import javax.inject.Inject
+
+@GameScope
+@BindUpdateBlockAction(stringKey = UpdateBedLeftAction.BLOCK_KEY)
+class UpdateBedLeftAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val gameItemsHolder: GameItemsHolder,
+) : IUpdateBlockAction {
+
+    override fun update(x: Int, y: Int) {
+        val bedRight = gameItemsHolder.getBlock("bed_r")
+        if (gameWorld.getForeMap(x + 1, y) != bedRight) {
+            gameWorld.resetForeMap(x, y)
+        }
+    }
+
+    companion object {
+        const val BLOCK_KEY = "bed_l"
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedRightAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedRightAction.kt
new file mode 100644 (file)
index 0000000..cd8b2d3
--- /dev/null
@@ -0,0 +1,26 @@
+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 ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUpdateBlockAction
+import javax.inject.Inject
+
+@GameScope
+@BindUpdateBlockAction(stringKey = UpdateBedRightAction.BLOCK_KEY)
+class UpdateBedRightAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val gameItemsHolder: GameItemsHolder,
+) : IUpdateBlockAction {
+
+    override fun update(x: Int, y: Int) {
+        val bedLeft = gameItemsHolder.getBlock("bed_l")
+        if (gameWorld.getForeMap(x - 1, y) != bedLeft) {
+            gameWorld.resetForeMap(x, y)
+        }
+    }
+
+    companion object {
+        const val BLOCK_KEY = "bed_r"
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGrassAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGrassAction.kt
new file mode 100644 (file)
index 0000000..476235a
--- /dev/null
@@ -0,0 +1,30 @@
+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 ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUpdateBlockAction
+import javax.inject.Inject
+
+@GameScope
+@BindUpdateBlockAction(stringKey = UpdateGrassAction.BLOCK_KEY)
+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)
+
+        val makesDirt = blockOnTop.params.hasCollision || blockOnTop.isFluid()
+
+        when {
+            makesDirt -> gameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("dirt"))
+            blockOnTop.params.key == "snow" -> gameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("grass_snowed"))
+        }
+    }
+
+    companion object {
+        const val BLOCK_KEY = "grass"
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateRequiresBlockAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateRequiresBlockAction.kt
new file mode 100644 (file)
index 0000000..53f56e3
--- /dev/null
@@ -0,0 +1,35 @@
+package ru.deadsoftware.cavedroid.game.actions.updateblock
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.FallingBlock
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUpdateBlockAction
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+@BindUpdateBlockAction(stringKey = UpdateRequiresBlockAction.ACTION_KEY)
+class UpdateRequiresBlockAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+) : IUpdateBlockAction {
+
+    override fun update(x: Int, y: Int) {
+        if (gameWorld.getForeMap(x, y + 1).params.hasCollision.not()) {
+            val block = gameWorld.getForeMap(x, y)
+
+            if (block.params.isFallable) {
+                gameWorld.resetForeMap(x, y)
+                FallingBlock(block.params.key, x.px, y.px)
+                    .attachToController(mobsController)
+            } else {
+                gameWorld.destroyForeMap(x, y)
+            }
+        }
+    }
+
+    companion object {
+        const val ACTION_KEY = "requires_block"
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSnowedGrassAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSnowedGrassAction.kt
new file mode 100644 (file)
index 0000000..2cf5a57
--- /dev/null
@@ -0,0 +1,29 @@
+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 ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUpdateBlockAction
+import javax.inject.Inject
+
+@GameScope
+@BindUpdateBlockAction(stringKey = UpdateSnowedGrassAction.BLOCK_KEY)
+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)
+        val makesDirt = blockOnTop.params.hasCollision || blockOnTop.isFluid()
+
+        when {
+            makesDirt -> gameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("dirt"))
+            blockOnTop.params.key != "snow" -> gameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("grass"))
+        }
+    }
+
+    companion object {
+        const val BLOCK_KEY = "grass_snowed"
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/IUseBlockAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/IUseBlockAction.kt
new file mode 100644 (file)
index 0000000..ddc70c5
--- /dev/null
@@ -0,0 +1,9 @@
+package ru.deadsoftware.cavedroid.game.actions.useblock
+
+import ru.deadsoftware.cavedroid.game.model.block.Block
+
+interface IUseBlockAction {
+
+    fun perform(block: Block, x: Int, y: Int)
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/UseChestAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/UseChestAction.kt
new file mode 100644 (file)
index 0000000..93684e7
--- /dev/null
@@ -0,0 +1,28 @@
+package ru.deadsoftware.cavedroid.game.actions.useblock
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.objects.container.Chest
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUseBlockAction
+import javax.inject.Inject
+
+@GameScope
+@BindUseBlockAction(stringKey = UseChestAction.KEY)
+class UseChestAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val gameWindowsManager: GameWindowsManager,
+) : IUseBlockAction {
+
+    override fun perform(block: Block, x: Int, y: Int) {
+        val chest = (gameWorld.getForegroundContainer(x, y) as? Chest)
+            ?: (gameWorld.getBackgroundContainer(x, y) as? Chest)
+            ?: return
+        gameWindowsManager.openChest(chest)
+    }
+
+    companion object {
+        const val KEY = "chest"
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/UseCraftingTableAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/UseCraftingTableAction.kt
new file mode 100644 (file)
index 0000000..6d6c1e9
--- /dev/null
@@ -0,0 +1,22 @@
+package ru.deadsoftware.cavedroid.game.actions.useblock
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUseBlockAction
+import javax.inject.Inject
+
+@GameScope
+@BindUseBlockAction(stringKey = UseCraftingTableAction.KEY)
+class UseCraftingTableAction @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager
+) : IUseBlockAction {
+
+    override fun perform(block: Block, x: Int, y: Int) {
+        gameWindowsManager.openCrafting()
+    }
+
+    companion object {
+        const val KEY = "crafting_table"
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/UseFurnaceAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useblock/UseFurnaceAction.kt
new file mode 100644 (file)
index 0000000..0b8ba53
--- /dev/null
@@ -0,0 +1,25 @@
+package ru.deadsoftware.cavedroid.game.actions.useblock
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUseBlockAction
+import javax.inject.Inject
+
+@GameScope
+@BindUseBlockAction(stringKey = UseFurnaceAction.KEY)
+class UseFurnaceAction @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val gameWindowsManager: GameWindowsManager,
+) : IUseBlockAction {
+
+    override fun perform(block: Block, x: Int, y: Int) {
+        val furnace = gameWorld.getForegroundFurnace(x, y) ?: gameWorld.getBackgroundFurnace(x, y) ?: return
+        gameWindowsManager.openFurnace(furnace)
+    }
+
+    companion object {
+        const val KEY = "furnace"
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/IUseItemAction.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseBedAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseBedAction.kt
new file mode 100644 (file)
index 0000000..f48115e
--- /dev/null
@@ -0,0 +1,33 @@
+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 ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUseItemAction
+import javax.inject.Inject
+
+@GameScope
+@BindUseItemAction(UseBedAction.ACTION_KEY)
+class UseBedAction @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 bedLeft = gameItemsHolder.getBlock("bed_l")
+        val bedRight = gameItemsHolder.getBlock("bed_r")
+
+        if (gameWorld.canPlaceToForeground(x, y, bedLeft) && gameWorld.canPlaceToForeground(x + 1, y, bedRight)) {
+            gameWorld.placeToForeground(x, y, bedLeft)
+            gameWorld.placeToForeground(x + 1, y, bedRight)
+            mobsController.player.inventory.decreaseCurrentItemAmount()
+        }
+    }
+
+    companion object {
+        const val ACTION_KEY = "use_bed_action"
+    }
+}
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseEmptyBucketAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseEmptyBucketAction.kt
new file mode 100644 (file)
index 0000000..6992af7
--- /dev/null
@@ -0,0 +1,40 @@
+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 ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUseItemAction
+import javax.inject.Inject
+
+@GameScope
+@BindUseItemAction(UseEmptyBucketAction.ACTION_KEY)
+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)
+
+        @Suppress("REDUNDANT_ELSE_IN_WHEN")
+        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/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseLavaBucketAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseLavaBucketAction.kt
new file mode 100644 (file)
index 0000000..ec1e585
--- /dev/null
@@ -0,0 +1,30 @@
+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 ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUseItemAction
+import javax.inject.Inject
+
+@GameScope
+@BindUseItemAction(UseLavaBucketAction.ACTION_KEY)
+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/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UsePigSpawnEggAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UsePigSpawnEggAction.kt
new file mode 100644 (file)
index 0000000..5eddc5e
--- /dev/null
@@ -0,0 +1,31 @@
+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.mobs.Pig
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUseItemAction
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+@BindUseItemAction(UsePigSpawnEggAction.ACTION_KEY)
+class UsePigSpawnEggAction @Inject constructor(
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : IUseItemAction {
+
+    override fun perform(item: Item.Usable, x: Int, y: Int) {
+        Pig(mobsController.player.cursorX.px, mobsController.player.cursorY.px)
+            .apply {
+                attachToController(mobsController)
+            }
+
+        mobsController.player.decreaseCurrentItemCount(gameItemsHolder)
+    }
+
+    companion object {
+        const val ACTION_KEY = "use_spawn_egg_pig"
+    }
+}
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseWaterBucketAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/actions/useitem/UseWaterBucketAction.kt
new file mode 100644 (file)
index 0000000..739ec28
--- /dev/null
@@ -0,0 +1,30 @@
+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 ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUseItemAction
+import javax.inject.Inject
+
+@GameScope
+@BindUseItemAction(UseWaterBucketAction.ACTION_KEY)
+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/main/kotlin/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt
new file mode 100644 (file)
index 0000000..51dc335
--- /dev/null
@@ -0,0 +1,39 @@
+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.container.ContainerController
+import ru.deadsoftware.cavedroid.game.objects.drop.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 containerController: ContainerController,
+    private val gameWorld: GameWorld,
+) {
+
+    fun getDebugStrings(): List<String> {
+        val player = mobsController.player
+
+        return listOf(
+            "FPS: ${Gdx.graphics.framesPerSecond}",
+            "X: ${player.mapX}",
+            "Y: ${player.upperMapY} (${gameWorld.height - player.upperMapY})",
+            "CurX: ${player.cursorX}",
+            "CurY: ${player.cursorY}",
+            "Velocity: ${player.velocity}",
+            "Swim: ${player.swim}",
+            "Mobs: ${mobsController.mobs.size}",
+            "Drops: ${dropController.size}",
+            "Containers: ${containerController.size}",
+            "Block: ${gameWorld.getForeMap(player.cursorX, player.cursorY).params.key}",
+            "Hand: ${player.inventory.activeItem.item.params.key}",
+            "Game mode: ${player.gameMode}",
+            "Block damage: ${player.blockDamage}"
+        )
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/IGameInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/IGameInputHandler.kt
new file mode 100644 (file)
index 0000000..2c5f118
--- /dev/null
@@ -0,0 +1,25 @@
+package ru.deadsoftware.cavedroid.game.input
+
+import ru.deadsoftware.cavedroid.game.input.action.IGameInputAction
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+
+interface IKeyboardInputHandler : IGameInputHandler<KeyboardInputAction>
+
+interface IMouseInputHandler : IGameInputHandler<MouseInputAction>
+
+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/main/kotlin/ru/deadsoftware/cavedroid/game/input/InputUtils.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/input/Joystick.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/Joystick.kt
new file mode 100644 (file)
index 0000000..6cbc052
--- /dev/null
@@ -0,0 +1,73 @@
+package ru.deadsoftware.cavedroid.game.input
+
+import com.badlogic.gdx.math.Vector2
+import com.badlogic.gdx.utils.TimeUtils
+
+class Joystick(
+    private val value: Float,
+) {
+
+    var active = false
+        private set
+    var centerX = 0f
+        private set
+    var centerY = 0f
+        private set
+
+    var activeX = 0f
+        private set
+    var activeY = 0f
+        private set
+
+    var pointer = 0
+        private set
+
+    private val stickVector = Vector2()
+
+    private var activateTimeMs = 0L
+
+    fun activate(touchX: Float, touchY: Float, pointer: Int) {
+        active = true
+        centerX = touchX
+        centerY = touchY
+        activateTimeMs = TimeUtils.millis()
+        this.pointer = pointer
+    }
+
+    fun deactivate() {
+        active = false
+    }
+
+    fun getVelocityVector(): Vector2 {
+        if (!active) {
+            return Vector2.Zero
+        }
+        return Vector2(
+            stickVector.x * value,
+            stickVector.y * value
+        )
+    }
+
+    fun updateState(touchX: Float, touchY: Float) {
+        if (!active) {
+            return
+        }
+
+        stickVector.x = touchX - centerX
+        stickVector.y = touchY - centerY
+        stickVector.clamp(0f, RADIUS)
+
+        activeX = centerX + stickVector.x
+        activeY = centerY + stickVector.y
+
+        stickVector.x /= RADIUS
+        stickVector.y /= RADIUS
+    }
+
+    companion object {
+        const val RADIUS = 48f
+        const val SIZE = RADIUS * 2
+        const val STICK_SIZE = 32f
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/IGameInputAction.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/KeyboardInputAction.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/MouseInputAction.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/keys/KeyboardInputActionKey.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/keys/KeyboardInputActionKey.kt
new file mode 100644 (file)
index 0000000..fd14fc4
--- /dev/null
@@ -0,0 +1,28 @@
+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 Up : KeyboardInputActionKey
+
+    data object Crouch : KeyboardInputActionKey
+
+    data object DropItem : 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 class SelectHotbarSlot(
+        val slot: Int
+    ) : KeyboardInputActionKey
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/keys/MouseInputActionKey.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/action/keys/MouseInputActionKey.kt
new file mode 100644 (file)
index 0000000..3b2744f
--- /dev/null
@@ -0,0 +1,48 @@
+package ru.deadsoftware.cavedroid.game.input.action.keys
+
+sealed interface MouseInputActionKey {
+
+    val touchUp: Boolean
+
+    sealed interface Touch : MouseInputActionKey {
+        val pointer: Int
+    }
+
+    data object None : MouseInputActionKey {
+        override val touchUp: Boolean
+            get() = throw IllegalAccessException("not applicable for mouse move action")
+    }
+
+    data class Dragged(
+        override val pointer: Int
+    ) : Touch {
+        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 Screen(
+        override val touchUp: Boolean,
+        override val pointer: Int,
+    ) : Touch
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/CloseGameWindowKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/CloseGameWindowKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..8bf9b1c
--- /dev/null
@@ -0,0 +1,41 @@
+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.IKeyboardInputHandler
+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.drop.DropController
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class CloseGameWindowKeyboardInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val dropController: DropController,
+) : IKeyboardInputHandler {
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/DropItemKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/DropItemKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..e9c8eb0
--- /dev/null
@@ -0,0 +1,51 @@
+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.IKeyboardInputHandler
+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.model.item.Item
+import ru.deadsoftware.cavedroid.game.objects.drop.Drop
+import ru.deadsoftware.cavedroid.game.objects.drop.DropController
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class DropItemKeyboardInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val dropController: DropController,
+) : IKeyboardInputHandler {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.DropItem &&
+                action.isKeyDown && gameWindowsManager.getCurrentWindow() == GameUiWindow.NONE &&
+                !mobsController.player.inventory.activeItem.item.isNone()
+    }
+
+    private fun createDrop(item: Item, playerX: Float, playerY: Float, amount: Int) {
+        dropController.addDrop(
+            /* x = */ playerX + ((DROP_DISTANCE - Drop.DROP_SIZE / 2) * mobsController.player.direction.basis),
+            /* y = */ playerY,
+            /* item = */ item,
+            /* count = */ amount
+        )
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        val player = mobsController.player
+        val currentItem = player.inventory.activeItem
+        val dropAmount =  if (currentItem.item.isTool()) currentItem.amount else 1
+
+        createDrop(currentItem.item, player.x, player.y, dropAmount)
+        player.inventory.decreaseCurrentItemAmount(dropAmount)
+    }
+
+    companion object {
+        const val DROP_DISTANCE = 20f
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyDownKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyDownKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..ae5f4c3
--- /dev/null
@@ -0,0 +1,34 @@
+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.IKeyboardInputHandler
+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.Player
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class FlyDownKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IKeyboardInputHandler {
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyUpKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyUpKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..444fe09
--- /dev/null
@@ -0,0 +1,35 @@
+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.IKeyboardInputHandler
+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.Player
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class FlyUpKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IKeyboardInputHandler {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.Up &&
+                !mobsController.player.swim &&
+                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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoLeftKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoLeftKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..2b40d46
--- /dev/null
@@ -0,0 +1,35 @@
+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.IKeyboardInputHandler
+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.Player
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class GoLeftKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IKeyboardInputHandler {
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoRightKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoRightKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..ec38218
--- /dev/null
@@ -0,0 +1,34 @@
+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.IKeyboardInputHandler
+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.Player
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class GoRightKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController
+) : IKeyboardInputHandler {
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/JumpKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/JumpKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..bcacbf5
--- /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.IKeyboardInputHandler
+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.Player
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class JumpKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IKeyboardInputHandler {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.Up &&
+                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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/MoveCursorControlsModeKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/MoveCursorControlsModeKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..84c5b90
--- /dev/null
@@ -0,0 +1,48 @@
+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.IKeyboardInputHandler
+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.Player
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class MoveCursorControlsModeKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+) : IKeyboardInputHandler {
+
+    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.Up ||
+                        action.actionKey is KeyboardInputActionKey.Down)
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        val player = mobsController.player
+
+        when (action.actionKey) {
+            KeyboardInputActionKey.Left -> player.cursorX--
+            KeyboardInputActionKey.Right -> player.cursorX++
+            KeyboardInputActionKey.Up -> player.cursorY--
+            KeyboardInputActionKey.Down -> player.cursorY++
+            else -> return
+        }
+
+        player.checkCursorBounds(gameWorld);
+    }
+
+    companion object {
+        private const val SURVIVAL_CURSOR_RANGE = 4
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenInventoryKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenInventoryKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..70ae158
--- /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.IKeyboardInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class OpenInventoryKeyboardInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+) : IKeyboardInputHandler {
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/PauseGameKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/PauseGameKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..0311c88
--- /dev/null
@@ -0,0 +1,42 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.IKeyboardInputHandler
+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.drop.DropController
+import ru.deadsoftware.cavedroid.game.objects.container.ContainerController
+import ru.deadsoftware.cavedroid.game.save.GameSaveLoader
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class PauseGameKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val dropController: DropController,
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+    private val containerController: ContainerController,
+    private val gameWindowsManager: GameWindowsManager,
+) : IKeyboardInputHandler {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.Pause && action.isKeyDown
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        if (gameWindowsManager.getCurrentWindow() != GameUiWindow.NONE) {
+            gameWindowsManager.closeWindow()
+            return
+        }
+
+        GameSaveLoader.save(mainConfig, dropController, mobsController, containerController, gameWorld)
+        mainConfig.caveGame.quitGame()
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SelectHotbarSlotKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SelectHotbarSlotKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..d22cd94
--- /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.input.IKeyboardInputHandler
+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.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class SelectHotbarSlotKeyboardInputHandler @Inject constructor(
+    private val mobsController: MobsController,
+) : IKeyboardInputHandler {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.SelectHotbarSlot &&
+                action.isKeyDown
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        mobsController.player.inventory.activeSlot = (action.actionKey as KeyboardInputActionKey.SelectHotbarSlot).slot
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/StopSwimKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/StopSwimKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..d8c5acc
--- /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.IKeyboardInputHandler
+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.Player
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class StopSwimKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+) : IKeyboardInputHandler {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.Up && !action.isKeyDown &&
+                mobsController.player.swim &&
+                (mobsController.player.controlMode == Player.ControlMode.WALK || !mainConfig.isTouch)
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        mobsController.player.swim = false
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SwimUpKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SwimUpKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..20cc82e
--- /dev/null
@@ -0,0 +1,38 @@
+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.IKeyboardInputHandler
+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.Player
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class SwimUpKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+) : IKeyboardInputHandler {
+
+    private fun checkSwim(): Boolean {
+        return gameWorld.getForeMap(mobsController.player.mapX, mobsController.player.lowerMapY).isFluid()
+    }
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return action.actionKey is KeyboardInputActionKey.Up && action.isKeyDown &&
+                !mobsController.player.swim &&
+                !mobsController.player.canJump() &&
+                checkSwim() && !mobsController.player.isFlyMode &&
+                (mobsController.player.controlMode == Player.ControlMode.WALK || !mainConfig.isTouch)
+    }
+
+    override fun handle(action: KeyboardInputAction) {
+        mobsController.player.swim = true
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleControlsModeKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleControlsModeKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..f8e0177
--- /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.IKeyboardInputHandler
+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.Player
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class ToggleControlsModeKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IKeyboardInputHandler {
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleDebugInfoKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleDebugInfoKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..7cf08ed
--- /dev/null
@@ -0,0 +1,24 @@
+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.IKeyboardInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class ToggleDebugInfoKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig
+) : IKeyboardInputHandler {
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleGameModeKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleGameModeKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..64a1116
--- /dev/null
@@ -0,0 +1,31 @@
+package ru.deadsoftware.cavedroid.game.input.handler.keyboard
+
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.input.IKeyboardInputHandler
+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.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class ToggleGameModeKeyboardInputHandler @Inject constructor(
+    private val mobsController: MobsController
+) : IKeyboardInputHandler {
+
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleMinimapKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleMinimapKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..2cd6638
--- /dev/null
@@ -0,0 +1,24 @@
+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.IKeyboardInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class ToggleMinimapKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+) : IKeyboardInputHandler {
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/TurnOnFlyModeKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/keyboard/TurnOnFlyModeKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..43a7122
--- /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.IKeyboardInputHandler
+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.Player
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindKeyboardInputHandler
+import javax.inject.Inject
+
+@GameScope
+@BindKeyboardInputHandler
+class TurnOnFlyModeKeyboardInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+) : IKeyboardInputHandler {
+
+    override fun checkConditions(action: KeyboardInputAction): Boolean {
+        return mobsController.player.gameMode == 1 && action.actionKey is KeyboardInputActionKey.Up &&
+                !mobsController.player.swim &&
+                !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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/AbstractInventoryItemsMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/AbstractInventoryItemsMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..3b925b0
--- /dev/null
@@ -0,0 +1,103 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import com.badlogic.gdx.graphics.g2d.TextureRegion
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.IMouseInputHandler
+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.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem.Companion.isNoneOrNull
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.ui.windows.inventory.AbstractInventoryWindow
+import ru.deadsoftware.cavedroid.game.ui.windows.inventory.AbstractInventoryWindowWithCraftGrid
+
+abstract class AbstractInventoryItemsMouseInputHandler(
+    private val gameItemsHolder: GameItemsHolder,
+    private val gameWindowsManager: GameWindowsManager,
+    private val windowType: GameUiWindow,
+) : IMouseInputHandler {
+
+    protected abstract val windowTexture: TextureRegion
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return gameWindowsManager.getCurrentWindow() == windowType &&
+                isInsideWindow(action, windowTexture) &&
+                (action.actionKey is MouseInputActionKey.Left ||
+                        action.actionKey is MouseInputActionKey.Right ||
+                        action.actionKey is MouseInputActionKey.Screen)
+                && (action.actionKey.touchUp || action.actionKey is MouseInputActionKey.Screen)
+    }
+
+    protected fun updateCraftResult(window: AbstractInventoryWindowWithCraftGrid) {
+        window.craftResult = gameItemsHolder.craftItem(window.craftingItems.map(InventoryItem::item))
+            ?: gameItemsHolder.fallbackItem.toInventoryItem()
+    }
+
+    private fun reduceCraftItems(window: AbstractInventoryWindowWithCraftGrid) {
+        for (i in window.craftingItems.indices) {
+            if (window.craftingItems[i].amount > 1) {
+                window.craftingItems[i].amount--
+            } else {
+                window.craftingItems[i] = gameItemsHolder.fallbackItem.toInventoryItem()
+            }
+        }
+    }
+
+    protected fun handleInsidePlaceableCell(
+        action: MouseInputAction,
+        items: MutableList<InventoryItem>,
+        window: AbstractInventoryWindow,
+        index: Int
+    ) {
+        if (action.actionKey is MouseInputActionKey.Screen) {
+            if (!action.actionKey.touchUp) {
+                window.onLeftCLick(items, gameItemsHolder, index, action.actionKey.pointer)
+            } else {
+                if (action.actionKey.pointer == window.selectItemPointer) {
+                    window.onLeftCLick(items, gameItemsHolder, index, action.actionKey.pointer)
+                } else {
+                    window.onRightClick(items, gameItemsHolder, index)
+                }
+            }
+        } else if (action.actionKey is MouseInputActionKey.Left) {
+            window.onLeftCLick(items, gameItemsHolder, index)
+        } else {
+            window.onRightClick(items, gameItemsHolder, index)
+        }
+    }
+
+    protected fun handleInsideCraftResultCell(
+        action: MouseInputAction,
+        items: MutableList<InventoryItem>,
+        window: AbstractInventoryWindow,
+        index: Int
+    ) {
+        val selectedItem = window.selectedItem
+
+        if (!selectedItem.isNoneOrNull() && (selectedItem.item != items[index].item ||
+                    !selectedItem.canBeAdded(items[index].amount))) {
+            return
+        }
+
+        if (!selectedItem.isNoneOrNull()) {
+            selectedItem.amount += items[index].amount
+            items[index] = gameItemsHolder.fallbackItem.toInventoryItem()
+        } else {
+            if (action.actionKey is MouseInputActionKey.Screen) {
+                if (!action.actionKey.touchUp) {
+                    window.onLeftCLick(items, gameItemsHolder, index, action.actionKey.pointer)
+                }
+            } else if (action.actionKey is MouseInputActionKey.Left) {
+                window.onLeftCLick(items, gameItemsHolder, index)
+            }
+        }
+
+        if (window is AbstractInventoryWindowWithCraftGrid) {
+            reduceCraftItems(window)
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/AttackMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/AttackMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..52333eb
--- /dev/null
@@ -0,0 +1,37 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.IMouseInputHandler
+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.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+@BindMouseInputHandler
+class AttackMouseInputHandler @Inject constructor(
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+    private val gameWindowsManager: GameWindowsManager
+) : IMouseInputHandler {
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/CloseGameWindowMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/CloseGameWindowMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..4fb0879
--- /dev/null
@@ -0,0 +1,63 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+import com.badlogic.gdx.graphics.g2d.TextureRegion
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.input.IMouseInputHandler
+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.drop.DropController
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+@BindMouseInputHandler
+class CloseGameWindowMouseInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val dropController: DropController,
+) : IMouseInputHandler {
+
+    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"])
+    private val furnaceInventoryTexture get() = requireNotNull(Assets.textureRegions["furnace"])
+    private val chestInventoryTexture get() = requireNotNull(Assets.textureRegions["chest"])
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return gameWindowsManager.getCurrentWindow() != GameUiWindow.NONE &&
+                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Screen) &&
+                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
+            GameUiWindow.FURNACE -> furnaceInventoryTexture
+            GameUiWindow.CHEST -> chestInventoryTexture
+            else -> throw UnsupportedOperationException("Cant close window ${window.name}")
+        }
+    }
+
+    override fun handle(action: MouseInputAction) {
+        val selectedItem = gameWindowsManager.currentWindow?.selectedItem
+        if (selectedItem != null) {
+                dropController.addDrop(
+                    /* x = */ mobsController.player.x + (32f * mobsController.player.direction.basis),
+                    /* y = */ mobsController.player.y,
+                    /* item = */ selectedItem.item,
+                    /* count = */ selectedItem.amount,
+                )
+            gameWindowsManager.currentWindow?.selectedItem = null
+        }
+        gameWindowsManager.closeWindow()
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/CreativeInventoryScrollMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/CreativeInventoryScrollMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..8acdb7e
--- /dev/null
@@ -0,0 +1,90 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+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.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.input.IMouseInputHandler
+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
+@BindMouseInputHandler
+class CreativeInventoryScrollMouseInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val gameWindowsManager: GameWindowsManager,
+    private val gameItemsHolder: GameItemsHolder,
+) : IMouseInputHandler {
+
+    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.Screen) &&
+                !action.actionKey.touchUp && !gameWindowsManager.isDragging
+    }
+
+    private fun checkEndDragConditions(action: MouseInputAction): Boolean {
+        return action.actionKey is MouseInputActionKey.Screen &&
+                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.Screen -> 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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/CursorMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/CursorMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..c1f2af5
--- /dev/null
@@ -0,0 +1,154 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+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.input.IMouseInputHandler
+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.Player
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.ui.TooltipManager
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+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.px
+import javax.inject.Inject
+
+@GameScope
+@BindMouseInputHandler
+class CursorMouseInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+    private val gameWindowsManager: GameWindowsManager,
+    private val gameItemsHolder: GameItemsHolder,
+    private val tooltipManager: TooltipManager,
+) : IMouseInputHandler {
+
+    private val player get() = mobsController.player
+
+    private val creativeInventoryTexture get() = requireNotNull(Assets.textureRegions["creative"])
+
+    private val Block.isAutoselectable
+        get() = !isNone() && params.hasCollision
+
+    private fun GameWorld.isCurrentBlockAutoselectable() =
+        getForeMap(player.cursorX, player.cursorY).isAutoselectable
+
+    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
+        player.headRotation = 0f
+
+        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 + player.width / 2)
+        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)
+
+        if (worldX < player.x + player.width / 2) {
+            player.setDir(Mob.Direction.LEFT)
+        } else {
+            player.setDir(Mob.Direction.RIGHT)
+        }
+    }
+
+    private fun getCreativeTooltip(action: MouseInputAction): String? {
+        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 null
+        }
+
+        val itemIndex = (gameWindowsManager.creativeScrollAmount * GameWindowsConfigs.Creative.itemsInRow +
+                (xOnGrid.toInt() + yOnGrid.toInt() * GameWindowsConfigs.Creative.itemsInRow))
+        val item = gameItemsHolder.getItemFromCreativeInventory(itemIndex)
+
+        return item.params.name
+    }
+
+    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)
+        }
+
+        player.checkCursorBounds(gameWorld)
+
+        if (player.controlMode == Player.ControlMode.WALK && mainConfig.isTouch) {
+            setPlayerDirectionToCursor()
+        }
+
+        if (player.cursorX != pastCursorX || player.cursorY != pastCursorY) {
+            player.blockDamage = 0f
+        }
+
+        if (gameWindowsManager.getCurrentWindow() == GameUiWindow.CREATIVE_INVENTORY) {
+            tooltipManager.showMouseTooltip(getCreativeTooltip(action).orEmpty())
+        }
+    }
+
+    companion object {
+        private const val SURVIVAL_CURSOR_RANGE = 4
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/HotbarMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/HotbarMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..6a66338
--- /dev/null
@@ -0,0 +1,128 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+import com.badlogic.gdx.utils.Timer
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.input.IMouseInputHandler
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import ru.deadsoftware.cavedroid.game.input.handler.keyboard.DropItemKeyboardInputHandler.Companion.DROP_DISTANCE
+import ru.deadsoftware.cavedroid.game.input.isInsideHotbar
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.player.Player
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.objects.drop.Drop
+import ru.deadsoftware.cavedroid.game.objects.drop.DropController
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+@BindMouseInputHandler
+class HotbarMouseInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val dropController: DropController,
+) : IMouseInputHandler {
+
+    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.Screen)
+                        && isInsideHotbar(action)
+                        || action.actionKey is MouseInputActionKey.Scroll) &&
+                gameWindowsManager.getCurrentWindow() == GameUiWindow.NONE
+    }
+
+    private fun cancelHold() {
+        buttonHoldTask?.cancel()
+        buttonHoldTask = null
+    }
+
+    private fun createDrop(item: Item, playerX: Float, playerY: Float, amount: Int) {
+        dropController.addDrop(
+            /* x = */ playerX + ((DROP_DISTANCE - Drop.DROP_SIZE / 2) * mobsController.player.direction.basis),
+            /* y = */ playerY,
+            /* item = */ item,
+            /* count = */ amount
+        )
+    }
+
+    private fun getActionSlot(action: MouseInputAction): Int {
+        return ((action.screenX -
+                (action.cameraViewport.width / 2 - hotbarTexture.regionWidth / 2))
+                / HOTBAR_CELL_WIDTH).toInt()
+    }
+
+    private fun handleHold(action: MouseInputAction) {
+//        buttonHoldTask = null
+//        gameWindowsManager.openInventory()
+        val player = mobsController.player
+        val actionSlot = getActionSlot(action)
+        val currentItem = player.inventory.items[actionSlot]
+        val dropAmount = if (currentItem.item.isTool()) currentItem.amount else 1
+
+        createDrop(currentItem.item, player.x, player.y, dropAmount)
+        player.inventory.decreaseItemAmount(actionSlot, dropAmount)
+    }
+
+    private fun handleDown(action: MouseInputAction) {
+        buttonHoldTask = object : Timer.Task() {
+            override fun run() {
+                handleHold(action)
+            }
+        }
+
+        Timer.schedule(buttonHoldTask, TOUCH_HOLD_TIME_SEC)
+    }
+
+    private fun handleUp(action: MouseInputAction) {
+        mobsController.player.inventory.activeSlot = getActionSlot(action)
+    }
+
+    private fun handleScroll(action: MouseInputAction) {
+        if (action.actionKey !is MouseInputActionKey.Scroll) {
+            return
+        }
+        mobsController.player.inventory.activeSlot += action.actionKey.amountY.toInt()
+        if (mobsController.player.inventory.activeSlot < 0) {
+            mobsController.player.inventory.activeSlot = Player.HOTBAR_SIZE - 1
+        } else if (mobsController.player.inventory.activeSlot >= Player.HOTBAR_SIZE) {
+            mobsController.player.inventory.activeSlot = 0
+        }
+    }
+
+    override fun handle(action: MouseInputAction) {
+        if (buttonHoldTask != null && buttonHoldTask?.isScheduled == true) {
+            cancelHold()
+        }
+
+        if (buttonHoldTask != null && buttonHoldTask?.isScheduled != true) {
+            buttonHoldTask = null
+            return
+        }
+
+        if (action.actionKey !is MouseInputActionKey.Left && action.actionKey !is MouseInputActionKey.Screen) {
+            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
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectChestInventoryItemMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectChestInventoryItemMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..62a4836
--- /dev/null
@@ -0,0 +1,74 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.ui.windows.inventory.ChestInventoryWindow
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+@BindMouseInputHandler
+class SelectChestInventoryItemMouseInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : AbstractInventoryItemsMouseInputHandler(gameItemsHolder, gameWindowsManager, GameUiWindow.CHEST) {
+
+    override val windowTexture get() = requireNotNull(Assets.textureRegions["chest"])
+
+    private fun handleInsideContentGrid(action: MouseInputAction, xOnGrid: Int, yOnGrid: Int) {
+        val window = gameWindowsManager.currentWindow as ChestInventoryWindow
+        val itemIndex = xOnGrid + yOnGrid * GameWindowsConfigs.Chest.contentsInRow
+
+        handleInsidePlaceableCell(action, window.chest.items, window, itemIndex)
+    }
+
+    private fun handleInsideInventoryGrid(action: MouseInputAction, xOnGrid: Int, yOnGrid: Int) {
+        val window = gameWindowsManager.currentWindow as ChestInventoryWindow
+
+        var itemIndex = xOnGrid + yOnGrid * GameWindowsConfigs.Chest.itemsInRow
+        itemIndex += GameWindowsConfigs.Chest.hotbarCells
+
+        if (itemIndex >= mobsController.player.inventory.size) {
+            itemIndex -= mobsController.player.inventory.size
+        }
+
+        handleInsidePlaceableCell(action, mobsController.player.inventory.items, window, itemIndex)
+    }
+
+    override fun handle(action: MouseInputAction) {
+        val texture = windowTexture
+
+        val xOnWindow = action.screenX - (action.cameraViewport.width / 2 - texture.regionWidth / 2)
+        val yOnWindow = action.screenY - (action.cameraViewport.height / 2 - texture.regionHeight / 2)
+
+        val xOnGrid = (xOnWindow - GameWindowsConfigs.Chest.itemsGridMarginLeft) /
+                GameWindowsConfigs.Chest.itemsGridColWidth
+        val yOnGrid = (yOnWindow - GameWindowsConfigs.Chest.itemsGridMarginTop) /
+                GameWindowsConfigs.Chest.itemsGridRowHeight
+
+        val xOnContent = (xOnWindow - GameWindowsConfigs.Chest.contentsMarginLeft) /
+                GameWindowsConfigs.Chest.itemsGridColWidth
+        val yOnContent = (yOnWindow - GameWindowsConfigs.Chest.contentsMarginTop) /
+                GameWindowsConfigs.Chest.itemsGridRowHeight
+
+        val isInsideInventoryGrid = xOnGrid >= 0 && xOnGrid < GameWindowsConfigs.Chest.itemsInRow &&
+                yOnGrid >= 0 && yOnGrid < GameWindowsConfigs.Chest.itemsInCol
+
+        val isInsideContentGrid = xOnContent >= 0 && xOnContent < GameWindowsConfigs.Chest.contentsInRow &&
+                yOnContent >= 0 && yOnContent < GameWindowsConfigs.Chest.contentsInCol
+
+
+        if (isInsideInventoryGrid) {
+            handleInsideInventoryGrid(action, xOnGrid.toInt(), yOnGrid.toInt())
+        } else if (isInsideContentGrid) {
+            handleInsideContentGrid(action, xOnContent.toInt(), yOnContent.toInt())
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCraftingInventoryItemMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCraftingInventoryItemMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..aefea54
--- /dev/null
@@ -0,0 +1,91 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.ui.windows.inventory.CraftingInventoryWindow
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+@BindMouseInputHandler
+class SelectCraftingInventoryItemMouseInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : AbstractInventoryItemsMouseInputHandler(gameItemsHolder, gameWindowsManager, GameUiWindow.CRAFTING_TABLE) {
+
+    override val windowTexture get() = requireNotNull(Assets.textureRegions["crafting_table"])
+
+    private fun handleInsideInventoryGrid(action: MouseInputAction, xOnGrid: Int, yOnGrid: Int) {
+        val window = gameWindowsManager.currentWindow as CraftingInventoryWindow
+
+        var itemIndex = xOnGrid + yOnGrid * GameWindowsConfigs.Crafting.itemsInRow
+        itemIndex += GameWindowsConfigs.Crafting.hotbarCells
+
+        if (itemIndex >= mobsController.player.inventory.size) {
+            itemIndex -= mobsController.player.inventory.size
+        }
+
+        handleInsidePlaceableCell(action, mobsController.player.inventory.items, window, itemIndex)
+    }
+
+    private fun handleInsideCraft(action: MouseInputAction, xOnCraft: Int, yOnCraft: Int) {
+        val window = gameWindowsManager.currentWindow as CraftingInventoryWindow
+        val index = xOnCraft + yOnCraft * GameWindowsConfigs.Crafting.craftGridSize
+
+        handleInsidePlaceableCell(action, window.craftingItems, window, index)
+
+        updateCraftResult(window)
+    }
+
+    private fun handleInsideCraftResult(action: MouseInputAction) {
+        val window = gameWindowsManager.currentWindow as CraftingInventoryWindow
+
+        handleInsideCraftResultCell(action, window.craftResultList, window, 0)
+
+        updateCraftResult(window)
+    }
+
+    override fun handle(action: MouseInputAction) {
+        val texture = windowTexture
+
+        val xOnWindow = action.screenX - (action.cameraViewport.width / 2 - texture.regionWidth / 2)
+        val yOnWindow = action.screenY - (action.cameraViewport.height / 2 - texture.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) {
+            handleInsideCraftResult(action)
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCreativeInventoryItemMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCreativeInventoryItemMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..976f443
--- /dev/null
@@ -0,0 +1,54 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.input.IMouseInputHandler
+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.ui.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+@BindMouseInputHandler
+class SelectCreativeInventoryItemMouseInputHandler @Inject constructor(
+    private val gameItemsHolder: GameItemsHolder,
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+) : IMouseInputHandler {
+
+    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.Screen) &&
+                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.addItem(item)
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectFurnaceInventoryItemMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectFurnaceInventoryItemMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..eac8cfa
--- /dev/null
@@ -0,0 +1,102 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem.Companion.isNoneOrNull
+import ru.deadsoftware.cavedroid.game.objects.container.Furnace
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.ui.windows.inventory.FurnaceInventoryWindow
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+@BindMouseInputHandler
+class SelectFurnaceInventoryItemMouseInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : AbstractInventoryItemsMouseInputHandler(gameItemsHolder, gameWindowsManager, GameUiWindow.FURNACE) {
+
+    override val windowTexture get() = requireNotNull(Assets.textureRegions["furnace"])
+
+    private fun handleInsideInventoryGrid(action: MouseInputAction, xOnGrid: Int, yOnGrid: Int) {
+        val window = gameWindowsManager.currentWindow as FurnaceInventoryWindow
+
+        var itemIndex = xOnGrid + yOnGrid * GameWindowsConfigs.Furnace.itemsInRow
+        itemIndex += GameWindowsConfigs.Furnace.hotbarCells
+
+        if (itemIndex >= mobsController.player.inventory.size) {
+            itemIndex -= mobsController.player.inventory.size
+        }
+
+        handleInsidePlaceableCell(action, mobsController.player.inventory.items, window, itemIndex)
+    }
+
+    private fun handleInsideFuel(action: MouseInputAction) {
+        val window = gameWindowsManager.currentWindow as FurnaceInventoryWindow
+
+        if (!window.selectedItem.isNoneOrNull() && window.selectedItem?.item?.params?.burningTimeMs == null) {
+            return
+        }
+
+        handleInsidePlaceableCell(action, window.furnace.items, window, Furnace.FUEL_INDEX)
+    }
+
+    private fun handleInsideInput(action: MouseInputAction) {
+        val window = gameWindowsManager.currentWindow as FurnaceInventoryWindow
+
+        handleInsidePlaceableCell(action, window.furnace.items, window, Furnace.INPUT_INDEX)
+    }
+
+    private fun handleInsideResult(action: MouseInputAction) {
+        val window = gameWindowsManager.currentWindow as FurnaceInventoryWindow
+
+        handleInsideCraftResultCell(action, window.furnace.items, window, Furnace.RESULT_INDEX)
+    }
+
+    override fun handle(action: MouseInputAction) {
+        val texture = windowTexture
+
+        val xOnWindow = action.screenX - (action.cameraViewport.width / 2 - texture.regionWidth / 2)
+        val yOnWindow = action.screenY - (action.cameraViewport.height / 2 - texture.regionHeight / 2)
+
+        val xOnGrid = (xOnWindow - GameWindowsConfigs.Furnace.itemsGridMarginLeft) /
+                GameWindowsConfigs.Furnace.itemsGridColWidth
+        val yOnGrid = (yOnWindow - GameWindowsConfigs.Furnace.itemsGridMarginTop) /
+                GameWindowsConfigs.Furnace.itemsGridRowHeight
+
+        val isInsideInput = xOnWindow > GameWindowsConfigs.Furnace.smeltInputMarginLeft &&
+                xOnWindow < GameWindowsConfigs.Furnace.smeltInputMarginLeft + GameWindowsConfigs.Furnace.itemsGridColWidth &&
+                yOnWindow > GameWindowsConfigs.Furnace.smeltInputMarginTop &&
+                yOnWindow < GameWindowsConfigs.Furnace.smeltInputMarginTop + GameWindowsConfigs.Furnace.itemsGridRowHeight
+
+        val isInsideFuel = xOnWindow > GameWindowsConfigs.Furnace.smeltFuelMarginLeft &&
+                xOnWindow < GameWindowsConfigs.Furnace.smeltFuelMarginLeft + GameWindowsConfigs.Furnace.itemsGridColWidth &&
+                yOnWindow > GameWindowsConfigs.Furnace.smeltFuelMarginTop &&
+                yOnWindow < GameWindowsConfigs.Furnace.smeltFuelMarginTop + GameWindowsConfigs.Furnace.itemsGridRowHeight
+
+        val isInsideResult = xOnWindow > GameWindowsConfigs.Furnace.smeltResultOffsetX &&
+                xOnWindow < GameWindowsConfigs.Furnace.smeltResultOffsetX + GameWindowsConfigs.Furnace.itemsGridColWidth &&
+                yOnWindow > GameWindowsConfigs.Furnace.smeltResultOffsetY &&
+                yOnWindow < GameWindowsConfigs.Furnace.smeltResultOffsetY + GameWindowsConfigs.Furnace.itemsGridRowHeight
+
+        val isInsideInventoryGrid = xOnGrid >= 0 && xOnGrid < GameWindowsConfigs.Furnace.itemsInRow &&
+                yOnGrid >= 0 && yOnGrid < GameWindowsConfigs.Furnace.itemsInCol
+
+        if (isInsideInventoryGrid) {
+            handleInsideInventoryGrid(action, xOnGrid.toInt(), yOnGrid.toInt())
+        } else if (isInsideFuel) {
+            handleInsideFuel(action)
+        } else if (isInsideInput) {
+            handleInsideInput(action)
+        } else if (isInsideResult) {
+            handleInsideResult(action)
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectSurvivalInventoryItemMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectSurvivalInventoryItemMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..58687fc
--- /dev/null
@@ -0,0 +1,89 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.ui.windows.inventory.SurvivalInventoryWindow
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+@BindMouseInputHandler
+class SelectSurvivalInventoryItemMouseInputHandler @Inject constructor(
+    private val gameWindowsManager: GameWindowsManager,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : AbstractInventoryItemsMouseInputHandler(gameItemsHolder, gameWindowsManager, GameUiWindow.SURVIVAL_INVENTORY) {
+
+    override val windowTexture get() = requireNotNull(Assets.textureRegions["survival"])
+
+    private fun handleInsideInventoryGrid(action: MouseInputAction, xOnGrid: Int, yOnGrid: Int) {
+        val window = gameWindowsManager.currentWindow as SurvivalInventoryWindow
+
+        var itemIndex = xOnGrid + yOnGrid * GameWindowsConfigs.Survival.itemsInRow
+        itemIndex += GameWindowsConfigs.Survival.hotbarCells
+
+        if (itemIndex >= mobsController.player.inventory.size) {
+            itemIndex -= mobsController.player.inventory.size
+        }
+
+        handleInsidePlaceableCell(action, mobsController.player.inventory.items, window, itemIndex)
+    }
+
+    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!!
+
+        handleInsidePlaceableCell(action, window.craftingItems, window, index)
+
+        updateCraftResult(window)
+    }
+
+    private fun handleInsideCraftResult(action: MouseInputAction) {
+        val window = gameWindowsManager.currentWindow as SurvivalInventoryWindow
+
+        handleInsideCraftResultCell(action, window.craftResultList, window, 0)
+
+        updateCraftResult(window)
+    }
+
+    override fun handle(action: MouseInputAction) {
+        val xOnWindow = action.screenX - (action.cameraViewport.width / 2 - windowTexture.regionWidth / 2)
+        val yOnWindow = action.screenY - (action.cameraViewport.height / 2 - windowTexture.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) {
+            handleInsideCraftResult(action)
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/UseItemMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/mouse/UseItemMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..7405faf
--- /dev/null
@@ -0,0 +1,136 @@
+package ru.deadsoftware.cavedroid.game.input.handler.mouse
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.utils.Timer
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+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.useblock.IUseBlockAction
+import ru.deadsoftware.cavedroid.game.actions.useitem.IUseItemAction
+import ru.deadsoftware.cavedroid.game.input.IMouseInputHandler
+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.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+@BindMouseInputHandler
+class UseItemMouseInputHandler @Inject constructor(
+    private val mobsController: MobsController,
+    private val useItemActionMap: Map<String, @JvmSuppressWildcards IUseItemAction>,
+    private val placeBlockActionMap: Map<String, @JvmSuppressWildcards IPlaceBlockAction>,
+    private val useBlockActionMap: Map<String, @JvmSuppressWildcards IUseBlockAction>,
+    private val gameWindowsManager: GameWindowsManager,
+    private val gameWorld: GameWorld,
+    private val gameItemsHolder: GameItemsHolder,
+) : IMouseInputHandler {
+
+    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.inventory.activeItem.item
+        player.startHitting(false)
+        player.stopHitting()
+
+        if (item is Item.Placeable) {
+            placeBlockActionMap.placeToBackgroundAction(
+                item = item,
+                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 tryUseBlock() {
+        val block = gameWorld.getForeMap(mobsController.player.cursorX, mobsController.player.cursorY)
+            .takeIf { !it.isNone() }
+            ?: gameWorld.getBackMap(mobsController.player.cursorX, mobsController.player.cursorY)
+                .takeIf { !it.isNone() }
+            ?: return
+
+        useBlockActionMap[block.params.key]?.perform(
+            block = block,
+            x = mobsController.player.cursorX,
+            y = mobsController.player.cursorY
+        )
+    }
+
+    private fun handleUp(action: MouseInputAction) {
+        val player = mobsController.player
+        val item = player.inventory.activeItem.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");
+        } else if (item is Item.Food && player.health < player.maxHealth) {
+            player.heal(item.heal)
+            player.decreaseCurrentItemCount(gameItemsHolder)
+        } else {
+            tryUseBlock()
+        }
+    }
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/touch/JoystickInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/handler/touch/JoystickInputHandler.kt
new file mode 100644 (file)
index 0000000..d717c18
--- /dev/null
@@ -0,0 +1,148 @@
+package ru.deadsoftware.cavedroid.game.input.handler.touch
+
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindMouseInputHandler
+import com.badlogic.gdx.utils.TimeUtils
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.*
+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.Player
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+@BindMouseInputHandler
+class JoystickInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+    private val gameWindowsManager: GameWindowsManager,
+    private val gameWorld: GameWorld,
+) : IMouseInputHandler {
+
+    private var activateTimeMs = 0L
+    private var cursorTimeoutMs = 100L
+
+    private var active = false
+        set(value) {
+            if (!value) {
+                resetVelocity()
+                if (TimeUtils.timeSinceMillis(activateTimeMs) < 200L &&
+                    mobsController.player.controlMode != Player.ControlMode.CURSOR) {
+                    mobsController.player.jump()
+                }
+            } else {
+                activateTimeMs = TimeUtils.millis()
+            }
+            field = value
+        }
+
+    private fun resetVelocity() {
+        mobsController.player.velocity.x = 0f
+
+        if (mobsController.player.isFlyMode) {
+            mobsController.player.velocity.y = 0f
+        }
+    }
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return gameWindowsManager.getCurrentWindow() == GameUiWindow.NONE &&
+                mainConfig.isTouch &&
+//                mobsController.player.controlMode == Player.ControlMode.WALK &&
+                mainConfig.joystick != null &&
+                (action.actionKey is MouseInputActionKey.Touch) &&
+                (action.actionKey.pointer == mainConfig.joystick?.pointer || !active) &&
+                ((action.actionKey is MouseInputActionKey.Dragged) ||
+                        (action.screenX < action.cameraViewport.width / 2 && !action.actionKey.touchUp || active)) &&
+                !(action.actionKey is MouseInputActionKey.Screen && isInsideHotbar(action))
+
+    }
+
+    private fun handleTouchDown(action: MouseInputAction) {
+        val key = action.actionKey as MouseInputActionKey.Screen
+        mainConfig.joystick?.activate(action.screenX, action.screenY, key.pointer) ?: return
+        active = true
+    }
+
+    private fun handleTouchUp(action: MouseInputAction) {
+        mainConfig.joystick?.deactivate()
+        active = false
+    }
+
+    private fun handleCursor() {
+        val joystick = mainConfig.joystick ?: return
+
+        if (TimeUtils.timeSinceMillis(cursorTimeoutMs) < 150L) {
+            return
+        }
+
+        val pastCursorX = mobsController.player.cursorX
+        val pastCursorY = mobsController.player.cursorY
+
+        if (Math.abs(joystick.activeX - joystick.centerX) >= Joystick.RADIUS / 2) {
+            mobsController.player.cursorX += if (joystick.activeX > joystick.centerX) 1 else -1
+            cursorTimeoutMs = TimeUtils.millis()
+        }
+
+        if (Math.abs(joystick.activeY - joystick.centerY) >= Joystick.RADIUS / 2) {
+            mobsController.player.cursorY += if (joystick.activeY > joystick.centerY) 1 else -1
+            cursorTimeoutMs = TimeUtils.millis()
+        }
+
+        mobsController.player.checkCursorBounds(gameWorld)
+
+        if (mobsController.player.cursorX != pastCursorX || mobsController.player.cursorY != pastCursorY) {
+            mobsController.player.blockDamage = 0f
+        }
+    }
+
+    private fun handleDragged() {
+        if (!active) {
+            return
+        }
+
+        if (mobsController.player.controlMode == Player.ControlMode.CURSOR) {
+            handleCursor()
+            return
+        }
+
+        val joystick = mainConfig.joystick ?: return
+        val joyVector = joystick.getVelocityVector()
+
+        if (mobsController.player.isFlyMode) {
+            joyVector.scl(2f);
+        }
+
+        mobsController.player.velocity.x = joyVector.x
+
+        mobsController.player.setDir(
+            if (joyVector.x < 0) {
+                Mob.Direction.LEFT
+            } else {
+                Mob.Direction.RIGHT
+            }
+        )
+
+        if (mobsController.player.isFlyMode) {
+            mobsController.player.velocity.y = joyVector.y
+        }
+    }
+
+    override fun handle(action: MouseInputAction) {
+        when (action.actionKey) {
+            is MouseInputActionKey.Dragged -> handleDragged()
+            else -> {
+                if (action.actionKey.touchUp) {
+                    handleTouchUp(action)
+                } else {
+                    handleTouchDown(action)
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/mapper/KeyboardInputActionMapper.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/mapper/KeyboardInputActionMapper.kt
new file mode 100644 (file)
index 0000000..8d18d07
--- /dev/null
@@ -0,0 +1,46 @@
+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.Up
+            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.Q -> KeyboardInputActionKey.DropItem
+
+            Input.Keys.NUM_1 -> KeyboardInputActionKey.SelectHotbarSlot(0)
+            Input.Keys.NUM_2 -> KeyboardInputActionKey.SelectHotbarSlot(1)
+            Input.Keys.NUM_3 -> KeyboardInputActionKey.SelectHotbarSlot(2)
+            Input.Keys.NUM_4 -> KeyboardInputActionKey.SelectHotbarSlot(3)
+            Input.Keys.NUM_5 -> KeyboardInputActionKey.SelectHotbarSlot(4)
+            Input.Keys.NUM_6 -> KeyboardInputActionKey.SelectHotbarSlot(5)
+            Input.Keys.NUM_7 -> KeyboardInputActionKey.SelectHotbarSlot(6)
+            Input.Keys.NUM_8 -> KeyboardInputActionKey.SelectHotbarSlot(7)
+            Input.Keys.NUM_9 -> KeyboardInputActionKey.SelectHotbarSlot(8)
+
+            else -> null
+        }
+
+        return actionKey?.let { KeyboardInputAction(it, isKeyDown) }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/mapper/MouseInputActionMapper.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/input/mapper/MouseInputActionMapper.kt
new file mode 100644 (file)
index 0000000..5ef450a
--- /dev/null
@@ -0,0 +1,82 @@
+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,
+        pointer: Int,
+    ): MouseInputAction? {
+        val actionKey = mapActionKey(button, touchUp, pointer) ?: return null
+
+        return MouseInputAction(
+            screenX = getScreenX(mouseX),
+            screenY = getScreenY(mouseY),
+            actionKey = actionKey,
+            cameraViewport = cameraViewport,
+        )
+    }
+
+    fun mapDragged(
+        mouseX: Float,
+        mouseY: Float,
+        cameraViewport: Rectangle,
+        pointer: Int,
+    ): MouseInputAction {
+        return MouseInputAction(
+            screenX = getScreenX(mouseX),
+            screenY = getScreenY(mouseY),
+            actionKey = MouseInputActionKey.Dragged(pointer),
+            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, pointer: Int): MouseInputActionKey? {
+        return when (button) {
+            Input.Buttons.LEFT -> MouseInputActionKey.Left(touchUp)
+            Input.Buttons.RIGHT -> MouseInputActionKey.Right(touchUp)
+            Input.Buttons.MIDDLE -> MouseInputActionKey.Middle(touchUp)
+            -1 -> MouseInputActionKey.Screen(touchUp, pointer)
+            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
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/FallingBlock.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/FallingBlock.kt
new file mode 100644 (file)
index 0000000..d4494a3
--- /dev/null
@@ -0,0 +1,96 @@
+package ru.deadsoftware.cavedroid.game.mobs
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.utils.bl
+import ru.deadsoftware.cavedroid.misc.utils.px
+
+class FallingBlock(
+    private val blockKey: String,
+    x: Float,
+    y: Float,
+) : Mob(x, y, 1.px, 1.px, Direction.RIGHT, Type.FALLING_BLOCK, Int.MAX_VALUE) {
+
+    private var _block: Block? = null
+
+    init {
+        velocity.y = 1f
+    }
+
+    override fun changeDir() = Unit
+
+    override fun getSpeed() = 0f
+
+    override fun jump() = Unit
+
+    override fun ai(
+        gameWorld: GameWorld,
+        gameItemsHolder: GameItemsHolder,
+        mobsController: MobsController,
+        delta: Float
+    ) {
+        if (_block == null) {
+            _block = gameItemsHolder.getBlock(blockKey)
+        }
+
+        if (velocity.isZero) {
+            gameWorld.setForeMap(x.bl, y.bl, _block)
+            kill()
+        }
+    }
+
+    override fun draw(
+        spriteBatch: SpriteBatch,
+        x: Float,
+        y: Float,
+        delta: Float
+    ) {
+        _block?.draw(spriteBatch, x, y)
+    }
+
+    override fun getSaveData(): SaveDataDto.FallingBlockSaveData {
+        return SaveDataDto.FallingBlockSaveData(
+            version = SAVE_DATA_VERSION,
+            x = x,
+            y = y,
+            width = width,
+            height = height,
+            velocityX = velocity.x,
+            velocityY = velocity.y,
+            type = mType,
+            animDelta = mAnimDelta,
+            anim = mAnim,
+            direction = mDirection,
+            dead = mDead,
+            canJump = mCanJump,
+            flyMode = mFlyMode,
+            maxHealth = mMaxHealth,
+            health = mHealth,
+            blockKey = blockKey,
+        )
+    }
+
+    companion object {
+        private const val SAVE_DATA_VERSION = 1
+
+        fun fromSaveData(saveData: SaveDataDto.FallingBlockSaveData): FallingBlock {
+            saveData.verifyVersion(SAVE_DATA_VERSION)
+
+            return FallingBlock(saveData.blockKey, saveData.x, saveData.y).apply {
+                velocity.x = saveData.velocityX
+                velocity.y = saveData.velocityY
+                mAnimDelta = saveData.animDelta
+                mAnim = saveData.anim
+                mDirection = saveData.direction
+                mDead = saveData.dead
+                mCanJump = saveData.canJump
+                mFlyMode = saveData.flyMode
+                mMaxHealth = saveData.maxHealth
+                mHealth = saveData.health
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/MobSaveDataMapper.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/MobSaveDataMapper.kt
new file mode 100644 (file)
index 0000000..49adee3
--- /dev/null
@@ -0,0 +1,12 @@
+package ru.deadsoftware.cavedroid.game.mobs
+
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto
+
+fun fromSaveData(saveData: SaveDataDto.MobSaveDataDto): Mob {
+    return when (saveData) {
+        is SaveDataDto.PigSaveData -> Pig.fromSaveData(saveData)
+        is SaveDataDto.FallingBlockSaveData -> FallingBlock.fromSaveData(saveData)
+
+        is SaveDataDto.PlayerSaveData -> throw IllegalArgumentException("Cannot load player as regular Mob")
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/MobsController.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/MobsController.kt
new file mode 100644 (file)
index 0000000..4b2c209
--- /dev/null
@@ -0,0 +1,57 @@
+package ru.deadsoftware.cavedroid.game.mobs
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.player.Player
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto
+import ru.deadsoftware.cavedroid.game.ui.TooltipManager
+import ru.deadsoftware.cavedroid.misc.Saveable
+import java.util.*
+import javax.inject.Inject
+
+@GameScope
+class MobsController @Inject constructor(
+    gameItemsHolder: GameItemsHolder,
+    tooltipManager: TooltipManager,
+) : Saveable {
+
+    private val _mobs = LinkedList<Mob>()
+
+    var player: Player = Player(gameItemsHolder, tooltipManager)
+        private set
+
+    val mobs: List<Mob>
+        get() = _mobs
+
+    fun addMob(mob: Mob) {
+        _mobs.add(mob)
+    }
+
+    override fun getSaveData(): SaveDataDto.MobsControllerSaveData {
+        return SaveDataDto.MobsControllerSaveData(
+            version = SAVE_DATA_VERSION,
+            mobs = _mobs.map(Mob::getSaveData),
+            player = player.getSaveData(),
+        )
+    }
+
+    companion object {
+        private const val SAVE_DATA_VERSION = 1
+
+        private const val TAG = "MobsController"
+
+        fun fromSaveData(
+            saveData: SaveDataDto.MobsControllerSaveData,
+            gameItemsHolder: GameItemsHolder,
+            tooltipManager: TooltipManager
+        ): MobsController {
+            saveData.verifyVersion(SAVE_DATA_VERSION)
+
+            return MobsController(gameItemsHolder, tooltipManager)
+                .apply {
+                    _mobs.addAll(saveData.mobs.map { mob -> Mob.fromSaveData(mob) })
+                    player = Player.fromSaveData(saveData.player, gameItemsHolder, tooltipManager)
+                }
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/PeacefulMob.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/PeacefulMob.kt
new file mode 100644 (file)
index 0000000..b0e5bc6
--- /dev/null
@@ -0,0 +1,20 @@
+package ru.deadsoftware.cavedroid.game.mobs
+
+import com.badlogic.gdx.math.MathUtils
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+
+abstract class PeacefulMob(x: Float, y: Float, width: Float, height: Float, direction: Direction, maxHealth: Int, )
+    : Mob(x, y, width, height, direction, Type.MOB, maxHealth) {
+
+    override fun ai(world: GameWorld, gameItemsHolder: GameItemsHolder, mobsController: MobsController, delta: Float) {
+        if (MathUtils.randomBoolean(delta)) {
+            if (velocity.x != 0f) {
+                velocity.x = 0f
+            } else {
+                changeDir()
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/Pig.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/Pig.kt
new file mode 100644 (file)
index 0000000..b080e40
--- /dev/null
@@ -0,0 +1,106 @@
+package ru.deadsoftware.cavedroid.game.mobs
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.math.Vector2
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+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) : PeacefulMob(x, y, WIDTH, HEIGHT, randomDir(), 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 damage(damage: Int) {
+        super.damage(damage)
+
+        if (damage > 0) {
+            if (canJump()) {
+                jump()
+            }
+        }
+    }
+
+    override fun getDrop(gameItemsHolder: GameItemsHolder): List<InventoryItem> {
+        return listOf(gameItemsHolder.getItem("porkchop_raw").toInventoryItem())
+    }
+
+    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, tint = tintColor)
+        spriteBatch.drawSprite(getBackgroundLeg(), rightLegX, legY, -anim, tint = tintColor)
+        spriteBatch.drawSprite(getBody(direction), x, y, tint = tintColor)
+        spriteBatch.drawSprite(getForegroundLeg(), leftLegX, legY, anim, tint = tintColor)
+        spriteBatch.drawSprite(getForegroundLeg(), rightLegX, legY, anim, tint = tintColor)
+    }
+
+    override fun getSaveData(): SaveDataDto.PigSaveData {
+        return SaveDataDto.PigSaveData(
+            version = SAVE_DATA_VERSION,
+            x = x,
+            y = y,
+            width = width,
+            height = height,
+            velocityX = velocity.x,
+            velocityY = velocity.y,
+            type = mType,
+            animDelta = mAnimDelta,
+            anim = mAnim,
+            direction = mDirection,
+            dead = mDead,
+            canJump = mCanJump,
+            flyMode = mFlyMode,
+            maxHealth = mMaxHealth,
+            health = mHealth
+        )
+    }
+    
+    companion object {
+        private const val SAVE_DATA_VERSION = 1
+
+        private const val WIDTH = 25f
+        private const val HEIGHT = 18f
+        private const val SPEED =  48f
+        private const val JUMP_VELOCITY = -133.332f
+        private const val MAX_HEALTH = 10
+
+        fun fromSaveData(saveData: SaveDataDto.PigSaveData): Pig {
+            saveData.verifyVersion(SAVE_DATA_VERSION)
+
+            return Pig(saveData.x, saveData.y).apply {
+                velocity.x = saveData.velocityX
+                velocity.y = saveData.velocityY
+                mAnimDelta = saveData.animDelta
+                mAnim = saveData.anim
+                mDirection = saveData.direction
+                mDead = saveData.dead
+                mCanJump = saveData.canJump
+                mFlyMode = saveData.flyMode
+                mMaxHealth = saveData.maxHealth
+                mHealth = saveData.health
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/player/Inventory.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/mobs/player/Inventory.kt
new file mode 100644 (file)
index 0000000..91e8be1
--- /dev/null
@@ -0,0 +1,179 @@
+package ru.deadsoftware.cavedroid.game.mobs.player
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.objects.drop.Drop
+import ru.deadsoftware.cavedroid.game.ui.TooltipManager
+import ru.deadsoftware.cavedroid.misc.Saveable
+
+class Inventory @JvmOverloads constructor(
+    val size: Int,
+    val hotbarSize: Int,
+    gameItemsHolder: GameItemsHolder,
+    tooltipManager: TooltipManager,
+    initialItems: List<InventoryItem>? = null
+) : Saveable {
+
+    @Suppress("UNNECESSARY_LATEINIT")
+    private lateinit var tooltipManager: TooltipManager
+
+    @Suppress("UNNECESSARY_LATEINIT")
+    private lateinit var fallbackItem: InventoryItem
+
+    private val _items: Array<InventoryItem>
+
+    init {
+        fallbackItem = gameItemsHolder.fallbackItem.toInventoryItem()
+        this.tooltipManager = tooltipManager
+
+        if (size < 0 || hotbarSize < 0 || hotbarSize > size) {
+            throw IllegalArgumentException("Invalid inventory sizes: hotbarSize=$hotbarSize; size=$size")
+        }
+
+        _items = Array(size) { index -> initialItems?.getOrNull(index) ?: InventoryItem(gameItemsHolder.fallbackItem) }
+    }
+
+    val items get() = _items.asList() as MutableList<InventoryItem>
+
+    val hotbarItems get() = items.subList(0, hotbarSize)
+
+    private var _activeSlot = 0
+
+    var activeSlot
+        get() = _activeSlot
+        set(value) {
+            if (value in 0 ..< hotbarSize) {
+                _activeSlot = value
+                showCurrentItemTooltip()
+            }
+        }
+
+    fun showCurrentItemTooltip() {
+        tooltipManager.showHotbarTooltip(activeItem.item.params.name)
+    }
+
+    val activeItem get() = _items[activeSlot]
+
+    fun initItems(gameItemsHolder: GameItemsHolder, tooltipManager: TooltipManager) {
+        this.tooltipManager = tooltipManager
+        fallbackItem = gameItemsHolder.fallbackItem.toInventoryItem()
+        _items.forEach { item ->
+            item.init(gameItemsHolder)
+        }
+    }
+
+    private fun getItemPickSlot(drop: Drop): Int {
+        val item = drop.item
+
+        for (i in _items.indices) {
+            val inventoryItem = _items[i]
+
+            if (item == inventoryItem.item && inventoryItem.canBeAdded()) {
+                return i
+            }
+        }
+
+        for (i in _items.indices) {
+            val inventoryItem = _items[i]
+
+            if (inventoryItem.item.isNone()) {
+                return i
+            }
+        }
+
+        return -1
+    }
+
+    fun canPickItem(drop: Drop): Boolean {
+        return getItemPickSlot(drop) >= 0
+    }
+
+    fun pickDrop(drop: Drop) {
+        val slot = getItemPickSlot(drop).takeIf { it >= 0 } ?: return
+        val inventoryItem = _items[slot]
+
+        if (inventoryItem.item == drop.item) {
+            if (inventoryItem.canBeAdded(drop.amount)) {
+                inventoryItem.add(drop.amount)
+                drop.pickedUp = true
+            } else {
+                val addCount = inventoryItem.item.params.maxStack - inventoryItem.amount
+                inventoryItem.add(addCount)
+                drop.subtract(addCount)
+                pickDrop(drop)
+            }
+        } else {
+            _items[slot] = drop.item.toInventoryItem(drop.amount)
+            if (slot == activeSlot) {
+                showCurrentItemTooltip()
+            }
+            drop.pickedUp = true
+        }
+    }
+
+    fun addItem(item: Item) {
+        _items.copyInto(
+            destination = _items,
+            destinationOffset = 1,
+            startIndex = 0,
+            endIndex = size - 1
+        )
+
+        _items[0] = item.toInventoryItem(item.params.maxStack)
+        showCurrentItemTooltip()
+    }
+
+    @JvmOverloads
+    fun decreaseItemAmount(slot: Int, count: Int = 1) {
+        val item = _items[slot]
+        item.subtract(count)
+        if (item.amount <= 0) {
+            _items[slot] = fallbackItem
+        }
+    }
+
+    @JvmOverloads
+    fun decreaseCurrentItemAmount(count: Int = 1) {
+        decreaseItemAmount(activeSlot, count)
+    }
+
+    fun clear() {
+        for (i in _items.indices) {
+            _items[i] = fallbackItem
+        }
+    }
+
+    override fun getSaveData(): SaveDataDto.InventorySaveData {
+        return SaveDataDto.InventorySaveData(
+            version = SAVE_DATA_VERSION,
+            size = size,
+            hotbarSize = hotbarSize,
+            activeSlot = _activeSlot,
+            items = items.map(InventoryItem::getSaveData)
+        )
+    }
+
+    companion object {
+        private const val SAVE_DATA_VERSION = 1
+
+        fun fromSaveData(
+            saveData: SaveDataDto.InventorySaveData,
+            gameItemsHolder: GameItemsHolder,
+            tooltipManager: TooltipManager,
+        ): Inventory {
+            saveData.verifyVersion(SAVE_DATA_VERSION)
+
+            return Inventory(
+                size = saveData.size,
+                hotbarSize = saveData.hotbarSize,
+                gameItemsHolder = gameItemsHolder,
+                tooltipManager = tooltipManager,
+                initialItems = saveData.items.map { item -> InventoryItem.fromSaveData(item, gameItemsHolder) }
+            ).apply {
+                _activeSlot = saveData.activeSlot
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/Block.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/Block.kt
new file mode 100644 (file)
index 0000000..fa99bfd
--- /dev/null
@@ -0,0 +1,232 @@
+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
+
+    protected var animation: Array<Sprite>? = null
+
+    private var _sprite: Sprite? = null
+        get() {
+            return animation?.get(currentAnimationFrame) ?: field
+        }
+
+    open 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 isContainer(): Boolean {
+        contract { returns(true) implies (this@Block is Container) }
+        return this is Container
+    }
+
+    fun isFurnace(): Boolean {
+        contract { returns(true) implies (this@Block is Furnace) }
+        return this is Furnace
+    }
+
+    fun isChest(): Boolean {
+        contract { returns(true) implies (this@Block is Chest) }
+        return this is Chest
+    }
+
+    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
+        )
+    }
+
+    sealed class Container() : Block()
+
+    data class None(
+        override val params: CommonBlockParams
+    ) : Block()
+
+    data class Normal(
+        override val params: CommonBlockParams,
+    ) : Block()
+
+    data class Furnace(
+        override val params: CommonBlockParams,
+    ): Container() {
+
+        override val sprite: Sprite
+            get() = getSprite(false)
+
+        private fun getSprite(isActive: Boolean): Sprite {
+            return animation?.let { animation ->
+                if (isActive) {
+                    animation[1]
+                } else {
+                    animation[0]
+                }
+            } ?: sprite
+        }
+
+        fun draw(spriter: SpriteBatch, x: Float, y: Float, isActive: Boolean) {
+            getSprite(isActive).apply {
+                setBounds(
+                    /* x = */ x + params.spriteMargins.left,
+                    /* y = */ y + params.spriteMargins.top,
+                    /* width = */ spriteWidth,
+                    /* height = */ spriteHeight
+                )
+                draw(spriter)
+            }
+        }
+
+    }
+
+    data class Chest(
+        override val params: CommonBlockParams
+    ): Container()
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/BlockAnimationInfo.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/BlockDropInfo.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/BlockMargins.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/CommonBlockParams.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/block/CommonBlockParams.kt
new file mode 100644 (file)
index 0000000..1945462
--- /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(
+    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?,
+    val isFallable: Boolean,
+)
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/craft/CraftingRecipe.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/craft/CraftingRecipe.kt
new file mode 100644 (file)
index 0000000..c8b2a86
--- /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<Regex>,
+    val output: CraftingResult
+)
+
+data class CraftingResult(
+    val item: Item,
+    val amount: Int,
+) {
+    fun toInventoryItem() = InventoryItem(item, amount)
+}
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt
new file mode 100644 (file)
index 0000000..ecbe5bf
--- /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(
+    @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,
+    @SerialName("fallable") val fallable: Boolean = false,
+)
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/CraftingDto.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt
new file mode 100644 (file)
index 0000000..a276b71
--- /dev/null
@@ -0,0 +1,24 @@
+package ru.deadsoftware.cavedroid.game.model.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ItemDto(
+    @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,
+    @SerialName("tint") val tint: String? = null,
+    @SerialName("burning_time") val burningTime: Long? = null,
+    @SerialName("smelt_product") val smeltProduct: String? = null,
+    @SerialName("heal") val heal: Int? = null,
+)
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/SaveDataDto.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/dto/SaveDataDto.kt
new file mode 100644 (file)
index 0000000..e6ad828
--- /dev/null
@@ -0,0 +1,191 @@
+package ru.deadsoftware.cavedroid.game.model.dto
+
+import kotlinx.serialization.Contextual
+import kotlinx.serialization.Serializable
+import ru.deadsoftware.cavedroid.game.mobs.Mob
+import ru.deadsoftware.cavedroid.game.mobs.player.Player.ControlMode
+
+@Serializable
+sealed class SaveDataDto {
+
+    abstract val version: Int
+
+    fun verifyVersion(expectedVersion: Int) {
+        require(version == expectedVersion) {
+            "${this::class.simpleName} version mismatch ($version != $expectedVersion)"
+        }
+    }
+
+    @Serializable
+    sealed class ContainerSaveDataDto : SaveDataDto() {
+        abstract val size: Int
+        abstract val items: List<InventoryItemSaveData>
+    }
+
+    @Serializable
+    sealed class RectangleObjectSaveDataDto : SaveDataDto() {
+        abstract val x: Float
+        abstract val y: Float
+        abstract val width: Float
+        abstract val height: Float
+        abstract val velocityX: Float
+        abstract val velocityY: Float
+    }
+
+    @Serializable
+    sealed class MobSaveDataDto : RectangleObjectSaveDataDto() {
+        abstract val type: Mob.Type
+        abstract val animDelta: Int
+        abstract val anim: Float
+        abstract val direction: Mob.Direction
+        abstract val dead: Boolean
+        abstract val canJump: Boolean
+        abstract val flyMode: Boolean
+        abstract val maxHealth: Int
+        abstract val health: Int
+    }
+
+    @Serializable
+    data class InventoryItemSaveData(
+        override val version: Int,
+        val itemKey: String,
+        val amount: Int,
+    ) : SaveDataDto()
+
+    @Serializable
+    data class InventorySaveData(
+        override val version: Int,
+        override val size: Int,
+        val hotbarSize: Int,
+        val activeSlot: Int,
+        override val items: List<InventoryItemSaveData>,
+    ) : ContainerSaveDataDto()
+
+    @Serializable
+    data class FurnaceSaveData(
+        override val version: Int,
+        override val size: Int,
+        val currentFuelItemKey: String?,
+        override val items: List<InventoryItemSaveData>,
+        val startBurnTimeMs: Long,
+        val startSmeltTimeMs: Long,
+        val burnProgress: Float,
+        val smeltProgress: Float,
+    ) : ContainerSaveDataDto()
+
+    @Serializable
+    data class ChestSaveData(
+        override val version: Int,
+        override val size: Int,
+        override val items: List<InventoryItemSaveData>
+    ) : ContainerSaveDataDto()
+
+    @Serializable
+    data class ContainerControllerSaveData(
+        override val version: Int,
+        val containerMap: Map<String, @Contextual ContainerSaveDataDto>,
+    ): SaveDataDto()
+
+    @Serializable
+    data class DropSaveData(
+        override val version: Int,
+        override val x: Float,
+        override val y: Float,
+        override val width: Float,
+        override val height: Float,
+        override val velocityX: Float,
+        override val velocityY: Float,
+        val itemKey: String,
+        val amount: Int,
+        val pickedUp: Boolean
+    ) : RectangleObjectSaveDataDto()
+    
+    @Serializable
+    data class DropControllerSaveData(
+        override val version: Int,
+        val drops: List<DropSaveData>
+    ) : SaveDataDto()
+    
+    @Serializable
+    data class PigSaveData(
+        override val version: Int,
+        override val x: Float,
+        override val y: Float,
+        override val width: Float,
+        override val height: Float,
+        override val velocityX: Float,
+        override val velocityY: Float,
+        override val type: Mob.Type,
+        override val animDelta: Int,
+        override val anim: Float,
+        override val direction: Mob.Direction,
+        override val dead: Boolean,
+        override val canJump: Boolean,
+        override val flyMode: Boolean,
+        override val maxHealth: Int,
+        override val health: Int,
+    ) : MobSaveDataDto()
+
+    @Serializable
+    data class FallingBlockSaveData(
+        override val version: Int,
+        override val x: Float,
+        override val y: Float,
+        override val width: Float,
+        override val height: Float,
+        override val velocityX: Float,
+        override val velocityY: Float,
+        override val type: Mob.Type,
+        override val animDelta: Int,
+        override val anim: Float,
+        override val direction: Mob.Direction,
+        override val dead: Boolean,
+        override val canJump: Boolean,
+        override val flyMode: Boolean,
+        override val maxHealth: Int,
+        override val health: Int,
+        val blockKey: String,
+    ) : MobSaveDataDto()
+
+    @Serializable
+    data class PlayerSaveData(
+        override val version: Int,
+        override val type: Mob.Type,
+        override val animDelta: Int,
+        override val anim: Float,
+        override val direction: Mob.Direction,
+        override val dead: Boolean,
+        override val canJump: Boolean,
+        override val flyMode: Boolean,
+        override val maxHealth: Int,
+        override val health: Int,
+        override val x: Float,
+        override val y: Float,
+        override val width: Float,
+        override val height: Float,
+        override val velocityX: Float,
+        override val velocityY: Float,
+        val hitting: Boolean,
+        val hittingWithDamage: Boolean,
+        val hitAnim: Float,
+        val hitAnimDelta: Float,
+        val inventory: InventorySaveData,
+        val gameMode: Int,
+        val swim: Boolean,
+        val headRotation: Float,
+        val blockDamage: Float,
+        val cursorX: Int,
+        val cursorY: Int,
+        val spawnPointX: Float,
+        val spawnPointY: Float,
+        val controlMode: ControlMode,
+    ) : MobSaveDataDto()
+
+    @Serializable
+    data class MobsControllerSaveData(
+        override val version: Int,
+        val mobs: List<@Contextual MobSaveDataDto>,
+        val player: PlayerSaveData,
+    ) : SaveDataDto()
+    
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/item/CommonItemParams.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/item/CommonItemParams.kt
new file mode 100644 (file)
index 0000000..00af08b
--- /dev/null
@@ -0,0 +1,12 @@
+package ru.deadsoftware.cavedroid.game.model.item
+
+import ru.deadsoftware.cavedroid.misc.utils.SpriteOrigin
+
+data class CommonItemParams(
+    val key: String,
+    val name: String,
+    val inHandSpriteOrigin: SpriteOrigin,
+    val maxStack: Int,
+    val burningTimeMs: Long?,
+    val smeltProductKey: String?,
+)
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/item/InventoryItem.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/item/InventoryItem.kt
new file mode 100644 (file)
index 0000000..f420120
--- /dev/null
@@ -0,0 +1,165 @@
+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.game.model.dto.SaveDataDto
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.Saveable
+import ru.deadsoftware.cavedroid.misc.utils.drawSprite
+import ru.deadsoftware.cavedroid.misc.utils.drawString
+import ru.deadsoftware.cavedroid.misc.utils.px
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+class InventoryItem @JvmOverloads constructor(
+    val itemKey: String,
+    _amount: Int = 1,
+) :  Saveable {
+
+    var amount = _amount
+        set(value) {
+            field = if (value < 0) {
+                0
+            } else {
+                value
+            }
+        }
+
+    private var _item: Item? = null
+
+    var item: Item
+        get() {
+            requireNotNull(_item) { "_item is null" }
+            return _item.takeIf { amount > 0 } ?: throw IllegalArgumentException("Accessing item with zero amount")
+        }
+        private set (value) {
+            _item = value
+        }
+
+    @JvmOverloads
+    constructor(item: Item, amount: Int = 1) : this(item.params.key, amount) {
+        _item = item
+    }
+
+    fun init(gameItemsHolder: GameItemsHolder) {
+        if (_item != null) {
+            return
+        }
+        _item = gameItemsHolder.getItem(itemKey)
+    }
+
+    @JvmOverloads
+    fun add(count: Int = 1) {
+        if (count > 0 && Int.MAX_VALUE - count < amount) {
+            throw IllegalArgumentException("$amount + $count exceeds Int.MAX_VALUE")
+        }
+
+        amount += count
+    }
+
+    @JvmOverloads
+    fun subtract(count: Int = 1) {
+        if (count < 0) {
+            throw IllegalArgumentException("Can't subtract negative amount")
+        }
+
+        add(-count)
+    }
+
+    @JvmOverloads
+    fun canBeAdded(count: Int = 1): Boolean {
+        return amount + count <= item.params.maxStack
+    }
+
+    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
+        val placeableMarginTop = (item as? Item.Placeable)?.block?.params?.spriteMargins?.top ?: 0
+        val placeableMarginLeft = (item as? Item.Placeable)?.block?.params?.spriteMargins?.left ?: 0
+        spriteBatch.drawSprite(sprite, x + placeableMarginLeft, y + placeableMarginTop)
+
+        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)
+            )
+        }
+    }
+
+    override fun getSaveData(): SaveDataDto.InventoryItemSaveData {
+        return SaveDataDto.InventoryItemSaveData(
+            version = SAVE_DATA_VERSION,
+            itemKey = itemKey,
+            amount = amount,
+        )
+    }
+
+    companion object {
+        private const val SAVE_DATA_VERSION = 1
+
+        @OptIn(ExperimentalContracts::class)
+        fun InventoryItem?.isNoneOrNull(): Boolean {
+            contract { returns(false) implies(this@isNoneOrNull != null) }
+            return this?.item == null || this.item.isNone()
+        }
+
+
+        fun fromSaveData(
+            saveData: SaveDataDto.InventoryItemSaveData,
+            gameItemsHolder: GameItemsHolder? = null
+        ): InventoryItem {
+            saveData.verifyVersion(SAVE_DATA_VERSION)
+
+            val inventoryItem = InventoryItem(saveData.itemKey, saveData.amount)
+            gameItemsHolder?.let(inventoryItem::init)
+
+            return inventoryItem
+        }
+    }
+
+}
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/item/Item.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/item/Item.kt
new file mode 100644 (file)
index 0000000..9b2c62d
--- /dev/null
@@ -0,0 +1,151 @@
+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 Usable) }
+        return this is Usable
+    }
+
+    fun isFood(): Boolean {
+        contract { returns(true) implies (this@Item is Food) }
+        return this is Food
+    }
+
+    @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 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 Usable(
+        override val params: CommonItemParams,
+        override val sprite: Sprite,
+        val useActionKey: String
+    ) : Item()
+
+    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 Food(
+        override val params: CommonItemParams,
+        override val sprite: Sprite,
+        val heal: Int,
+    ) : Item()
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt
new file mode 100644 (file)
index 0000000..19d8c47
--- /dev/null
@@ -0,0 +1,108 @@
+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))
+            "furnace" -> Furnace(commonBlockParams)
+            "chest" -> Chest(commonBlockParams)
+            "none" -> None(commonBlockParams)
+            else -> Normal(commonBlockParams)
+        }
+    }
+
+    private fun mapCommonParams(key: String, dto: BlockDto): CommonBlockParams {
+        return CommonBlockParams(
+            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,
+            isFallable = dto.fallable,
+        )
+    }
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt
new file mode 100644 (file)
index 0000000..a1a6f0c
--- /dev/null
@@ -0,0 +1,70 @@
+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 ru.deadsoftware.cavedroid.misc.utils.colorFromHexString
+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)))
+            "usable" -> Usable(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))
+            "food" -> Food(params, requireNotNull(loadSprite(dto)), requireNotNull(dto.heal))
+            "none" -> None(params)
+            else -> throw IllegalArgumentException("Unknown item type ${dto.type}")
+        }
+    }
+
+    private fun mapCommonParams(key: String, dto: ItemDto): CommonItemParams {
+        return CommonItemParams(
+            key = key,
+            name = dto.name,
+            inHandSpriteOrigin = SpriteOrigin(
+                x = dto.originX,
+                y = dto.origin_y,
+            ),
+            maxStack = dto.maxStack,
+            burningTimeMs = dto.burningTime,
+            smeltProductKey = dto.smeltProduct,
+        )
+    }
+
+    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)
+                dto.tint?.let {
+                    color = colorFromHexString(it)
+                }
+            }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/model/world/Biome.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/model/world/generator/WorldGeneratorConfig.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/Chest.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/Chest.kt
new file mode 100644 (file)
index 0000000..a9e893b
--- /dev/null
@@ -0,0 +1,38 @@
+package ru.deadsoftware.cavedroid.game.objects.container
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.misc.Saveable
+
+class Chest @JvmOverloads constructor(
+    gameItemsHolder: GameItemsHolder,
+    initialItems: List<InventoryItem>? = null
+) : Container(SIZE, gameItemsHolder, initialItems), Saveable {
+
+    override fun update(gameItemsHolder: GameItemsHolder) {
+        // no-op
+    }
+
+    override fun getSaveData(): SaveDataDto.ChestSaveData {
+        return SaveDataDto.ChestSaveData(
+            version = SAVE_DATA_VERSION,
+            size = size,
+            items = items.map(InventoryItem::getSaveData)
+        )
+    }
+
+    companion object {
+        private const val SAVE_DATA_VERSION = 1
+        private const val SIZE = 27
+
+        fun fromSaveData(saveData: SaveDataDto.ChestSaveData, gameItemsHolder: GameItemsHolder): Chest {
+            saveData.verifyVersion(SAVE_DATA_VERSION)
+
+            return Chest(
+                gameItemsHolder = gameItemsHolder,
+                initialItems = saveData.items.map { item -> InventoryItem.fromSaveData(item, gameItemsHolder) }
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/Container.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/Container.kt
new file mode 100644 (file)
index 0000000..3938ef7
--- /dev/null
@@ -0,0 +1,40 @@
+package ru.deadsoftware.cavedroid.game.objects.container
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.misc.Saveable
+
+abstract class Container @JvmOverloads constructor(
+    val size: Int,
+    gameItemsHolder: GameItemsHolder,
+    initialItems: List<InventoryItem>? = null,
+) : Saveable {
+
+    private val _items = Array(size) { index ->
+        initialItems?.getOrNull(index) ?: gameItemsHolder.fallbackItem.toInventoryItem()
+    }
+
+    val items get() = _items.asList() as MutableList<InventoryItem>
+
+    open fun initItems(gameItemsHolder: GameItemsHolder) {
+        _items.forEach { it.init(gameItemsHolder) }
+    }
+
+    abstract fun update(gameItemsHolder: GameItemsHolder)
+
+    abstract override fun getSaveData(): SaveDataDto.ContainerSaveDataDto
+
+    companion object {
+        fun fromSaveData(saveData: SaveDataDto.ContainerSaveDataDto, gameItemsHolder: GameItemsHolder): Container {
+            return when (saveData) {
+                is SaveDataDto.FurnaceSaveData -> Furnace.fromSaveData(saveData, gameItemsHolder)
+                is SaveDataDto.ChestSaveData -> Chest.fromSaveData(saveData, gameItemsHolder)
+
+                is SaveDataDto.InventorySaveData -> {
+                    throw IllegalArgumentException("Cannot load Container from InventorySaveData")
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/ContainerController.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/ContainerController.kt
new file mode 100644 (file)
index 0000000..d2159f1
--- /dev/null
@@ -0,0 +1,112 @@
+package ru.deadsoftware.cavedroid.game.objects.container
+
+import com.badlogic.gdx.Gdx
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem.Companion.isNoneOrNull
+import ru.deadsoftware.cavedroid.game.objects.drop.DropController
+import ru.deadsoftware.cavedroid.misc.Saveable
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+class ContainerController @Inject constructor(
+    _dropController: DropController,
+    _gameItemsHolder: GameItemsHolder
+) : Saveable {
+
+    @Suppress("UNNECESSARY_LATEINIT")
+    private lateinit var dropController: DropController
+
+    @Suppress("UNNECESSARY_LATEINIT")
+    private lateinit var gameItemsHolder: GameItemsHolder
+
+    private val containerMap = mutableMapOf<String, Container>()
+
+    val size get() = containerMap.size
+
+    init {
+        dropController = _dropController
+        gameItemsHolder = _gameItemsHolder
+    }
+
+    fun init(dropController: DropController, gameItemsHolder: GameItemsHolder) {
+        this.dropController = dropController
+        this.gameItemsHolder = gameItemsHolder
+        containerMap.forEach { (_, container) -> container.initItems(gameItemsHolder) }
+    }
+
+    fun getContainer(x: Int, y: Int, z: Int): Container? {
+        return containerMap["$x;$y;$z"]
+    }
+
+    fun addContainer(x: Int, y: Int, z: Int, clazz: Class<out Block.Container>) {
+        val container = when (clazz) {
+            Block.Furnace::class.java -> Furnace(gameItemsHolder)
+            Block.Chest::class.java -> Chest(gameItemsHolder)
+            else -> {
+                Gdx.app.error(TAG, "Unknown container class $clazz")
+                return
+            }
+        }
+        containerMap["$x;$y;$z"] = container
+    }
+
+    @JvmOverloads
+    fun destroyContainer(x: Int, y: Int, z: Int, dropItems: Boolean = true) {
+        val container = containerMap.remove("$x;$y;$z") ?: return
+
+        if (!dropItems) {
+            return
+        }
+
+        val xPx = (x + .5f).px
+        val yPx = (y + .5f).px
+
+        container.items.forEach { item ->
+            if (!item.isNoneOrNull()) {
+                dropController.addDrop(xPx, yPx, item)
+            }
+        }
+    }
+
+    fun update() {
+        containerMap.forEach { (_, container) ->
+            container.update(gameItemsHolder)
+        }
+    }
+
+    override fun getSaveData(): SaveDataDto.ContainerControllerSaveData {
+        return SaveDataDto.ContainerControllerSaveData(
+            version = SAVE_DATA_VERSION,
+            containerMap = containerMap.mapValues { (_, container) -> container.getSaveData() },
+        )
+    }
+
+    companion object {
+        private const val SAVE_DATA_VERSION = 1
+        private const val TAG = "ContainerController"
+
+        fun fromSaveData(
+            saveData: SaveDataDto.ContainerControllerSaveData,
+            dropController: DropController,
+            gameItemsHolder: GameItemsHolder
+        ): ContainerController {
+            saveData.verifyVersion(SAVE_DATA_VERSION)
+
+            return ContainerController(
+                dropController,
+                gameItemsHolder
+            ).apply {
+                containerMap.putAll(
+                    saveData.containerMap.mapValues { (_, containerSaveData) ->
+                        Container.fromSaveData(containerSaveData, gameItemsHolder)
+                    }
+                )
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/Furnace.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/container/Furnace.kt
new file mode 100644 (file)
index 0000000..237006d
--- /dev/null
@@ -0,0 +1,181 @@
+package ru.deadsoftware.cavedroid.game.objects.container
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.math.MathUtils
+import com.badlogic.gdx.utils.TimeUtils
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem.Companion.isNoneOrNull
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.misc.Saveable
+
+class Furnace @JvmOverloads constructor(
+    gameItemsHolder: GameItemsHolder,
+    initialItems: List<InventoryItem>? = null
+) : Container(SIZE, gameItemsHolder, initialItems), Saveable {
+
+    var fuel: InventoryItem
+        get() = items[FUEL_INDEX]
+        set(value) {
+            items[FUEL_INDEX] = value
+        }
+
+    var input: InventoryItem
+        get() = items[INPUT_INDEX]
+        set(value) {
+            items[INPUT_INDEX] = value
+        }
+
+    var result: InventoryItem
+        get() = items[RESULT_INDEX]
+        set(value) {
+            items[RESULT_INDEX] = value
+        }
+
+    val isActive: Boolean get() = currentFuel != null
+
+    var currentFuel: Item? = null
+        set(value) {
+            currentFuelKey = value?.params?.key
+            field = value
+        }
+
+    var currentFuelKey: String? = null
+
+    private var startBurnTimeMs = 0L
+    private var smeltStarTimeMs = 0L
+
+    var burnProgress = 0f
+        private set(value) {
+            field = MathUtils.clamp(value, 0f, 1f)
+        }
+    var smeltProgress = 0f
+        private set(value) {
+            field = MathUtils.clamp(value, 0f, 1f)
+        }
+
+    fun init(gameItemsHolder: GameItemsHolder) {
+        currentFuel = currentFuelKey?.let { gameItemsHolder.getItem(it) }
+        items.forEach { it.init(gameItemsHolder) }
+    }
+
+    fun canSmelt(): Boolean {
+        return (result.isNoneOrNull() || (result.item.params.key == input.item.params.smeltProductKey)) &&
+                !input.isNoneOrNull() && input.item.params.smeltProductKey != null &&
+                (!fuel.isNoneOrNull() || burnProgress > 0f)
+    }
+
+    private fun startBurning(gameItemsHolder: GameItemsHolder) {
+        requireNotNull(fuel.item.params.burningTimeMs) { "Cant start burning without fuel" }
+        currentFuel = fuel.item
+        fuel.subtract()
+        if (fuel.amount <= 0) {
+            fuel = gameItemsHolder.fallbackItem.toInventoryItem()
+        }
+        startBurnTimeMs = TimeUtils.millis()
+        burnProgress = 0f
+    }
+
+    override fun update(gameItemsHolder: GameItemsHolder) {
+        if (currentFuel?.isNone() == true) {
+            currentFuel = null
+        }
+
+        currentFuel?.let { curFuel ->
+            val burningTimeMs = curFuel.params.burningTimeMs ?: run {
+                Gdx.app.error(TAG, "Burning item has no burning time. Item : ${curFuel.params.key}")
+                return
+            }
+
+            if (TimeUtils.timeSinceMillis(startBurnTimeMs).toDouble() / burningTimeMs >= 0.01) {
+                burnProgress += 0.01f
+                startBurnTimeMs = TimeUtils.millis()
+            }
+
+        }
+
+        if (currentFuel?.isNone() == false && burnProgress >= 1f) {
+            if (canSmelt()) {
+                startBurning(gameItemsHolder)
+            } else {
+                currentFuel = null
+                burnProgress = 0f
+                smeltProgress = 0f
+            }
+        }
+
+        if (!canSmelt()) {
+            return
+        }
+        if (currentFuel == null && !fuel.isNoneOrNull()) {
+            startBurning(gameItemsHolder)
+            smeltStarTimeMs = startBurnTimeMs
+            smeltProgress = 0f
+        }
+
+        if ((TimeUtils.timeSinceMillis(smeltStarTimeMs).toDouble() / SMELTING_TIME_MS) >= 0.01) {
+            smeltProgress += 0.01f
+            smeltStarTimeMs = TimeUtils.millis()
+        }
+
+        if (isActive && smeltProgress >= 1f) {
+            val productKey = requireNotNull(input.item.params.smeltProductKey)
+            val res = gameItemsHolder.getItem(productKey)
+            if (result.isNoneOrNull()) {
+                result = res.toInventoryItem()
+            } else {
+                result.add()
+            }
+            input.subtract()
+            if (input.amount <= 0) {
+                input = gameItemsHolder.fallbackItem.toInventoryItem()
+            }
+            smeltStarTimeMs = TimeUtils.millis()
+            smeltProgress = 0f
+        }
+    }
+
+    override fun getSaveData(): SaveDataDto.FurnaceSaveData {
+        return SaveDataDto.FurnaceSaveData(
+            version = SAVE_DATA_VERSION,
+            size = size,
+            currentFuelItemKey = currentFuelKey,
+            items = items.map(InventoryItem::getSaveData),
+            startBurnTimeMs = startBurnTimeMs,
+            startSmeltTimeMs = smeltStarTimeMs,
+            burnProgress = burnProgress,
+            smeltProgress = smeltProgress,
+        )
+    }
+
+    companion object {
+        private const val SAVE_DATA_VERSION = 1
+        private const val SIZE = 3
+        private const val TAG = "Furnace"
+
+        const val FUEL_INDEX = 0
+        const val INPUT_INDEX = 1
+        const val RESULT_INDEX = 2
+
+        const val SMELTING_TIME_MS = 10000L
+
+        fun fromSaveData(saveData: SaveDataDto.FurnaceSaveData, gameItemsHolder: GameItemsHolder): Furnace {
+            saveData.verifyVersion(SAVE_DATA_VERSION)
+
+            return Furnace(
+                gameItemsHolder = gameItemsHolder,
+                initialItems = saveData.items.map { item ->
+                    InventoryItem.fromSaveData(item, gameItemsHolder)
+                }
+            ).apply {
+                currentFuelKey = saveData.currentFuelItemKey
+                startBurnTimeMs = saveData.startSmeltTimeMs
+                smeltStarTimeMs = saveData.startSmeltTimeMs
+                burnProgress = saveData.burnProgress
+                smeltProgress = saveData.smeltProgress
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/drop/Drop.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/objects/drop/Drop.kt
new file mode 100644 (file)
index 0000000..2b4f740
--- /dev/null
@@ -0,0 +1,104 @@
+package ru.deadsoftware.cavedroid.game.objects.drop
+
+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.dto.SaveDataDto
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.misc.Saveable
+
+class Drop @JvmOverloads constructor(
+    x: Float,
+    y: Float,
+    _item: Item,
+    _amount: Int = 1,
+) : Rectangle(x, y, DROP_SIZE, DROP_SIZE), Saveable {
+
+    var amount: Int = _amount
+        private set
+
+    val itemKey = _item.params.key
+    val velocity = getInitialVelocity()
+    var pickedUp = false
+
+    @Suppress("UNNECESSARY_LATEINIT")
+    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)
+    }
+
+    @JvmOverloads
+    fun subtract(count: Int = 1) {
+        if (count < 0) {
+            throw IllegalArgumentException("Can't subtract negative amount")
+        }
+
+        amount -= count
+    }
+
+    private fun getMagnetArea(): Rectangle {
+        return Rectangle(
+            /* x = */ x - MAGNET_DISTANCE,
+            /* y = */ y - MAGNET_DISTANCE,
+            /* width = */ width + MAGNET_DISTANCE * 2,
+            /* height = */ height + MAGNET_DISTANCE * 2,
+        )
+    }
+
+    override fun getSaveData(): SaveDataDto.DropSaveData {
+        return SaveDataDto.DropSaveData(
+            version = SAVE_DATA_VERSION,
+            x = x,
+            y = y,
+            width = width,
+            height = height,
+            velocityX = velocity.x,
+            velocityY = velocity.y,
+            itemKey = itemKey,
+            amount = amount,
+            pickedUp = pickedUp
+        )
+    }
+
+    companion object {
+        private const val SAVE_DATA_VERSION = 1
+
+        private const val MAGNET_DISTANCE = 8f
+
+        const val MAGNET_VELOCITY = 256f
+        const val DROP_SIZE = 8f
+
+        private fun getInitialVelocity(): Vector2 = Vector2(0f, -1f)
+
+        fun fromSaveData(saveData: SaveDataDto.DropSaveData, gameItemsHolder: GameItemsHolder): Drop {
+            saveData.verifyVersion(SAVE_DATA_VERSION)
+
+            return Drop(
+                x = saveData.x,
+                y = saveData.y,
+                _item = gameItemsHolder.getItem(saveData.itemKey),
+                _amount = saveData.amount,
+            ).apply {
+                velocity.x = saveData.velocityX
+                velocity.y = saveData.velocityY
+                pickedUp = saveData.pickedUp
+            }
+        }
+    }
+}
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt
new file mode 100644 (file)
index 0000000..0d34540
--- /dev/null
@@ -0,0 +1,55 @@
+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.annotations.multibinding.BindRenderer
+import ru.deadsoftware.cavedroid.misc.utils.forEachBlockInArea
+import javax.inject.Inject
+
+@GameScope
+@BindRenderer
+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/main/kotlin/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt
new file mode 100644 (file)
index 0000000..68af67b
--- /dev/null
@@ -0,0 +1,116 @@
+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.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
+            if (backgroundBlock is Block.Furnace) {
+                backgroundBlock.draw(spriteBatch, drawX, drawY, gameWorld.getBackgroundFurnace(x, y)?.isActive ?: false)
+            } else {
+                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
+
+            if (foregroundBlock is Block.Furnace) {
+                foregroundBlock.draw(spriteBatch, drawX, drawY, gameWorld.getForegroundFurnace(x, y)?.isActive ?: false)
+            } else {
+                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/main/kotlin/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt
new file mode 100644 (file)
index 0000000..0fa2756
--- /dev/null
@@ -0,0 +1,115 @@
+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.annotations.multibinding.BindRenderer
+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
+@BindRenderer
+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/main/kotlin/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt
new file mode 100644 (file)
index 0000000..d1fc65a
--- /dev/null
@@ -0,0 +1,42 @@
+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.drop.DropController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindRenderer
+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
+@BindRenderer
+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/main/kotlin/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt
new file mode 100644 (file)
index 0000000..8353b0b
--- /dev/null
@@ -0,0 +1,34 @@
+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.annotations.multibinding.BindRenderer
+import ru.deadsoftware.cavedroid.misc.utils.forEachBlockInArea
+import javax.inject.Inject
+
+@GameScope
+@BindRenderer
+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/main/kotlin/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt
new file mode 100644 (file)
index 0000000..b6b22ae
--- /dev/null
@@ -0,0 +1,149 @@
+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.Player
+import ru.deadsoftware.cavedroid.game.mobs.player.Player.ControlMode
+import ru.deadsoftware.cavedroid.game.ui.TooltipManager
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindRenderer
+import ru.deadsoftware.cavedroid.misc.utils.drawString
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+@BindRenderer
+class HudRenderer @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+    private val tooltipManager: TooltipManager,
+) : 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 emptyHeartTexture get() = requireNotNull(Assets.textureRegions[EMPTY_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 halfHeart = halfHeartTexture
+        val emptyHeart = emptyHeartTexture
+
+        val totalHearts = Player.MAX_HEALTH / 2
+        val wholeHearts = player.health / 2
+
+        for (i in 0..< totalHearts) {
+            if (i < wholeHearts) {
+                spriteBatch.draw(wholeHeart, x + i * wholeHeart.regionWidth, y)
+            } else if (i == wholeHearts && player.health % 2 == 1) {
+                spriteBatch.draw(halfHeart, x + i * wholeHeart.regionWidth, y)
+            } else {
+                spriteBatch.draw(emptyHeart, x + i * wholeHeart.regionWidth, y)
+            }
+        }
+
+
+
+
+    }
+
+    private fun drawHotbarItems(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer,  hotbarX: Float) {
+        mobsController.player.inventory.items.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.inventory.activeSlot * (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)
+
+        val tooltip = tooltipManager.currentHotbarTooltip
+        if (tooltip.isNotBlank()) {
+            spriteBatch.drawString(
+                str = tooltip,
+                x = viewport.width / 2 - Assets.getStringWidth(tooltip) / 2,
+                y = hotbarTexture.regionHeight.toFloat()
+            )
+        }
+    }
+
+    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 const val EMPTY_HEART_KEY = "heart_empty"
+
+        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/main/kotlin/ru/deadsoftware/cavedroid/game/render/IGameRenderer.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt
new file mode 100644 (file)
index 0000000..52ac971
--- /dev/null
@@ -0,0 +1,47 @@
+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.annotations.multibinding.BindRenderer
+import ru.deadsoftware.cavedroid.misc.utils.cycledInsideWorld
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+@BindRenderer
+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/main/kotlin/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt
new file mode 100644 (file)
index 0000000..f2198aa
--- /dev/null
@@ -0,0 +1,85 @@
+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.input.Joystick
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.player.Player.ControlMode
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindRenderer
+import ru.deadsoftware.cavedroid.misc.utils.ArrayMapExtensions.component1
+import ru.deadsoftware.cavedroid.misc.utils.ArrayMapExtensions.component2
+import ru.deadsoftware.cavedroid.misc.utils.drawSprite
+import javax.inject.Inject
+
+@GameScope
+@BindRenderer
+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]
+
+    private fun drawJoystick(spriteBatch: SpriteBatch) {
+        val joystick = mainConfig.joystick?.takeIf { it.active } ?: return
+
+        spriteBatch.drawSprite(
+            sprite = Assets.joyBackground,
+            x = joystick.centerX - Joystick.RADIUS,
+            y = joystick.centerY - Joystick.RADIUS,
+            width = Joystick.SIZE,
+            height = Joystick.SIZE
+        )
+
+        spriteBatch.drawSprite(
+            sprite = Assets.joyStick,
+            x = joystick.activeX - Joystick.STICK_SIZE / 2,
+            y = joystick.activeY - Joystick.STICK_SIZE / 2,
+            width = Joystick.STICK_SIZE,
+            height = Joystick.STICK_SIZE
+        )
+    }
+
+    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)
+        }
+
+        drawJoystick(spriteBatch)
+    }
+
+    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/main/kotlin/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt
new file mode 100644 (file)
index 0000000..945bb1e
--- /dev/null
@@ -0,0 +1,44 @@
+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.*
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindRenderer
+import javax.inject.Inject
+
+@GameScope
+@BindRenderer
+class WindowsRenderer @Inject constructor(
+    private val creativeWindowRenderer: CreativeWindowRenderer,
+    private val survivalWindowRenderer: SurvivalWindowRenderer,
+    private val craftingWindowRenderer: CraftingWindowRenderer,
+    private val gameWindowsManager: GameWindowsManager,
+    private val furnaceWindowRenderer: FurnaceWindowRenderer,
+    private val chestWindowRenderer: ChestWindowRenderer,
+) : 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.FURNACE -> furnaceWindowRenderer.draw(spriteBatch, shapeRenderer, viewport, delta)
+            GameUiWindow.CHEST -> chestWindowRenderer.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/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/AbstractWindowRenderer.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/ChestWindowRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/ChestWindowRenderer.kt
new file mode 100644 (file)
index 0000000..78178bc
--- /dev/null
@@ -0,0 +1,93 @@
+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.ui.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.ui.windows.inventory.ChestInventoryWindow
+import ru.deadsoftware.cavedroid.game.ui.windows.inventory.SurvivalInventoryWindow
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+import kotlin.math.atan
+
+@GameScope
+class ChestWindowRenderer @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 chestWindowTexture get() = requireNotNull(Assets.textureRegions[CHEST_WINDOW_KEY])
+    
+    
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        val windowTexture = chestWindowTexture
+        val window = gameWindowsManager.currentWindow as ChestInventoryWindow
+
+        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.Chest.contentsMarginLeft,
+            gridY = windowY + GameWindowsConfigs.Chest.contentsMarginTop,
+            items = window.chest.items,
+            itemsInRow = GameWindowsConfigs.Chest.itemsInRow,
+            cellWidth = GameWindowsConfigs.Chest.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Chest.itemsGridRowHeight,
+        )
+        
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            gridX = windowX + GameWindowsConfigs.Chest.itemsGridMarginLeft,
+            gridY = windowY + GameWindowsConfigs.Chest.itemsGridMarginTop,
+            items = mobsController.player.inventory.items.asSequence()
+                .drop(GameWindowsConfigs.Chest.hotbarCells)
+                .take(GameWindowsConfigs.Chest.itemsInCol * GameWindowsConfigs.Chest.itemsInRow)
+                .asIterable(),
+            itemsInRow = GameWindowsConfigs.Chest.itemsInRow,
+            cellWidth = GameWindowsConfigs.Chest.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Chest.itemsGridRowHeight,
+        )
+
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            gridX = windowX + GameWindowsConfigs.Chest.itemsGridMarginLeft,
+            gridY = windowY + windowTexture.regionHeight - GameWindowsConfigs.Chest.hotbarOffsetFromBottom,
+            items = mobsController.player.inventory.items.asSequence()
+                .take(GameWindowsConfigs.Chest.hotbarCells)
+                .asIterable(),
+            itemsInRow = GameWindowsConfigs.Chest.hotbarCells,
+            cellWidth = GameWindowsConfigs.Chest.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Chest.itemsGridRowHeight,
+        )
+
+        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 CHEST_WINDOW_KEY = "chest"
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/CraftingWindowRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/CraftingWindowRenderer.kt
new file mode 100644 (file)
index 0000000..6772282
--- /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.ui.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.ui.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.items.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.items.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/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt
new file mode 100644 (file)
index 0000000..6bfac42
--- /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.ui.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.ui.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.items.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/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/FurnaceWindowRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/FurnaceWindowRenderer.kt
new file mode 100644 (file)
index 0000000..3037009
--- /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.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.ui.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.ui.windows.inventory.FurnaceInventoryWindow
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.utils.drawSprite
+import ru.deadsoftware.cavedroid.misc.utils.withScissors
+import javax.inject.Inject
+
+@GameScope
+class FurnaceWindowRenderer @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 furnaceWindowTexture get() = requireNotNull(Assets.textureRegions[FURNACE_WINDOW_KEY])
+
+    
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        val windowTexture = furnaceWindowTexture
+        
+        val window = gameWindowsManager.currentWindow as FurnaceInventoryWindow
+
+        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.Furnace.itemsGridMarginLeft,
+            gridY = windowY + GameWindowsConfigs.Furnace.itemsGridMarginTop,
+            items = mobsController.player.inventory.items.asSequence()
+                .drop(GameWindowsConfigs.Furnace.hotbarCells)
+                .take(GameWindowsConfigs.Furnace.itemsInCol * GameWindowsConfigs.Furnace.itemsInRow)
+                .asIterable(),
+            itemsInRow = GameWindowsConfigs.Furnace.itemsInRow,
+            cellWidth = GameWindowsConfigs.Furnace.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Furnace.itemsGridRowHeight,
+        )
+
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            gridX = windowX + GameWindowsConfigs.Furnace.itemsGridMarginLeft,
+            gridY = windowY + windowTexture.regionHeight - GameWindowsConfigs.Furnace.hotbarOffsetFromBottom,
+            items = mobsController.player.inventory.items.asSequence()
+                .take(GameWindowsConfigs.Furnace.hotbarCells)
+                .asIterable(),
+            itemsInRow = GameWindowsConfigs.Furnace.hotbarCells,
+            cellWidth = GameWindowsConfigs.Furnace.itemsGridColWidth,
+            cellHeight = GameWindowsConfigs.Furnace.itemsGridRowHeight,
+        )
+
+        window.furnace.fuel?.draw(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            x = windowX + GameWindowsConfigs.Furnace.smeltFuelMarginLeft,
+            y = windowY + GameWindowsConfigs.Furnace.smeltFuelMarginTop
+        )
+
+        window.furnace.input?.draw(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            x = windowX + GameWindowsConfigs.Furnace.smeltInputMarginLeft,
+            y = windowY + GameWindowsConfigs.Furnace.smeltInputMarginTop
+        )
+
+        window.furnace.result?.draw(
+            spriteBatch = spriteBatch,
+            shapeRenderer = shapeRenderer,
+            x = windowX + GameWindowsConfigs.Furnace.smeltResultOffsetX,
+            y = windowY + GameWindowsConfigs.Furnace.smeltResultOffsetY
+        )
+
+        if (window.furnace.isActive) {
+            val burn = GameWindowsConfigs.Furnace.fuelBurnHeight * window.furnace.burnProgress
+
+            spriteBatch.withScissors(
+                mainConfig = mainConfig,
+                x = windowX + GameWindowsConfigs.Furnace.fuelBurnMarginLeft,
+                y = windowY + GameWindowsConfigs.Furnace.fuelBurnMarginTop + burn,
+                width = Assets.furnaceBurn.width,
+                height = GameWindowsConfigs.Furnace.fuelBurnHeight,
+            ) {
+                spriteBatch.drawSprite(
+                    sprite = Assets.furnaceBurn,
+                    x = windowX + GameWindowsConfigs.Furnace.fuelBurnMarginLeft,
+                    y = windowY + GameWindowsConfigs.Furnace.fuelBurnMarginTop
+                )
+            }
+
+            if (window.furnace.canSmelt()) {
+                val progress = GameWindowsConfigs.Furnace.progressWidth * window.furnace.smeltProgress
+
+                spriteBatch.withScissors(
+                    mainConfig = mainConfig,
+                    x = windowX + GameWindowsConfigs.Furnace.progressMarginLeft,
+                    y = windowY + GameWindowsConfigs.Furnace.progressMarginTop,
+                    width = progress,
+                    height = Assets.furnaceProgress.height
+                ) {
+                    spriteBatch.drawSprite(
+                        sprite = Assets.furnaceProgress,
+                        x = windowX + GameWindowsConfigs.Furnace.progressMarginLeft,
+                        y = windowY + GameWindowsConfigs.Furnace.progressMarginTop,
+                    )
+                }
+            }
+        }
+
+        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 FURNACE_WINDOW_KEY = "furnace"
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/SurvivalWindowRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/render/windows/SurvivalWindowRenderer.kt
new file mode 100644 (file)
index 0000000..7ee3b27
--- /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.ui.windows.GameWindowsConfigs
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.ui.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.items.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.items.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/main/kotlin/ru/deadsoftware/cavedroid/game/save/GameSaveData.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/save/GameSaveData.kt
new file mode 100644 (file)
index 0000000..e0d64e7
--- /dev/null
@@ -0,0 +1,55 @@
+package ru.deadsoftware.cavedroid.game.save
+
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.objects.container.ContainerController
+import ru.deadsoftware.cavedroid.game.objects.drop.DropController
+
+class GameSaveData(
+    private var mobsController: MobsController?,
+    private var dropController: DropController?,
+    private var containerController: ContainerController?,
+    private var foreMap: Array<Array<Block>>?,
+    private var backMap: Array<Array<Block>>?
+) {
+
+    fun retrieveMobsController(): MobsController {
+        val value = requireNotNull(mobsController)
+        mobsController = null
+        return value
+    }
+
+    fun retrieveDropController(): DropController {
+        val value = requireNotNull(dropController)
+        dropController = null
+        return value
+    }
+
+    fun retrieveContainerController(): ContainerController {
+        val value = requireNotNull(containerController)
+        containerController = null
+        return value
+    }
+
+    fun retrieveForeMap(): Array<Array<Block>> {
+        val value = requireNotNull(foreMap)
+        foreMap = null
+        return value
+    }
+
+    fun retrieveBackMap(): Array<Array<Block>> {
+        val value = requireNotNull(backMap)
+        backMap = null
+        return value
+    }
+
+    fun isEmpty(): Boolean {
+        return mobsController == null &&
+                dropController == null &&
+                containerController == null &&
+                foreMap == null &&
+                backMap == null
+    }
+
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/save/GameSaveLoader.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/save/GameSaveLoader.kt
new file mode 100644 (file)
index 0000000..bd8c3d6
--- /dev/null
@@ -0,0 +1,255 @@
+package ru.deadsoftware.cavedroid.game.save
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.files.FileHandle
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.decodeFromByteArray
+import kotlinx.serialization.encodeToByteArray
+import kotlinx.serialization.protobuf.ProtoBuf
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto
+import ru.deadsoftware.cavedroid.game.objects.container.ContainerController
+import ru.deadsoftware.cavedroid.game.objects.drop.DropController
+import ru.deadsoftware.cavedroid.game.ui.TooltipManager
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import java.nio.ByteBuffer
+import java.util.zip.GZIPInputStream
+import java.util.zip.GZIPOutputStream
+
+@OptIn(ExperimentalSerializationApi::class)
+object GameSaveLoader {
+
+    private const val MAP_SAVE_VERSION: UByte = 2u
+
+    private const val SAVES_DIR = "/saves"
+    private const val DROP_FILE = "/drop.dat"
+    private const val MOBS_FILE = "/mobs.dat"
+    private const val CONTAINERS_FILE = "/containers.dat"
+    private const val DICT_FILE = "/dict"
+    private const val FOREMAP_FILE = "/foremap.dat.gz"
+    private const val BACKMAP_FILE = "/backmap.dat.gz"
+
+    private fun Int.toByteArray(): ByteArray {
+        return ByteBuffer.allocate(Int.SIZE_BYTES)
+            .putInt(this)
+            .array()
+    }
+
+    private fun Short.toByteArray(): ByteArray {
+        return ByteBuffer.allocate(Short.SIZE_BYTES)
+            .putShort(this)
+            .array()
+    }
+
+    private fun buildBlocksDictionary(
+        foreMap: Array<Array<Block>>,
+        backMap: Array<Array<Block>>
+    ): Map<String, Int> {
+        val maps = sequenceOf(foreMap.asSequence(), backMap.asSequence())
+
+        return maps.flatten()
+            .flatMap(Array<Block>::asSequence)
+            .toSet()
+            .mapIndexed { index, block -> block.params.key to index }
+            .toMap()
+    }
+
+    private fun saveDict(file: FileHandle, dict: Map<String, Int>) {
+        val result = dict.asSequence()
+            .sortedBy { it.value }
+            .joinToString(separator = "\n") { it.key }
+            .encodeToByteArray()
+
+        file.writeBytes(result, false)
+    }
+
+    private fun compressMap(map: Array<Array<Block>>, dict: Map<String, Int>): ByteArray {
+        if (dict.size > 0xff) {
+            throw IllegalArgumentException("Cannot save this map as bytes")
+        }
+
+        val width = map.size
+        val height = map[0].size
+
+        val blocks = sequence {
+            for (y in 0 ..< height) {
+                for (x in 0 ..< width) {
+                    yield(map[x][y])
+                }
+            }
+        }
+
+        val result = sequence {
+            var run = 0
+            var runValue: UByte? = null
+
+            yield(MAP_SAVE_VERSION.toByte())
+            width.toByteArray().forEach { yield(it) }
+            height.toByteArray().forEach { yield(it) }
+
+            blocks.forEach { block ->
+                val key = block.params.key
+
+                val blockId = dict[key]?.toUByte()
+                    ?: throw IllegalArgumentException("Dictionary does not contain key $key")
+
+                if (blockId != runValue || run == Int.MAX_VALUE) {
+                    if (run > 0 && runValue != null) {
+                        run.toByteArray().forEach { yield(it) }
+                        yield(runValue!!.toByte())
+                    }
+                    run = 1
+                    runValue = blockId
+                } else {
+                    run++
+                }
+            }
+
+            run.toByteArray().forEach { yield(it) }
+            yield(runValue!!.toByte())
+        }
+
+        return result.toList().toByteArray()
+    }
+
+    private fun decompressMap(
+        bytes: ByteArray,
+        dict: List<String>,
+        gameItemsHolder: GameItemsHolder
+    ): Array<Array<Block>> {
+        val version = bytes.first().toUByte()
+        require(version == MAP_SAVE_VERSION)
+
+        val width = ByteBuffer.wrap(bytes, 1, Int.SIZE_BYTES).getInt()
+        val height = ByteBuffer.wrap(bytes, 1 + Int.SIZE_BYTES, Int.SIZE_BYTES).getInt()
+
+        val blocks = buildList {
+            for (i in 1 + (Int.SIZE_BYTES shl 1) .. bytes.lastIndex step Int.SIZE_BYTES + 1) {
+                val run = ByteBuffer.wrap(bytes, i, Int.SIZE_BYTES).getInt()
+                val blockId = bytes[i + Int.SIZE_BYTES].toUByte().toInt()
+
+                for (j in 0 ..< run) {
+                    add(gameItemsHolder.getBlock(dict[blockId]))
+                }
+            }
+        }
+
+        return Array(width) { x ->
+            Array(height) { y ->
+                blocks[x + y * width]
+            }
+        }
+    }
+
+    private fun loadMap(
+        gameItemsHolder: GameItemsHolder,
+        savesPath: String
+    ): Pair<Array<Array<Block>>, Array<Array<Block>>> {
+        val dict = Gdx.files.absolute("$savesPath$DICT_FILE").readString().split("\n")
+
+        val foreMap: Array<Array<Block>>
+        with(GZIPInputStream(Gdx.files.absolute("$savesPath$FOREMAP_FILE").read())) {
+            foreMap = decompressMap(readBytes(), dict, gameItemsHolder)
+            close()
+        }
+
+        val backMap: Array<Array<Block>>
+        with(GZIPInputStream(Gdx.files.absolute("$savesPath$BACKMAP_FILE").read())) {
+            backMap = decompressMap(readBytes(), dict, gameItemsHolder)
+            close()
+        }
+
+        return foreMap to backMap
+    }
+
+    private fun saveMap(gameWorld: GameWorld, savesPath: String) {
+        val fullForeMap = gameWorld.fullForeMap
+        val fullBackMap = gameWorld.fullBackMap
+
+        val dict = buildBlocksDictionary(fullForeMap, fullBackMap)
+
+        saveDict(Gdx.files.absolute("$savesPath$DICT_FILE"), dict)
+
+        with(GZIPOutputStream(Gdx.files.absolute("$savesPath$FOREMAP_FILE").write(false))) {
+            write(compressMap(fullForeMap, dict))
+            close()
+        }
+
+        with(GZIPOutputStream(Gdx.files.absolute("$savesPath$BACKMAP_FILE").write(false))) {
+            write(compressMap(fullBackMap, dict))
+            close()
+        }
+    }
+
+    fun load(
+        mainConfig: MainConfig,
+        gameItemsHolder: GameItemsHolder,
+        tooltipManager: TooltipManager
+    ): GameSaveData {
+        val gameFolder = mainConfig.gameFolder
+        val savesPath = "$gameFolder$SAVES_DIR"
+
+        val dropFile = Gdx.files.absolute("$savesPath$DROP_FILE")
+        val mobsFile = Gdx.files.absolute("$savesPath$MOBS_FILE")
+        val containersFile = Gdx.files.absolute("$savesPath$CONTAINERS_FILE")
+
+        val dropBytes = dropFile.readBytes()
+        val mobsBytes = mobsFile.readBytes()
+        val containersBytes = containersFile.readBytes()
+
+        val dropController = ProtoBuf.decodeFromByteArray<SaveDataDto.DropControllerSaveData>(dropBytes)
+            .let { saveData -> DropController.fromSaveData(saveData, gameItemsHolder) }
+        val mobsController = ProtoBuf.decodeFromByteArray<SaveDataDto.MobsControllerSaveData>(mobsBytes)
+            .let { saveData -> MobsController.fromSaveData(saveData, gameItemsHolder, tooltipManager) }
+        val containerController = ProtoBuf.decodeFromByteArray<SaveDataDto.ContainerControllerSaveData>(containersBytes)
+            .let { saveData -> ContainerController.fromSaveData(saveData, dropController, gameItemsHolder) }
+
+        val (foreMap, backMap) = loadMap(gameItemsHolder, savesPath)
+
+        return GameSaveData(mobsController, dropController, containerController, foreMap, backMap)
+    }
+
+    fun save(
+        mainConfig: MainConfig,
+        dropController: DropController,
+        mobsController: MobsController,
+        containerController: ContainerController,
+        gameWorld: GameWorld
+    ) {
+        val gameFolder = mainConfig.gameFolder
+        val savesPath = "$gameFolder$SAVES_DIR"
+
+        Gdx.files.absolute(savesPath).mkdirs()
+
+        val dropFile = Gdx.files.absolute("$savesPath$DROP_FILE")
+        val mobsFile = Gdx.files.absolute("$savesPath$MOBS_FILE")
+        val containersFile = Gdx.files.absolute("$savesPath$CONTAINERS_FILE")
+
+        val dropBytes = ProtoBuf.encodeToByteArray(dropController.getSaveData())
+        val mobsBytes = ProtoBuf.encodeToByteArray(mobsController.getSaveData())
+        val containersBytes = ProtoBuf.encodeToByteArray(containerController.getSaveData())
+
+        dropFile.writeBytes(dropBytes, false)
+        mobsFile.writeBytes(mobsBytes, false)
+        containersFile.writeBytes(containersBytes, false)
+
+        saveMap(gameWorld, savesPath)
+    }
+
+    fun exists(mainConfig: MainConfig): Boolean {
+        val gameFolder = mainConfig.gameFolder
+        val savesPath = "$gameFolder$SAVES_DIR"
+
+        return Gdx.files.absolute("$savesPath$DROP_FILE").exists() &&
+                Gdx.files.absolute("$savesPath$MOBS_FILE").exists() &&
+                Gdx.files.absolute("$savesPath$CONTAINERS_FILE").exists() &&
+                Gdx.files.absolute("$savesPath$DICT_FILE").exists() &&
+                Gdx.files.absolute("$savesPath$FOREMAP_FILE").exists() &&
+                Gdx.files.absolute("$savesPath$BACKMAP_FILE").exists()
+    }
+
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/TooltipManager.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/TooltipManager.kt
new file mode 100644 (file)
index 0000000..a8f5a9b
--- /dev/null
@@ -0,0 +1,41 @@
+package ru.deadsoftware.cavedroid.game.ui
+
+import com.badlogic.gdx.utils.Timer
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import javax.inject.Inject
+
+@GameScope
+class TooltipManager @Inject constructor(
+    private val mainConfig: MainConfig
+) {
+
+    private val resetTask = object : Timer.Task() {
+        override fun run() {
+            currentHotbarTooltip = ""
+        }
+    }
+
+    var currentHotbarTooltip: String = ""
+        private set
+
+    var currentMouseTooltip: String = ""
+        private set
+
+    fun showHotbarTooltip(tooltip: String) {
+        currentHotbarTooltip = tooltip
+        if (resetTask.isScheduled) {
+            resetTask.cancel()
+        }
+        Timer.schedule(resetTask, TOOLTIP_TIME_S)
+    }
+
+    fun showMouseTooltip(tooltip: String) {
+        currentMouseTooltip = tooltip
+    }
+
+    companion object {
+        private const val TOOLTIP_TIME_S = 2f
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsConfigs.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsConfigs.kt
new file mode 100644 (file)
index 0000000..2f1d264
--- /dev/null
@@ -0,0 +1,124 @@
+package ru.deadsoftware.cavedroid.game.ui.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 = 124f
+        const val craftResultOffsetY = 36f
+    }
+
+    data object Furnace {
+        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 smeltInputMarginLeft = 56f
+        const val smeltInputMarginTop = 18f
+
+        const val smeltFuelMarginLeft = 56f
+        const val smeltFuelMarginTop = 54f
+
+        const val smeltResultOffsetX = 116f
+        const val smeltResultOffsetY = 36f
+
+        const val fuelBurnMarginLeft = 56f
+        const val fuelBurnMarginTop = 36f
+        const val fuelBurnHeight = 14f
+
+        const val progressMarginLeft = 79f
+        const val progressMarginTop = 34f
+        const val progressWidth = 24f
+    }
+
+    data object Chest {
+        const val itemsGridMarginLeft = 8f
+        const val itemsGridMarginTop = 86f
+
+        const val itemsGridRowHeight = 18f
+        const val itemsGridColWidth = 18f
+
+        const val hotbarCells = 9
+        const val hotbarOffsetFromBottom = 24f
+
+        const val itemsInRow = 9
+        const val itemsInCol = 5
+
+        const val contentsMarginLeft = 8f
+        const val contentsMarginTop = 18f
+
+        const val contentsInRow = 9
+        const val contentsInCol = 3
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsManager.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsManager.kt
new file mode 100644 (file)
index 0000000..8d32f94
--- /dev/null
@@ -0,0 +1,63 @@
+package ru.deadsoftware.cavedroid.game.ui.windows
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.objects.container.Chest
+import ru.deadsoftware.cavedroid.game.objects.drop.DropController
+import ru.deadsoftware.cavedroid.game.objects.container.Furnace
+import ru.deadsoftware.cavedroid.game.ui.TooltipManager
+import ru.deadsoftware.cavedroid.game.ui.windows.inventory.*
+import javax.inject.Inject
+
+@GameScope
+class GameWindowsManager @Inject constructor(
+    private val tooltipManager: TooltipManager,
+    private val mobsController: MobsController,
+    private val dropController: DropController,
+    private val gameItemsHolder: GameItemsHolder,
+) {
+
+    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()
+        } else {
+            currentWindow = SurvivalInventoryWindow(gameItemsHolder)
+        }
+    }
+
+    fun openFurnace(furnace: Furnace) {
+        currentWindow = FurnaceInventoryWindow(furnace)
+    }
+
+    fun openChest(chest: Chest) {
+        currentWindow = ChestInventoryWindow(chest)
+    }
+
+    fun openCrafting() {
+        currentWindow = CraftingInventoryWindow(gameItemsHolder)
+    }
+
+    fun closeWindow() {
+        (currentWindow as? AbstractInventoryWindowWithCraftGrid)?.let { window ->
+            window.craftingItems.forEach { item ->
+                dropController.addDrop(mobsController.player.x, mobsController.player.y, item)
+            }
+        }
+
+        currentWindow = null
+        tooltipManager.showMouseTooltip("")
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindow.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindow.kt
new file mode 100644 (file)
index 0000000..ed15bac
--- /dev/null
@@ -0,0 +1,81 @@
+package ru.deadsoftware.cavedroid.game.ui.windows.inventory
+
+import com.badlogic.gdx.math.MathUtils
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem.Companion.isNoneOrNull
+
+abstract class AbstractInventoryWindow {
+
+    abstract val type: GameUiWindow
+
+    abstract var selectedItem: InventoryItem?
+
+    var selectItemPointer: Int = -1
+
+    fun onLeftCLick(
+        items: MutableList<InventoryItem>,
+        gameItemsHolder: GameItemsHolder,
+        index: Int,
+        pointer: Int = -1
+    ) {
+        if (selectedItem != null &&
+            selectedItem?.item?.isNone() != true &&
+            pointer >= 0 && selectItemPointer >= 0 &&
+            pointer != selectItemPointer
+        ) {
+            return
+        }
+
+        val clickedItem = items[index]
+
+        selectedItem?.let { selectedItem ->
+            if (!clickedItem.isNoneOrNull() && items[index].item == selectedItem.item &&
+                items[index].amount + selectedItem.amount <= selectedItem.item.params.maxStack
+            ) {
+                items[index].amount += selectedItem.amount
+                this@AbstractInventoryWindow.selectedItem = null
+                selectItemPointer = -1
+                return
+            }
+        }
+
+        val item = items[index]
+        items[index] = selectedItem ?: gameItemsHolder.fallbackItem.toInventoryItem()
+        selectedItem = item
+        selectItemPointer = pointer
+    }
+
+    fun onRightClick(items: MutableList<InventoryItem>, gameItemsHolder: GameItemsHolder, index: Int) {
+        val clickedItem = items[index]
+        val selectedItem = selectedItem
+
+        if (selectedItem.isNoneOrNull() && !clickedItem.isNoneOrNull()) {
+            val half = InventoryItem(clickedItem.item, MathUtils.ceil(clickedItem.amount.toFloat() / 2f))
+            this.selectedItem = half
+            clickedItem.subtract(half.amount)
+            if (clickedItem.amount == 0) {
+                items[index] = gameItemsHolder.fallbackItem.toInventoryItem()
+            }
+            return
+        }
+
+        if (selectedItem == null ||
+            (!clickedItem.isNoneOrNull() && selectedItem.item != clickedItem.item) ||
+            !clickedItem.canBeAdded()) {
+            return
+        }
+
+        val newItem = selectedItem.item.toInventoryItem(
+            (clickedItem.takeIf { !it.item.isNone() }?.amount ?: 0) + 1
+        )
+        items[index] = newItem
+        selectedItem.amount--
+
+        if (selectedItem.amount <= 0) {
+            this@AbstractInventoryWindow.selectedItem = null
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindowWithCraftGrid.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindowWithCraftGrid.kt
new file mode 100644 (file)
index 0000000..066ca37
--- /dev/null
@@ -0,0 +1,24 @@
+package ru.deadsoftware.cavedroid.game.ui.windows.inventory
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+
+abstract class AbstractInventoryWindowWithCraftGrid(
+    gameItemsHolder: GameItemsHolder,
+) : AbstractInventoryWindow() {
+
+    private val _items = Array(10) { gameItemsHolder.fallbackItem.toInventoryItem() }
+
+    val items get() = _items.asList()
+
+    val craftingItems get() = items.subList(0, 9) as MutableList<InventoryItem>
+
+    val craftResultList get() = items.subList(9, 10) as MutableList<InventoryItem>
+
+    var craftResult: InventoryItem
+        get() = craftResultList[0]
+        set(value) {
+            craftResultList[0] = value
+        }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/ChestInventoryWindow.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/ChestInventoryWindow.kt
new file mode 100644 (file)
index 0000000..6f4a4fe
--- /dev/null
@@ -0,0 +1,13 @@
+package ru.deadsoftware.cavedroid.game.ui.windows.inventory
+
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.objects.container.Chest
+
+class ChestInventoryWindow(val chest: Chest) : AbstractInventoryWindow() {
+
+    override val type = GameUiWindow.CHEST
+
+    override var selectedItem: InventoryItem? = null
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CraftingInventoryWindow.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CraftingInventoryWindow.kt
new file mode 100644 (file)
index 0000000..cd7c0ff
--- /dev/null
@@ -0,0 +1,14 @@
+package ru.deadsoftware.cavedroid.game.ui.windows.inventory
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+
+class CraftingInventoryWindow(
+    gameItemsHolder: GameItemsHolder
+) : AbstractInventoryWindowWithCraftGrid(gameItemsHolder) {
+
+    override val type = GameUiWindow.CRAFTING_TABLE
+
+    override var selectedItem: InventoryItem? = null
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CreativeInventoryWindow.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CreativeInventoryWindow.kt
new file mode 100644 (file)
index 0000000..a38fd29
--- /dev/null
@@ -0,0 +1,11 @@
+package ru.deadsoftware.cavedroid.game.ui.windows.inventory
+
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+
+class CreativeInventoryWindow() : AbstractInventoryWindow() {
+
+    override val type = GameUiWindow.CREATIVE_INVENTORY
+
+    override var selectedItem: InventoryItem? = null
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/FurnaceInventoryWindow.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/FurnaceInventoryWindow.kt
new file mode 100644 (file)
index 0000000..42f7038
--- /dev/null
@@ -0,0 +1,15 @@
+package ru.deadsoftware.cavedroid.game.ui.windows.inventory
+
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.objects.container.Furnace
+
+class FurnaceInventoryWindow(
+    val furnace: Furnace,
+) : AbstractInventoryWindow() {
+
+    override val type = GameUiWindow.FURNACE
+
+    override var selectedItem: InventoryItem? = null
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/SurvivalInventoryWindow.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/ui/windows/inventory/SurvivalInventoryWindow.kt
new file mode 100644 (file)
index 0000000..ce250fc
--- /dev/null
@@ -0,0 +1,14 @@
+package ru.deadsoftware.cavedroid.game.ui.windows.inventory
+
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+
+class SurvivalInventoryWindow(
+    gameItemsHolder: GameItemsHolder
+) : AbstractInventoryWindowWithCraftGrid(gameItemsHolder) {
+
+    override val type = GameUiWindow.SURVIVAL_INVENTORY
+
+    override var selectedItem: InventoryItem? = null
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldBlocksLogicControllerTask.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldBlocksLogicControllerTask.kt
new file mode 100644 (file)
index 0000000..e92b9e2
--- /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.run { requiresBlock || isFallable } }
+
+        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/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.kt
new file mode 100644 (file)
index 0000000..d0f4eaa
--- /dev/null
@@ -0,0 +1,189 @@
+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 ru.deadsoftware.cavedroid.misc.utils.bl
+import java.util.PriorityQueue
+import javax.inject.Inject
+import kotlin.math.min
+import kotlin.reflect.KClass
+
+@GameScope
+class GameWorldFluidsLogicControllerTask @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : Timer.Task() {
+
+    private var updateTick: Short = 0;
+
+    private val fluidStatesMap = mutableMapOf<KClass<out Block.Fluid>, List<Block.Fluid>>()
+
+    private val updateQueue = PriorityQueue<UpdateCommand>(16) { c1, c2 ->
+        c1.priority.compareTo(c2.priority)
+    }
+
+    init {
+        val waters = gameItemsHolder.getBlocksByType(Block.Water::class.java)
+            .sortedBy(Block.Water::state)
+        val lavas = gameItemsHolder.getBlocksByType(Block.Lava::class.java)
+            .sortedBy(Block.Lava::state)
+
+        fluidStatesMap[Block.Water::class] = waters
+        fluidStatesMap[Block.Lava::class] = lavas
+    }
+
+    private fun getNextStateBlock(fluid: Block.Fluid): Block.Fluid? {
+        val stateList = fluidStatesMap[fluid::class] ?: return null
+        val currentState = stateList.indexOf(fluid)
+            .takeIf { it >= 0 } ?: return null
+
+        var nextState = currentState + 1
+
+        if (nextState == 1) {
+            nextState++
+        }
+
+        if (nextState < stateList.size) {
+            return stateList[nextState]
+        }
+
+        return null
+    }
+
+    private fun noFluidNearby(x: Int, y: Int): Boolean {
+        val current = gameWorld.getForeMap(x, y)
+
+        if (current !is Block.Fluid) {
+            throw IllegalArgumentException("block at $x;$y is not a fluid")
+        }
+
+        val onTop = gameWorld.getForeMap(x, y - 1)
+        val onLeft = gameWorld.getForeMap(x - 1, y)
+        val onRight = gameWorld.getForeMap(x + 1, y)
+
+        return !onTop.isFluid() &&
+                (onLeft !is Block.Fluid || onLeft.state >= current.state) &&
+                (onRight !is Block.Fluid || onRight.state >= current.state)
+    }
+
+    private fun drainFluid(x: Int, y: Int): Boolean {
+        val fluid = (gameWorld.getForeMap(x, y) as? Block.Fluid)
+            ?: return true
+
+        if (fluid.state > 0) {
+            if (noFluidNearby(x, y)) {
+                val nexState = getNextStateBlock(fluid)
+                if (nexState == null) {
+                    updateQueue.offer(UpdateCommand(-1) { gameWorld.resetForeMap(x, y) })
+                    return true
+                }
+                updateQueue.offer(UpdateCommand(nexState, x, y))
+            }
+        }
+
+        return false
+    }
+
+    private fun fluidCanFlowThere(fluid: Block.Fluid, targetBlock: Block): Boolean {
+        return targetBlock.isNone() ||
+                (!targetBlock.params.hasCollision && !targetBlock.isFluid()) ||
+                (fluid::class == targetBlock::class && fluid.state < (targetBlock as Block.Fluid).state)
+    }
+
+    private fun flowFluidTo(currentFluid: Block.Fluid, x: Int, y: Int, nextStateFluid: Block.Fluid) {
+        val targetBlock = gameWorld.getForeMap(x, y)
+
+        val command = when {
+            fluidCanFlowThere(currentFluid, targetBlock) -> UpdateCommand(nextStateFluid, x, y)
+
+            currentFluid.isWater() && targetBlock is Block.Lava && targetBlock.state > 0 ->
+                UpdateCommand(100) { gameWorld.setForeMap(x, y, gameItemsHolder.getBlock("cobblestone")) }
+
+            currentFluid.isWater() && targetBlock.isLava() ->
+                UpdateCommand(100) { gameWorld.setForeMap(x, y, gameItemsHolder.getBlock("obsidian")) }
+
+            currentFluid.isLava() && targetBlock.isWater() ->
+                UpdateCommand(200) { gameWorld.setForeMap(x, y, gameItemsHolder.getBlock("stone")) }
+
+            else -> null
+        }
+
+        command?.let(updateQueue::offer)
+    }
+
+    private fun flowFluid(x: Int, y: Int) {
+        val fluid = gameWorld.getForeMap(x, y) as Block.Fluid
+        val stateList = fluidStatesMap[fluid::class] ?: return
+
+          if (fluid.state < stateList.lastIndex && gameWorld.getForeMap(x, y + 1).params.hasCollision) {
+              val nextState = getNextStateBlock(fluid) ?: return
+
+              flowFluidTo(fluid, x - 1, y, nextState)
+              flowFluidTo(fluid, x + 1, y, nextState)
+          } else {
+              flowFluidTo(fluid, x, y + 1, stateList[1])
+          }
+    }
+
+    fun updateFluids(x: Int, y: Int) {
+        val block = gameWorld.getForeMap(x, y)
+        if (!block.isFluid() || (block.isLava() && updateTick % 2 == 0)) {
+            return
+        }
+
+        if (drainFluid(x, y)) {
+            return
+        }
+
+        flowFluid(x, y)
+    }
+
+    private fun fluidUpdater() {
+        val midScreen = mobsController.player.x.bl
+
+        for (y in gameWorld.height - 1 downTo 0) {
+            for (x in 0 ..< min(gameWorld.width / 2, 32)) {
+                updateFluids(midScreen + x, y)
+                updateFluids(midScreen - x, y)
+            }
+        }
+
+        while (!updateQueue.isEmpty()) {
+            updateQueue.poll().exec()
+        }
+    }
+
+    override fun run() {
+        if (updateTick < 0xFF) {
+            updateTick++
+        } else {
+            updateTick = 1
+        }
+
+        fluidUpdater()
+    }
+
+    private inner class UpdateCommand(
+        val priority: Int,
+        val command: Runnable
+    ) {
+
+        constructor(block: Block, x: Int, y: Int, priority: Int) :
+                this(priority, Runnable { gameWorld.setForeMap(x, y, block) })
+
+        constructor(fluid: Block.Fluid, x: Int, y: Int) :
+                this(fluid, x, y, ((5 - fluid.state) + 1) * (if (fluid.isLava()) 2 else 1))
+
+        fun exec() = command.run()
+
+    }
+
+    companion object {
+        const val FLUID_UPDATE_INTERVAL_SEC = 0.25f
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt
new file mode 100644 (file)
index 0000000..9562f08
--- /dev/null
@@ -0,0 +1,334 @@
+package ru.deadsoftware.cavedroid.game.world
+
+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.math.min
+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")
+        val snow = gameItemsHolder.getBlock("snow")
+
+        foreMap[x][surfaceHeight] = grass
+        foreMap[x][config.height - 1] = bedrock
+        backMap[x][surfaceHeight] = grass
+        backMap[x][config.height - 1] = bedrock
+
+        if (surfaceHeight - 1 < config.seaLevel) {
+            foreMap[x][surfaceHeight - 1] = snow
+        }
+
+        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) {
+                generateSpruce(x)
+            }
+        }
+    }
+
+    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 generateSpruce(x: Int) {
+        val log = gameItemsHolder.getBlock("log_spruce")
+        val leaves = gameItemsHolder.getBlock("leaves_spruce")
+        val h = heights[x] - 1
+        val treeH = random.nextInt(7, 9)
+        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)) {
+            val y = height
+            foreMap[x1][y] = leaves
+            backMap[x1][y] = leaves
+        }
+
+        for (y in 1..2) {
+            for (x1 in max(0, x - y) .. min(config.width - 1, x + y)) {
+                foreMap[x1][height + 1 + y] = leaves
+                backMap[x1][height + 1 + y] = leaves
+            }
+        }
+
+        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/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldMobDamageControllerTask.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/game/world/GameWorldMobDamageControllerTask.kt
new file mode 100644 (file)
index 0000000..f016c67
--- /dev/null
@@ -0,0 +1,41 @@
+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)
+
+                val damage = max(foregroundBlock.params.damage, backgroundBlock.params.damage)
+                if (damage > 0) {
+                    mob.damage(damage)
+                }
+            }
+        }
+
+
+    }
+
+    companion object {
+        const val ENVIRONMENTAL_MOB_DAMAGE_INTERVAL_SEC = 0.5f
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/menu/objects/BooleanOptionButton.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/menu/objects/BooleanOptionButton.kt
new file mode 100644 (file)
index 0000000..56329d5
--- /dev/null
@@ -0,0 +1,29 @@
+package ru.deadsoftware.cavedroid.menu.objects
+
+import ru.deadsoftware.cavedroid.MainConfig
+
+class BooleanOptionButton(
+    private val mainConfig: MainConfig,
+    private val optionKey: String,
+    private val defaultValue: Boolean,
+    label: String,
+    x: Int,
+    y: Int,
+    type: Int,
+) : Button(
+    label,
+    x,
+    y,
+    type,
+    {
+        val current = (mainConfig.getPreference(optionKey)?.toBooleanStrictOrNull()) ?: defaultValue
+        mainConfig.setPreference(optionKey, (!current).toString())
+    }
+) {
+
+    override fun getLabel(): String {
+        val value = (mainConfig.getPreference(optionKey)?.toBooleanStrictOrNull()) ?: defaultValue
+        return super.getLabel().replace("%%value%%", value.toString())
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/menu/submenus/MenuOptions.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/menu/submenus/MenuOptions.kt
new file mode 100644 (file)
index 0000000..7cc8a90
--- /dev/null
@@ -0,0 +1,27 @@
+package ru.deadsoftware.cavedroid.menu.submenus
+
+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
+
+class MenuOptions(
+    width: Float,
+    height: Float,
+    buttonRenderer: ButtonRenderer,
+    mainConfig: MainConfig,
+    menuInput: MenuProc.Input,
+    assetLoader: AssetLoader,
+) : Menu(width, height, buttonRenderer, mainConfig, menuInput, assetLoader) {
+
+    override fun getButtonEventListeners(): HashMap<String, ButtonEventListener> {
+        val map = HashMap<String, ButtonEventListener>()
+        map["back"] = ButtonEventListener { mMenuInput.backClicked() }
+        return map
+    }
+
+    override fun initButtons() {
+        loadButtonsFromJson(mAssetLoader.getAssetHandle("json/menu_options_buttons.json"))
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/menu/submenus/MenusFactory.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/menu/submenus/MenusFactory.kt
new file mode 100644 (file)
index 0000000..e31a9ad
--- /dev/null
@@ -0,0 +1,43 @@
+package ru.deadsoftware.cavedroid.menu.submenus
+
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.menu.MenuProc
+import ru.deadsoftware.cavedroid.menu.MenuScope
+import ru.deadsoftware.cavedroid.menu.objects.ButtonRenderer
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader
+import javax.inject.Inject
+
+@MenuScope
+class MenusFactory @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val assetLoader: AssetLoader,
+) {
+
+    fun getMainMenu(
+        width: Float,
+        height: Float,
+        buttonRenderer: ButtonRenderer,
+        menuInput: MenuProc.Input,
+    ): MenuMain {
+        return MenuMain(width, height, buttonRenderer, mainConfig, menuInput, assetLoader)
+    }
+
+    fun getMenuNewGame(
+        width: Float,
+        height: Float,
+        buttonRenderer: ButtonRenderer,
+        menuInput: MenuProc.Input,
+    ): MenuNewGame {
+        return MenuNewGame(width, height, buttonRenderer, mainConfig, menuInput, assetLoader)
+    }
+
+    fun getMenuOptions(
+        width: Float,
+        height: Float,
+        buttonRenderer: ButtonRenderer,
+        menuInput: MenuProc.Input,
+    ): MenuOptions {
+        return MenuOptions(width, height, buttonRenderer, mainConfig, menuInput, assetLoader)
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/Saveable.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/Saveable.kt
new file mode 100644 (file)
index 0000000..d350f1f
--- /dev/null
@@ -0,0 +1,7 @@
+package ru.deadsoftware.cavedroid.misc
+
+import ru.deadsoftware.cavedroid.game.model.dto.SaveDataDto
+
+interface Saveable {
+    fun getSaveData(): SaveDataDto
+}
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindKeyboardInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindKeyboardInputHandler.kt
new file mode 100644 (file)
index 0000000..558dc96
--- /dev/null
@@ -0,0 +1,11 @@
+package ru.deadsoftware.cavedroid.misc.annotations.multibinding
+
+import ru.deadsoftware.cavedroid.game.input.IKeyboardInputHandler
+import ru.fredboy.automultibind.annotations.BindsIntoSet
+
+@BindsIntoSet(
+    interfaceClass = IKeyboardInputHandler::class,
+    modulePackage = MultibindingConfig.GENERATED_MODULES_PACKAGE,
+    moduleName = "KeyboardInputHandlersModule"
+)
+annotation class BindKeyboardInputHandler
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindMouseInputHandler.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindMouseInputHandler.kt
new file mode 100644 (file)
index 0000000..af9b608
--- /dev/null
@@ -0,0 +1,11 @@
+package ru.deadsoftware.cavedroid.misc.annotations.multibinding
+
+import ru.deadsoftware.cavedroid.game.input.IMouseInputHandler
+import ru.fredboy.automultibind.annotations.BindsIntoSet
+
+@BindsIntoSet(
+    interfaceClass = IMouseInputHandler::class,
+    modulePackage = MultibindingConfig.GENERATED_MODULES_PACKAGE,
+    moduleName = "MouseInputHandlersModule"
+)
+annotation class BindMouseInputHandler
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindPlaceBlockAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindPlaceBlockAction.kt
new file mode 100644 (file)
index 0000000..89f2dd0
--- /dev/null
@@ -0,0 +1,11 @@
+package ru.deadsoftware.cavedroid.misc.annotations.multibinding
+
+import ru.deadsoftware.cavedroid.game.actions.placeblock.IPlaceBlockAction
+import ru.fredboy.automultibind.annotations.BindsIntoMapStringKey
+
+@BindsIntoMapStringKey(
+    interfaceClass = IPlaceBlockAction::class,
+    modulePackage = MultibindingConfig.GENERATED_MODULES_PACKAGE,
+    moduleName = "PlaceBlockActionsModule"
+)
+annotation class BindPlaceBlockAction(val stringKey: String)
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindRenderer.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindRenderer.kt
new file mode 100644 (file)
index 0000000..c1d96a9
--- /dev/null
@@ -0,0 +1,11 @@
+package ru.deadsoftware.cavedroid.misc.annotations.multibinding
+
+import ru.deadsoftware.cavedroid.game.render.IGameRenderer
+import ru.fredboy.automultibind.annotations.BindsIntoSet
+
+@BindsIntoSet(
+    interfaceClass = IGameRenderer::class,
+    modulePackage = MultibindingConfig.GENERATED_MODULES_PACKAGE,
+    moduleName = "RenderModule"
+)
+annotation class BindRenderer
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindUpdateBlockAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindUpdateBlockAction.kt
new file mode 100644 (file)
index 0000000..310d69e
--- /dev/null
@@ -0,0 +1,11 @@
+package ru.deadsoftware.cavedroid.misc.annotations.multibinding
+
+import ru.deadsoftware.cavedroid.game.actions.updateblock.IUpdateBlockAction
+import ru.fredboy.automultibind.annotations.BindsIntoMapStringKey
+
+@BindsIntoMapStringKey(
+    interfaceClass = IUpdateBlockAction::class,
+    modulePackage = MultibindingConfig.GENERATED_MODULES_PACKAGE,
+    moduleName = "UpdateBlockActionsModule"
+)
+annotation class BindUpdateBlockAction(val stringKey: String)
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindUseBlockAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindUseBlockAction.kt
new file mode 100644 (file)
index 0000000..46353ca
--- /dev/null
@@ -0,0 +1,11 @@
+package ru.deadsoftware.cavedroid.misc.annotations.multibinding
+
+import ru.deadsoftware.cavedroid.game.actions.useblock.IUseBlockAction
+import ru.fredboy.automultibind.annotations.BindsIntoMapStringKey
+
+@BindsIntoMapStringKey(
+    interfaceClass = IUseBlockAction::class,
+    modulePackage = MultibindingConfig.GENERATED_MODULES_PACKAGE,
+    moduleName = "UseBlockActionsModule"
+)
+annotation class BindUseBlockAction(val stringKey: String)
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindUseItemAction.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/BindUseItemAction.kt
new file mode 100644 (file)
index 0000000..7f17bfc
--- /dev/null
@@ -0,0 +1,11 @@
+package ru.deadsoftware.cavedroid.misc.annotations.multibinding
+
+import ru.deadsoftware.cavedroid.game.actions.useitem.IUseItemAction
+import ru.fredboy.automultibind.annotations.BindsIntoMapStringKey
+
+@BindsIntoMapStringKey(
+    interfaceClass = IUseItemAction::class,
+    modulePackage = MultibindingConfig.GENERATED_MODULES_PACKAGE,
+    moduleName = "UseItemActionsModule"
+)
+annotation class BindUseItemAction(val stringKey: String)
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/MultibindingConfig.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/annotations/multibind/MultibindingConfig.kt
new file mode 100644 (file)
index 0000000..12427bd
--- /dev/null
@@ -0,0 +1,5 @@
+package ru.deadsoftware.cavedroid.misc.annotations.multibinding
+
+data object MultibindingConfig {
+    const val GENERATED_MODULES_PACKAGE = "ru.deadsoftware.cavedroid.generated.module"
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/ArrayMapExtensions.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/GdxExtensions.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/GdxExtensions.kt
new file mode 100644 (file)
index 0000000..1c80048
--- /dev/null
@@ -0,0 +1,5 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.Graphics
+
+val Graphics.ratio get() = width.toFloat() / height.toFloat()
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/ItemUtils.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt
new file mode 100644 (file)
index 0000000..24103e7
--- /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 / 16f)
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt
new file mode 100644 (file)
index 0000000..917f69b
--- /dev/null
@@ -0,0 +1,98 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.graphics.Color
+import com.badlogic.gdx.graphics.GL20
+import com.badlogic.gdx.graphics.g2d.GlyphLayout
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.MainConfig
+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)
+}
+
+fun SpriteBatch.withScissors(
+    mainConfig: MainConfig,
+    x: Float,
+    y: Float,
+    width: Float,
+    height: Float,
+    block: () -> Unit
+) {
+    val scaleX = Gdx.graphics.width / mainConfig.width
+    val scaleY = Gdx.graphics.height / mainConfig.height
+
+    flush()
+    Gdx.gl.glEnable(GL20.GL_SCISSOR_TEST)
+    Gdx.gl.glScissor(
+        /* x = */ (x * scaleX).toInt(),
+        /* y = */ ((mainConfig.height - y - height) * scaleY).toInt(),
+        /* width = */ (width * scaleX).toInt(),
+        /* height = */ (height * scaleY).toInt()
+    )
+    block.invoke()
+    flush()
+    Gdx.gl.glDisable(GL20.GL_SCISSOR_TEST)
+}
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/SpriteOrigin.kt b/core/src/main/kotlin/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/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/SpriteUtils.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/SpriteUtils.kt
new file mode 100644 (file)
index 0000000..a26c5b9
--- /dev/null
@@ -0,0 +1,36 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.graphics.Color
+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(),
+    tint: Color? = null,
+) {
+    val oldColor = sprite.color
+
+    sprite.setPosition(x, y)
+    sprite.setSize(width, height)
+    sprite.rotation = rotation
+    tint?.let(sprite::setColor)
+
+    sprite.draw(this)
+
+    sprite.setSize(sprite.regionWidth.toFloat(), sprite.regionHeight.toFloat())
+    sprite.rotation = 0f
+    sprite.color = oldColor
+}
+
+fun Sprite.applyOrigin(origin: SpriteOrigin) {
+    origin.applyToSprite(this)
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/misc/utils/mobs/MobSprites.kt b/core/src/main/kotlin/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
diff --git a/core/src/main/kotlin/ru/deadsoftware/cavedroid/prefs/PreferencesStore.kt b/core/src/main/kotlin/ru/deadsoftware/cavedroid/prefs/PreferencesStore.kt
new file mode 100644 (file)
index 0000000..edc9517
--- /dev/null
@@ -0,0 +1,9 @@
+package ru.deadsoftware.cavedroid.prefs
+
+interface PreferencesStore {
+
+    fun getPreference(key: String): String?
+
+    fun setPreference(key: String, value: String?)
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/CaveGame.java b/core/src/ru/deadsoftware/cavedroid/CaveGame.java
deleted file mode 100644 (file)
index 031b613..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-package ru.deadsoftware.cavedroid;
-
-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;
-
-public class CaveGame extends Game {
-
-    private static final String TAG = "CaveGame";
-
-    public static final String VERSION = "alpha 0.4";
-
-    private final MainConfig mMainConfig;
-    private final MainComponent mMainComponent;
-
-    private final String mGameFolder;
-    private final boolean mTouch;
-    private boolean mDebug;
-
-    public CaveGame(String gameFolder, boolean touch) {
-        mGameFolder = gameFolder;
-        mTouch = touch;
-
-        mMainComponent = DaggerMainComponent.builder().caveGame(this).build();
-        mMainConfig = mMainComponent.getMainConfig();
-    }
-
-    public void setDebug(boolean debug) {
-        mDebug = debug;
-    }
-
-    private void initConfig() {
-        int width = mTouch ? 320 : 480;
-        int height = (int) (width * ((float) Gdx.graphics.getHeight() / Gdx.graphics.getWidth()));
-
-        mMainConfig.setMainComponent(mMainComponent);
-        mMainConfig.setGameFolder(mGameFolder);
-        mMainConfig.setTouch(mTouch);
-        mMainConfig.setWidth(width);
-        mMainConfig.setHeight(height);
-        mMainConfig.setShowInfo(mDebug);
-    }
-
-    public void newGame() {
-        GameScreen gameScreen = mMainComponent.getGameScreen();
-        gameScreen.newGame();
-        setScreen(gameScreen);
-    }
-
-    public void loadGame() {
-        GameScreen gameScreen = mMainComponent.getGameScreen();
-        gameScreen.loadGame();
-        setScreen(gameScreen);
-    }
-
-    public void quitGame() {
-        setScreen(mMainComponent.getMenuScreen());
-    }
-
-    @Override
-    public void create() {
-        Gdx.app.log(TAG, mGameFolder);
-        Gdx.files.absolute(mGameFolder).mkdirs();
-
-        Assets.load();
-        GameItems.load();
-
-        initConfig();
-
-        setScreen(mMainComponent.getMenuScreen());
-    }
-
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/MainComponent.java b/core/src/ru/deadsoftware/cavedroid/MainComponent.java
deleted file mode 100644 (file)
index d8a05da..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package ru.deadsoftware.cavedroid;
-
-import dagger.Component;
-import ru.deadsoftware.cavedroid.game.GameScreen;
-import ru.deadsoftware.cavedroid.menu.MenuScreen;
-
-import javax.inject.Singleton;
-
-@Singleton
-@Component(dependencies = CaveGame.class)
-public interface MainComponent {
-    GameScreen getGameScreen();
-
-    MenuScreen getMenuScreen();
-
-    MainConfig getMainConfig();
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/MainConfig.java b/core/src/ru/deadsoftware/cavedroid/MainConfig.java
deleted file mode 100644 (file)
index 8cd0ffd..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-package ru.deadsoftware.cavedroid;
-
-import ru.deadsoftware.cavedroid.game.GameUiWindow;
-
-import javax.annotation.CheckForNull;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-@Singleton
-public class MainConfig {
-
-    private final CaveGame mCaveGame;
-
-    @CheckForNull
-    private MainComponent mMainComponent;
-
-    private GameUiWindow mGameUiWindow;
-    private String mGameFolder;
-
-    private boolean mTouch;
-    private boolean mShowInfo;
-    private boolean mShowMap;
-
-    private float mWidth;
-    private float mHeight;
-
-    @Inject
-    public MainConfig(CaveGame caveGame) {
-        mCaveGame = caveGame;
-
-        mGameUiWindow = GameUiWindow.NONE;
-        mGameFolder = "";
-    }
-
-    public CaveGame getCaveGame() {
-        return mCaveGame;
-    }
-
-    public MainComponent getMainComponent() {
-        assert mMainComponent != null;
-        return mMainComponent;
-    }
-
-    public void setMainComponent(MainComponent mainComponent) {
-        mMainComponent = mainComponent;
-    }
-
-    public boolean checkGameUiWindow(GameUiWindow gameUiWindow) {
-        return mGameUiWindow == gameUiWindow;
-    }
-
-    public GameUiWindow getGameUiWindow() {
-        return mGameUiWindow;
-    }
-
-    public void setGameUiWindow(GameUiWindow gameUiWindow) {
-        mGameUiWindow = gameUiWindow;
-    }
-
-    public String getGameFolder() {
-        return mGameFolder;
-    }
-
-    public void setGameFolder(String gameFolder) {
-        mGameFolder = gameFolder;
-    }
-
-    public boolean isTouch() {
-        return mTouch;
-    }
-
-    public void setTouch(boolean touch) {
-        mTouch = touch;
-    }
-
-    public float getWidth() {
-        return mWidth;
-    }
-
-    public void setWidth(float width) {
-        mWidth = width;
-    }
-
-    public float getHeight() {
-        return mHeight;
-    }
-
-    public void setHeight(float height) {
-        mHeight = height;
-    }
-
-    public boolean isShowInfo() {
-        return mShowInfo;
-    }
-
-    public void setShowInfo(boolean showInfo) {
-        mShowInfo = showInfo;
-    }
-
-    public boolean isShowMap() {
-        return mShowMap;
-    }
-
-    public void setShowMap(boolean showMap) {
-        mShowMap = showMap;
-    }
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameComponent.java b/core/src/ru/deadsoftware/cavedroid/game/GameComponent.java
deleted file mode 100644 (file)
index 25779f0..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package ru.deadsoftware.cavedroid.game;
-
-import dagger.Component;
-import ru.deadsoftware.cavedroid.MainComponent;
-
-@GameScope
-@Component(dependencies = MainComponent.class, modules = GameModule.class)
-public interface GameComponent {
-    GameProc getGameProc();
-
-    GameInputProcessor getGameInputProcessor();
-}
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/GameModule.java b/core/src/ru/deadsoftware/cavedroid/game/GameModule.java
deleted file mode 100644 (file)
index d7ccc2d..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-package ru.deadsoftware.cavedroid.game;
-
-import dagger.Module;
-import dagger.Provides;
-import ru.deadsoftware.cavedroid.MainConfig;
-import ru.deadsoftware.cavedroid.game.mobs.MobsController;
-import ru.deadsoftware.cavedroid.game.objects.DropController;
-
-import javax.annotation.CheckForNull;
-
-@Module
-public class GameModule {
-
-    @CheckForNull
-    private static GameSaver.Data data;
-
-    public static void load(MainConfig mainConfig) {
-        data = GameSaver.load(mainConfig);
-    }
-
-    private static void makeDataNullIfEmpty() {
-        if (data != null && data.isEmpty()) {
-            data = null;
-        }
-    }
-
-    @Provides
-    @GameScope
-    public static DropController provideDropController() {
-        DropController controller = data != null ? data.retrieveDropController() : new DropController();
-        makeDataNullIfEmpty();
-        return controller;
-    }
-
-    @Provides
-    @GameScope
-    public static MobsController provideMobsController() {
-        MobsController controller = data != null ? data.retrieveMobsController() : new MobsController();
-        makeDataNullIfEmpty();
-        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;
-        makeDataNullIfEmpty();
-        return new GameWorld(dropController, mobsController, fm, bm);
-    }
-
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GamePhysics.java b/core/src/ru/deadsoftware/cavedroid/game/GamePhysics.java
deleted file mode 100644 (file)
index d6b5a9a..0000000
+++ /dev/null
@@ -1,288 +0,0 @@
-package ru.deadsoftware.cavedroid.game;
-
-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.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.objects.Drop;
-import ru.deadsoftware.cavedroid.game.objects.DropController;
-
-import javax.inject.Inject;
-import java.util.Iterator;
-
-
-@GameScope
-class GamePhysics {
-
-    static final int PL_SPEED = 2;
-
-    private final Vector2 gravity = new Vector2(0, .9f);
-
-    private final GameWorld mGameWorld;
-    private final MainConfig mMainConfig;
-    private final MobsController mMobsController;
-    private final DropController mDropController;
-
-    @Inject
-    public GamePhysics(GameWorld gameWorld,
-                       MainConfig mainConfig,
-                       MobsController mobsController,
-                       DropController dropController) {
-        mGameWorld = gameWorld;
-        mMainConfig = mainConfig;
-        mMobsController = mobsController;
-        mDropController = dropController;
-    }
-
-    /**
-     * Checks if mob should jump
-     *
-     * @return true if mob should jump
-     */
-    private boolean checkJump(Mob mob) {
-        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);
-
-        if (checkColl(new Rectangle(blX, mob.getY() - 18, mob.getWidth(), mob.getHeight()))) {
-            block = 0;
-        }
-
-        return (block > 0 && GameItems.getBlock(block).toJump() &&
-                (mob.getY() + mob.getHeight()) - GameItems.getBlock(block).getRectangle(blX / 16, blY / 16).y > 8);
-    }
-
-    private boolean checkColl(Rectangle rect) {
-        int minX = (int) ((rect.x + rect.width / 2) / 16) - 4;
-        int minY = (int) ((rect.y + rect.height / 2) / 16) - 4;
-        int maxX = (int) ((rect.x + rect.width / 2) / 16) + 4;
-        int maxY = (int) ((rect.y + rect.height / 2) / 16) + 4;
-
-        if (minY < 0) {
-            minY = 0;
-        }
-
-        if (maxY > mGameWorld.getHeight()) {
-            maxY = mGameWorld.getHeight();
-        }
-
-        int block;
-        for (int y = minY; y < maxY; y++) {
-            for (int x = minX; x < maxX; x++) {
-                block = mGameWorld.getForeMap(x, y);
-                if (block > 0 && GameItems.getBlock(block).hasCollision()) {
-                    if (Intersector.overlaps(rect, GameItems.getBlock(block).getRectangle(x, y))) {
-                        return true;
-                    }
-                }
-            }
-        }
-
-        return false;
-    }
-
-    private int 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);
-        } 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;
-            }
-        }
-        drop.move();
-
-
-        if (checkColl(drop)) {
-            drop.getMove().set(0, -1);
-            do {
-                drop.move();
-            } while (checkColl(drop));
-            drop.getMove().setZero();
-        }
-    }
-
-    private void mobXColl(Mob mob) {
-        if (checkColl(mob)) {
-            if (mob.canJump() && !mob.isFlyMode()) {
-                mob.y -= 8;
-            }
-
-            if (checkColl(mob)) {
-                if (mob.canJump() && !mob.isFlyMode()) {
-                    mob.y += 8;
-                }
-
-                int d = 0;
-
-                if (mob.getMove().x < 0) {
-                    d = 1;
-                } else if (mob.getMove().x > 0) {
-                    d = -1;
-                }
-
-                mob.x = MathUtils.round(mob.getX());
-
-                while (checkColl(mob)) {
-                    mob.x += d;
-                }
-
-                if (mob.canJump()) {
-                    mob.changeDir();
-                }
-            }
-        }
-
-        mob.checkWorldBounds(mGameWorld);
-    }
-
-    private void mobYColl(Mob mob) {
-        if (checkColl(mob)) {
-            int d = -1;
-
-            if (mob.getMove().y < 0) {
-                d = 1;
-            }
-
-            if (d == -1) {
-                mob.setCanJump(true);
-                mob.setFlyMode(false);
-            }
-
-            mob.y = MathUtils.round(mob.getY());
-
-            while (checkColl(mob)) {
-                mob.y += d;
-            }
-
-            mob.getMove().y = 0;
-
-        } else {
-            mob.setCanJump(false);
-        }
-
-        if (mob.getY() > mGameWorld.getHeightPx()) {
-            mob.kill();
-        }
-    }
-
-    private void playerPhy(Player player) {
-        player.y += player.getMove().y;
-        mobYColl(player);
-
-        if (player.isDead()) {
-            return;
-        }
-
-        if (GameItems.isFluid(getBlock(player))) {
-            if (mMainConfig.isTouch() && player.getMove().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.getMove().y > 4.5f) {
-                    player.getMove().add(0, -1f);
-                }
-            } else {
-                player.getMove().add(0, -.5f);
-                if (player.getMove().y < -3) {
-                    player.getMove().y = -3;
-                }
-            }
-        } else {
-            if (!player.isFlyMode() && player.getMove().y < 18) {
-                player.getMove().add(gravity);
-            }
-        }
-
-        player.x += player.getMove().x * (player.isFlyMode() ? 1.5f : 1) *
-                (GameItems.isFluid(getBlock(player)) && !player.isFlyMode() ? .8f : 1);
-
-        mobXColl(player);
-
-        if (mMainConfig.isTouch() && !player.isFlyMode() && player.canJump() && player.getMove().x != 0 && checkJump(player)) {
-            player.getMove().add(0, -8);
-            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);
-            }
-
-            mob.getMove().add(0, -.5f);
-
-            if (mob.getMove().y < -3) {
-                mob.getMove().y = -3;
-            }
-        } else if (!mob.isFlyMode() && mob.getMove().y < 18) {
-            mob.getMove().add(gravity);
-        }
-
-        mob.y += mob.getMove().y;
-        mobYColl(mob);
-
-        if (mob.isDead()) {
-            return;
-        }
-
-        mob.x += mob.getMove().x;
-        mobXColl(mob);
-
-        if (mob.canJump() && mob.getMove().x != 0 && checkJump(mob)) {
-            mob.getMove().add(0, -8);
-            mob.setCanJump(false);
-        }
-    }
-
-    void update() {
-        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()) {
-                it.remove();
-            }
-        }
-
-        for (Iterator<Mob> it = mMobsController.getIterator(); it.hasNext(); ) {
-            Mob mob = it.next();
-            mob.ai(mGameWorld);
-            mobPhy(mob);
-            if (mob.isDead()) {
-                it.remove();
-            }
-        }
-
-        playerPhy(player);
-        if (player.isDead()) {
-            player.respawn(mGameWorld);
-        }
-    }
-
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameProc.java b/core/src/ru/deadsoftware/cavedroid/game/GameProc.java
deleted file mode 100644 (file)
index 44a4687..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package ru.deadsoftware.cavedroid.game;
-
-import com.badlogic.gdx.utils.Disposable;
-
-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;
-
-    @Inject
-    public GameProc(GameWorld gameWorld,
-                    GamePhysics gamePhysics,
-                    GameInput gameInput,
-                    GameRenderer gameRenderer) {
-        mGameWorld = gameWorld;
-        mGamePhysics = gamePhysics;
-        mGameInput = gameInput;
-        mGameRenderer = gameRenderer;
-
-        mGameWorld.startFluidsThread();
-    }
-
-    public void update(float delta) {
-        mGamePhysics.update();
-        mGameInput.update();
-        mGameWorld.update();
-        mGameRenderer.render(delta);
-    }
-
-    @Override
-    public void dispose() {
-        mGameWorld.dispose();
-    }
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameRenderer.java b/core/src/ru/deadsoftware/cavedroid/game/GameRenderer.java
deleted file mode 100644 (file)
index 19f2681..0000000
+++ /dev/null
@@ -1,303 +0,0 @@
-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 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.objects.Drop;
-import ru.deadsoftware.cavedroid.game.objects.DropController;
-import ru.deadsoftware.cavedroid.misc.ControlMode;
-import ru.deadsoftware.cavedroid.misc.Renderer;
-
-import javax.inject.Inject;
-
-import static ru.deadsoftware.cavedroid.misc.Assets.guiMap;
-import static ru.deadsoftware.cavedroid.misc.Assets.textureRegions;
-
-@GameScope
-public class GameRenderer extends Renderer {
-
-    private final MainConfig mMainConfig;
-    private final GameInput mGameInput;
-    private final GameWorld mGameWorld;
-    private final MobsController mMobsController;
-    private final DropController mDropController;
-
-    @Inject
-    GameRenderer(MainConfig mainConfig,
-                 GameInput gameInput,
-                 GameWorld gameWorld,
-                 MobsController mobsController,
-                 DropController dropController) {
-        super(mainConfig.getWidth(), mainConfig.getHeight());
-
-        mMainConfig = mainConfig;
-        mGameInput = gameInput;
-        mGameWorld = gameWorld;
-        mMobsController = mobsController;
-        mDropController = dropController;
-
-        Gdx.gl.glClearColor(0f, .6f, .6f, 1f);
-    }
-
-    private float drawX(int x) {
-        return x * 16 - getCamX();
-    }
-
-    private float drawY(int y) {
-        return y * 16 - getCamY();
-    }
-
-    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 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 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 void drawMob(Mob mob) {
-        float mobDrawX = mob.getX() - getCamX();
-        float mobDrawY = mob.getY() - getCamY();
-
-        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;
-        }
-
-        mob.draw(spriter, mobDrawX, mobDrawY);
-    }
-
-    private void drawDrop(Drop drop) {
-    }
-
-    @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);
-                }
-            }
-        }
-
-    }
-
-    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());
-        }
-        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);
-                }
-            }
-        }
-        spriter.draw(hotbarSelector,
-                getWidth() / 2 - (float) hotbar.getRegionWidth() / 2 - 1 + 20 * mMobsController.getPlayer().slot,
-                -1);
-    }
-
-    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);
-        }
-    }
-
-    private void drawGamePlay() {
-        Player player = mMobsController.getPlayer();
-
-        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();
-    }
-
-    private void updateCameraPosition() {
-        Player player = mMobsController.getPlayer();
-        setCamPos(player.getX() + player.getWidth() / 2 - getWidth() / 2,
-                player.getY() + player.getHeight() / 2 - getHeight() / 2);
-    }
-
-    @Override
-    public void render(float delta) {
-        int fps = (int) (1 / delta);
-        updateCameraPosition();
-        mGameInput.moveCursor(this);
-
-        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
-
-        spriter.begin();
-
-        drawGamePlay();
-
-        switch (mMainConfig.getGameUiWindow()) {
-            case CREATIVE_INVENTORY:
-                drawCreative();
-                break;
-            //TODO draw other ui windows
-        }
-
-
-        if (mMainConfig.isTouch()) {
-            drawTouchGui();
-        }
-
-        spriter.end();
-
-        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();
-            //=================
-        }
-
-        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();
-        }
-
-    }
-
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameSaver.java b/core/src/ru/deadsoftware/cavedroid/game/GameSaver.java
deleted file mode 100644 (file)
index 1ff2bad..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-package ru.deadsoftware.cavedroid.game;
-
-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.objects.DropController;
-
-import javax.annotation.CheckForNull;
-import java.io.*;
-import java.nio.ByteBuffer;
-
-public class GameSaver {
-
-    public static class Data {
-        @CheckForNull
-        private MobsController mMobsController;
-        @CheckForNull
-        private DropController mDropController;
-        @CheckForNull
-        private int[][] mForeMap, mBackMap;
-
-        public Data(MobsController mobsController, DropController dropController, int[][] foreMap, int[][] backMap) {
-            mMobsController = mobsController;
-            mDropController = dropController;
-            mForeMap = foreMap;
-            mBackMap = backMap;
-        }
-
-        public MobsController retrieveMobsController() {
-            assert mMobsController != null;
-            MobsController mobsController = mMobsController;
-            mMobsController = null;
-            return mobsController;
-        }
-
-        public DropController retrieveDropController() {
-            assert mDropController != null;
-            DropController dropController = mDropController;
-            mDropController = null;
-            return dropController;
-        }
-
-        public int[][] retrieveForeMap() {
-            assert mForeMap != null;
-            int[][] foreMap = mForeMap;
-            mForeMap = null;
-            return foreMap;
-        }
-
-        public int[][] retrieveBackMap() {
-            assert mBackMap != null;
-            int[][] backMap = mBackMap;
-            mBackMap = null;
-            return backMap;
-        }
-
-        public boolean isEmpty() {
-            return mMobsController == null && mDropController == null && mForeMap == null && mBackMap == null;
-        }
-    }
-
-    private static final int SAVE_VERSION = 1;
-
-    private static byte[] intToBytes(int i) {
-        return ByteBuffer.allocate(4).putInt(i).array();
-    }
-
-    private static void saveMap(FileHandle file, int[][] map) 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(intToBytes(width));
-        out.write(intToBytes(height));
-
-        for (int y = 0; y < height; y++) {
-            block = map[0][y];
-            run = 0;
-            for (int[] ints : map) {
-                if (ints[y] != block) {
-                    out.write(intToBytes(run));
-                    out.write(intToBytes(block));
-                    run = 0;
-                    block = ints[y];
-                }
-                run++;
-            }
-            out.write(intToBytes(run));
-            out.write(intToBytes(block));
-        }
-
-        out.flush();
-        out.close();
-    }
-
-    private static int[][] loadMap(FileHandle file) throws Exception {
-        int[][] map;
-        int version, width, height;
-        int run, block;
-
-        DataInputStream in = new DataInputStream(file.read());
-
-        version = in.readInt();
-
-        if (SAVE_VERSION == version) {
-            width = in.readInt();
-            height = in.readInt();
-            map = new int[width][height];
-            for (int y = 0; y < height; y++) {
-                for (int x = 0; x < width; x += run) {
-                    run = in.readInt();
-                    block = in.readInt();
-                    for (int i = x; i < x + run; i++) {
-                        map[i][y] = block;
-                    }
-                }
-            }
-        } else {
-            throw new Exception("version mismatch");
-        }
-
-        in.close();
-        return map;
-    }
-
-    @CheckForNull
-    public static Data load(MainConfig mainConfig) {
-        String folder = mainConfig.getGameFolder();
-        FileHandle file = Gdx.files.absolute(folder + "/saves/game.sav");
-
-        try {
-            ObjectInputStream in = new ObjectInputStream(file.read());
-            int version = in.readInt();
-            DropController dropController;
-            MobsController mobsController;
-
-            if (SAVE_VERSION == version) {
-                dropController = (DropController) in.readObject();
-                mobsController = (MobsController) in.readObject();
-            } else {
-                throw new Exception("version mismatch");
-            }
-
-            in.close();
-
-            int[][] foreMap = loadMap(Gdx.files.absolute(mainConfig.getGameFolder() + "/saves/foremap.sav"));
-            int[][] backMap = loadMap(Gdx.files.absolute(mainConfig.getGameFolder() + "/saves/backmap.sav"));
-
-            if (dropController == null || mobsController == null) {
-                throw new Exception("couldn't load");
-            }
-
-            return new Data(mobsController, dropController, foreMap, backMap);
-        } catch (Exception e) {
-            Gdx.app.error("GameSaver", e.getMessage());
-        }
-
-        return null;
-    }
-
-    public static void save(MainConfig mainConfig,
-                            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");
-
-        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());
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    public static boolean exists(MainConfig mainConfig) {
-        String folder = mainConfig.getGameFolder();
-        return (Gdx.files.absolute(folder + "/saves/game.sav").exists() &&
-                Gdx.files.absolute(folder + "/saves/foremap.sav").exists() &&
-                Gdx.files.absolute(folder + "/saves/backmap.sav").exists());
-    }
-}
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/mobs/FallingGravel.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/FallingGravel.java
deleted file mode 100644 (file)
index 23cf1dc..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package ru.deadsoftware.cavedroid.game.mobs;
-
-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;
-
-/**
- * Falling gravel is actually a mob, that spawns in place of gravel when there is no block under it,
- * falls down to the next block and becomes a block of gravel again.
- */
-public class FallingGravel extends Mob {
-
-    /**
-     * Creates a FallingGravel mob at coordinates
-     *
-     * @param x X in pixels
-     * @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);
-    }
-
-    @Override
-    public void ai(GameWorld gameWorld) {
-        if (mMove.isZero()) {
-            gameWorld.setForeMap(getMapX(), getMiddleMapY(), 11);
-            kill();
-        }
-    }
-
-    @Override
-    public void changeDir() {
-    }
-
-    @Override
-    public void draw(SpriteBatch spriteBatch, float x, float y) {
-        spriteBatch.draw(GameItems.getBlockTex(11), x, y);
-    }
-
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/FallingSand.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/FallingSand.java
deleted file mode 100644 (file)
index 9a383a2..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-package ru.deadsoftware.cavedroid.game.mobs;
-
-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;
-
-
-/**
- * Falling sand is actually a mob, that spawns in place of gravel when there is no block under it,
- * falls down to the next block and becomes a block of sand again.
- */
-public class FallingSand extends Mob {
-
-    /**
-     * Creates a FallingSand mob at coordinates
-     *
-     * @param x X in pixels
-     * @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);
-    }
-
-    @Override
-    public void ai(GameWorld gameWorld) {
-        if (mMove.isZero()) {
-            gameWorld.setForeMap(getMapX(), getMiddleMapY(), 10);
-            kill();
-        }
-    }
-
-    @Override
-    public void changeDir() {
-    }
-
-    @Override
-    public void draw(SpriteBatch spriteBatch, float x, float y) {
-        spriteBatch.draw(GameItems.getBlockTex(10), x, y);
-    }
-
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/Mob.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/Mob.java
deleted file mode 100644 (file)
index 0f76592..0000000
+++ /dev/null
@@ -1,177 +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.Rectangle;
-import com.badlogic.gdx.math.Vector2;
-import ru.deadsoftware.cavedroid.game.GameWorld;
-
-import java.io.Serializable;
-
-/**
- * Mob class.
- */
-public abstract class Mob extends Rectangle implements Serializable {
-
-    public enum Type {
-        MOB,
-        SAND,
-        GRAVEL
-    }
-
-    public enum Direction {
-        LEFT,
-        RIGHT
-    }
-
-    protected Vector2 mMove;
-    protected Type mType;
-    protected int mAnimDelta = 6;
-    protected int mAnim;
-
-    private Direction mDirection;
-    private boolean mDead;
-    private boolean mCanJump;
-    private boolean mFlyMode;
-
-    /**
-     * @param x          in pixels
-     * @param y          in pixels
-     * @param width      in pixels
-     * @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) {
-        super(x, y, width, height);
-        mMove = new Vector2(0, 0);
-        mCanJump = false;
-        mDead = false;
-        this.mDirection = mDirection;
-        this.mType = type;
-    }
-
-    protected static Direction randomDir() {
-        return MathUtils.randomBoolean(.5f) ? Direction.LEFT : Direction.RIGHT;
-    }
-
-    /**
-     * @return The X coordinate of a mob in blocks
-     */
-    public final int getMapX() {
-        return (int) (x + (getWidth() / 2)) / 16;
-    }
-
-    /**
-     * @return The Y coordinate of mob's upper edge in blocks
-     */
-    public final int getUpperMapY() {
-        return (int) (y / 16);
-    }
-
-    /**
-     * @return The Y coordinate if mob's vertical center in blocks
-     */
-    public final int getMiddleMapY() {
-        return (int) (y + (getHeight() / 2)) / 16;
-    }
-
-    /**
-     * @return The Y coordinate of mob's legs in blocks
-     */
-    public final int getLowerMapY() {
-        return (int) (y + getHeight()) / 16;
-    }
-
-    public final float getWidth() {
-        return width;
-    }
-
-    public final float getHeight() {
-        return height;
-    }
-
-    /**
-     * @return Integer representing a direction in which mob is looking, where 0 is left and 1 is right
-     */
-    public final Direction getDirection() {
-        return mDirection;
-    }
-
-    public final boolean looksLeft() {
-        return mDirection == Direction.LEFT;
-    }
-
-    public final boolean looksRight() {
-        return mDirection == Direction.RIGHT;
-    }
-
-    /**
-     * Switches direction in which mob is looking
-     */
-    protected final void switchDir() {
-        mDirection = looksLeft() ? Direction.RIGHT : Direction.LEFT;
-    }
-
-    protected final int dirMultiplier() {
-        return looksLeft() ? 0 : 1;
-    }
-
-    public final boolean isDead() {
-        return mDead;
-    }
-
-    public final int getAnim() {
-        return mAnim;
-    }
-
-    /**
-     * Set's mob's dead variable to true and nothing else. It doesn't delete the
-     */
-    public final void kill() {
-        mDead = true;
-    }
-
-    public final void move() {
-        x += mMove.x;
-        y += mMove.y;
-    }
-
-    public final Vector2 getMove() {
-        return mMove;
-    }
-
-    public final boolean canJump() {
-        return mCanJump;
-    }
-
-    public final void setCanJump(boolean canJump) {
-        this.mCanJump = canJump;
-    }
-
-    public final boolean isFlyMode() {
-        return mFlyMode;
-    }
-
-    public final void setFlyMode(boolean flyMode) {
-        this.mFlyMode = flyMode;
-    }
-
-    public final Type getType() {
-        return mType;
-    }
-
-    public void checkWorldBounds(GameWorld gameWorld) {
-        if (x + width / 2 < 0) {
-            x += gameWorld.getWidthPx();
-        }
-        if (x + width / 2 > gameWorld.getWidthPx()) {
-            x -= gameWorld.getWidthPx();
-        }
-    }
-
-    public abstract void draw(SpriteBatch spriteBatch, float x, float y);
-
-    public abstract void ai(GameWorld gameWorld);
-
-    public abstract void changeDir();
-}
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/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/Player.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/Player.java
deleted file mode 100644 (file)
index 0b7ab8b..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-package ru.deadsoftware.cavedroid.game.mobs;
-
-import com.badlogic.gdx.graphics.g2d.SpriteBatch;
-import com.badlogic.gdx.math.Vector2;
-import ru.deadsoftware.cavedroid.game.GameWorld;
-import ru.deadsoftware.cavedroid.misc.Assets;
-
-public class Player extends Mob {
-
-    public final int[] inventory;
-    public int slot;
-    public final int gameMode;
-    public boolean swim;
-
-    public Player() {
-        super(0, 0, 4, 30, randomDir(), Type.MOB);
-        this.gameMode = 1;
-        inventory = new int[9];
-        swim = false;
-    }
-
-    public void respawn(GameWorld gameWorld) {
-        Vector2 pos = getSpawnPoint(gameWorld);
-        this.x = pos.x;
-        this.y = pos.y;
-        mMove.setZero();
-    }
-
-    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);
-                break;
-            }
-            if (gameWorld.hasForeAt(0, y) && gameWorld.getForeMapBlock(0, y).hasCollision()) {
-                break;
-            }
-        }
-        return new Vector2(8 - getWidth() / 2, (float) y * 16 - getHeight());
-    }
-
-    public void setDir(Direction dir) {
-        if (dir != getDirection()) {
-            switchDir();
-        }
-    }
-
-    @Override
-    public void ai(GameWorld gameWorld) {
-    }
-
-    @Override
-    public void changeDir() {
-    }
-
-    @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);
-        } 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);
-    }
-
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/package-info.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/package-info.java
deleted file mode 100644 (file)
index e76bf1b..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-@NonnullByDefault
-package ru.deadsoftware.cavedroid.game.mobs;
-
-import ru.deadsoftware.cavedroid.misc.annotations.NonnullByDefault;
\ No newline at end of file
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/DropController.java b/core/src/ru/deadsoftware/cavedroid/game/objects/DropController.java
deleted file mode 100644 (file)
index 0e4c659..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package ru.deadsoftware.cavedroid.game.objects;
-
-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 DropController implements Serializable {
-
-    public interface Callback {
-        void run(Drop drop);
-    }
-
-    private final LinkedList<Drop> mDrops = new LinkedList<>();
-
-    @Inject
-    public DropController() {
-    }
-
-    public void addDrop(float x, float y, int id) {
-        mDrops.add(new Drop(x, y, id));
-    }
-
-    public int getSize() {
-        return mDrops.size();
-    }
-
-    public void forEach(Callback callback) {
-        mDrops.forEach(callback::run);
-    }
-
-    public Iterator<Drop> getIterator() {
-        return mDrops.iterator();
-    }
-
-}
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/objects/package-info.java b/core/src/ru/deadsoftware/cavedroid/game/objects/package-info.java
deleted file mode 100644 (file)
index c5856a0..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-@NonnullByDefault
-package ru.deadsoftware.cavedroid.game.objects;
-
-import ru.deadsoftware.cavedroid.misc.annotations.NonnullByDefault;
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/package-info.java b/core/src/ru/deadsoftware/cavedroid/game/package-info.java
deleted file mode 100644 (file)
index 6290746..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-@NonnullByDefault
-package ru.deadsoftware.cavedroid.game;
-
-import ru.deadsoftware.cavedroid.misc.annotations.NonnullByDefault;
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/menu/objects/package-info.java b/core/src/ru/deadsoftware/cavedroid/menu/objects/package-info.java
deleted file mode 100644 (file)
index b132187..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-@NonnullByDefault
-package ru.deadsoftware.cavedroid.menu.objects;
-
-import ru.deadsoftware.cavedroid.misc.annotations.NonnullByDefault;
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/menu/package-info.java b/core/src/ru/deadsoftware/cavedroid/menu/package-info.java
deleted file mode 100644 (file)
index 304c211..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-@NonnullByDefault
-package ru.deadsoftware.cavedroid.menu;
-
-import ru.deadsoftware.cavedroid.misc.annotations.NonnullByDefault;
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuMain.java b/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuMain.java
deleted file mode 100644 (file)
index d2dedd2..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package ru.deadsoftware.cavedroid.menu.submenus;
-
-import com.badlogic.gdx.Gdx;
-import ru.deadsoftware.cavedroid.MainConfig;
-import ru.deadsoftware.cavedroid.game.GameSaver;
-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 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);
-    }
-
-    @Override
-    protected HashMap<String, ButtonEventListener> getButtonEventListeners() {
-        HashMap<String, ButtonEventListener> map = new HashMap<>();
-        map.put("new_game", mMenuInput::newGameClicked);
-        map.put("load_game", mMenuInput::loadGameClicked);
-        map.put("quit", mMenuInput::quitClicked);
-        return map;
-    }
-
-    @Override
-    protected void initButtons() {
-        loadButtonsFromJson(Gdx.files.internal("json/menu_main_buttons.json"));
-        if (GameSaver.exists(mMainConfig)) {
-            getButtons().get("load_game").setType(Button.NORMAL);
-        }
-    }
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuNewGame.java b/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuNewGame.java
deleted file mode 100644 (file)
index 2c3b38b..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-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 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);
-    }
-
-    @Override
-    protected HashMap<String, ButtonEventListener> getButtonEventListeners() {
-        HashMap<String, ButtonEventListener> map = new HashMap<>();
-        map.put("survival", mMenuInput::survivalClicked);
-        map.put("creative", mMenuInput::creativeClicked);
-        map.put("back", mMenuInput::backClicked);
-        return map;
-    }
-
-    @Override
-    protected void initButtons() {
-        loadButtonsFromJson(Gdx.files.internal("json/menu_new_game_buttons.json"));
-    }
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/menu/submenus/package-info.java b/core/src/ru/deadsoftware/cavedroid/menu/submenus/package-info.java
deleted file mode 100644 (file)
index 6c28493..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-@NonnullByDefault
-package ru.deadsoftware.cavedroid.menu.submenus;
-
-import ru.deadsoftware.cavedroid.misc.annotations.NonnullByDefault;
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/Assets.java b/core/src/ru/deadsoftware/cavedroid/misc/Assets.java
deleted file mode 100644 (file)
index 335e3c0..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-package ru.deadsoftware.cavedroid.misc;
-
-import com.badlogic.gdx.Gdx;
-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.utils.ArrayMap;
-import com.badlogic.gdx.utils.JsonReader;
-import com.badlogic.gdx.utils.JsonValue;
-import ru.deadsoftware.cavedroid.game.objects.TouchButton;
-
-import java.util.HashMap;
-
-public class Assets {
-
-    public static final JsonReader jsonReader = new JsonReader();
-    public static final Sprite[][] playerSprite = new Sprite[2][4];
-    public static final Sprite[][] pigSprite = new Sprite[2][2];
-    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;
-
-    private static TextureRegion flippedRegion(Texture texture, int x, int y, int width, int height) {
-        return new TextureRegion(texture, x, y + height, width, -height);
-    }
-
-    private static Sprite flippedSprite(Texture texture) {
-        Sprite sprite = new Sprite(texture);
-        sprite.flip(false, true);
-        return sprite;
-    }
-
-    private static Sprite flippedSprite(TextureRegion texture) {
-        Sprite sprite = new Sprite(texture);
-        sprite.flip(false, true);
-        return sprite;
-    }
-
-    private static void loadMob(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].setOrigin(sprite[i][j].getWidth() / 2, 0);
-            }
-        }
-    }
-
-    /**
-     * 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"));
-        for (JsonValue file = json.child(); file != null; file = file.next()) {
-            Texture texture = new Texture(Gdx.files.internal(file.name() + ".png"));
-            if (file.size == 0) {
-                textureRegions.put(file.name(),
-                        flippedRegion(texture, 0, 0, texture.getWidth(), texture.getHeight()));
-            } else {
-                for (JsonValue key = file.child(); key != null; key = key.next()) {
-                    int x = getIntFromJson(key, "x", 0);
-                    int y = getIntFromJson(key, "y", 0);
-                    int w = getIntFromJson(key, "w", texture.getWidth());
-                    int h = getIntFromJson(key, "h", texture.getHeight());
-                    textureRegions.put(key.name(), flippedRegion(texture, x, y, w, h));
-                }
-            }
-        }
-    }
-
-    public static void load() {
-        loadMob(playerSprite, "char");
-        loadMob(pigSprite, "pig");
-        loadJSON();
-        minecraftFont = new BitmapFont(Gdx.files.internal("font.fnt"), true);
-        minecraftFont.getData().setScale(.375f);
-    }
-
-    /**
-     * @param s string whose width you want to know
-     * @return A width of string written in {@link #minecraftFont} in pixels
-     */
-    public static int getStringWidth(String s) {
-        glyphLayout.setText(minecraftFont, s);
-        return (int) glyphLayout.width;
-    }
-
-    /**
-     * @param s string whose height you want to know
-     * @return A height of string written in {@link #minecraftFont} in pixels
-     */
-    public static int getStringHeight(String s) {
-        glyphLayout.setText(minecraftFont, s);
-        return (int) glyphLayout.height;
-    }
-
-    public static int getIntFromJson(JsonValue json, String name, int defaultValue) {
-        return json.has(name) ? json.getInt(name) : defaultValue;
-    }
-
-    public static String getStringFromJson(JsonValue json, String name, String defaultValue) {
-        return json.has(name) ? json.getString(name) : defaultValue;
-    }
-
-    public static boolean getBooleanFromJson(JsonValue json, String name, boolean defaultValue) {
-        return json.has(name) ? json.getBoolean(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
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/annotations/NonnullByDefault.java b/core/src/ru/deadsoftware/cavedroid/misc/annotations/NonnullByDefault.java
deleted file mode 100644 (file)
index bee4070..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package ru.deadsoftware.cavedroid.misc.annotations;
-
-import javax.annotation.Nonnull;
-import javax.annotation.meta.TypeQualifierDefault;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import static java.lang.annotation.ElementType.*;
-
-@Nonnull
-@TypeQualifierDefault({METHOD, FIELD, PARAMETER})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface NonnullByDefault {
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/package-info.java b/core/src/ru/deadsoftware/cavedroid/misc/package-info.java
deleted file mode 100644 (file)
index acdf3e1..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-@NonnullByDefault
-package ru.deadsoftware.cavedroid.misc;
-
-import ru.deadsoftware.cavedroid.misc.annotations.NonnullByDefault;
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/package-info.java b/core/src/ru/deadsoftware/cavedroid/package-info.java
deleted file mode 100644 (file)
index a566cb5..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-@NonnullByDefault
-package ru.deadsoftware.cavedroid;
-
-import ru.deadsoftware.cavedroid.misc.annotations.NonnullByDefault;
\ No newline at end of file
diff --git a/desktop/build.gradle b/desktop/build.gradle
deleted file mode 100644 (file)
index b3a6693..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-apply plugin: "java"
-
-sourceCompatibility = 1.8
-sourceSets.main.java.srcDirs = [ "src/" ]
-sourceSets.main.resources.srcDirs = ["../android/assets"]
-
-project.ext.mainClassName = "ru.deadsoftware.cavedroid.desktop.DesktopLauncher"
-project.ext.assetsDir = new File("../android/assets")
-
-task run(dependsOn: classes, type: JavaExec) {
-    main = project.mainClassName
-    classpath = sourceSets.main.runtimeClasspath
-    standardInput = System.in
-    workingDir = project.assetsDir
-    ignoreExitValue = true as JavaExecSpec
-}
-
-task runTouch(dependsOn: classes, type: JavaExec) {
-    main = project.mainClassName
-    classpath = sourceSets.main.runtimeClasspath
-    standardInput = System.in
-    workingDir = project.assetsDir
-    ignoreExitValue = true as JavaExecSpec
-    args "--touch"
-}
-
-task debug(dependsOn: classes, type: JavaExec) {
-    main = project.mainClassName
-    classpath = sourceSets.main.runtimeClasspath
-    standardInput = System.in
-    workingDir = project.assetsDir
-    ignoreExitValue = true as JavaExecSpec
-    debug = true
-}
-
-task dist(type: Jar) {
-    manifest {
-        attributes 'Main-Class': project.mainClassName
-    }
-    from {
-        configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) }
-    }
-    with jar
-}
-
-
-dist.dependsOn classes
\ No newline at end of file
diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts
new file mode 100644 (file)
index 0000000..5e2203e
--- /dev/null
@@ -0,0 +1,41 @@
+plugins {
+    id("kotlin")
+}
+
+java.sourceCompatibility = ApplicationInfo.sourceCompatibility
+java.targetCompatibility = ApplicationInfo.sourceCompatibility
+
+private val desktopLauncherClassName = "ru.deadsoftware.cavedroid.desktop.DesktopLauncher"
+
+tasks.register<JavaExec>("run") {
+    dependsOn("build")
+    mainClass = desktopLauncherClassName
+    classpath = sourceSets["main"].runtimeClasspath
+    workingDir = sourceSets["main"].resources.sourceDirectories.first()
+    args("--debug")
+}
+
+tasks.register<JavaExec>("runTouch") {
+    dependsOn("build")
+    mainClass = desktopLauncherClassName
+    classpath = sourceSets["main"].runtimeClasspath
+    workingDir = sourceSets["main"].resources.sourceDirectories.first()
+    args("--touch", "--debug")
+}
+
+tasks.register<Jar>("dist") {
+    dependsOn("build")
+    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+    manifest {
+        attributes["Main-Class"] = desktopLauncherClassName
+    }
+    from(configurations.runtimeClasspath.get().resolve().map { it.takeIf(File::isDirectory) ?: zipTree(it) })
+}
+
+dependencies {
+    implementation(project(":core"))
+
+    implementation(Dependencies.LibGDX.gdx)
+    implementation(Dependencies.LibGDX.Desktop.backend)
+    implementation(Dependencies.LibGDX.Desktop.natives)
+}
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
diff --git a/desktop/src/main/kotlin/ru/deadsoftware/cavedroid/desktop/DesktopLauncher.kt b/desktop/src/main/kotlin/ru/deadsoftware/cavedroid/desktop/DesktopLauncher.kt
new file mode 100644 (file)
index 0000000..b4b3db4
--- /dev/null
@@ -0,0 +1,54 @@
+package ru.deadsoftware.cavedroid.desktop
+
+import com.badlogic.gdx.Files
+import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application
+import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
+import ru.deadsoftware.cavedroid.CaveGame
+
+internal object DesktopLauncher {
+
+    @JvmStatic
+    fun main(arg: Array<String>) {
+        val config = Lwjgl3ApplicationConfiguration()
+
+        with(config) {
+            setWindowIcon(
+                /* fileType = */ Files.FileType.Internal,
+                /* ...filePaths = */ "icons/icon512.png", "icons/icon256.png", "icons/icon128.png"
+            )
+            setTitle("CaveDroid")
+            setWindowedMode(960, 540)
+            useVsync(true)
+        }
+
+        var touch = false
+        var debug = false
+        var assetsPath: String? = null
+
+        for (anArg in arg) {
+            if (anArg == "--touch") {
+                touch = true
+            }
+
+            if (anArg == "--debug") {
+                debug = true
+            }
+
+            if (anArg.startsWith("--assets")) {
+                val splitArg: Array<String> = anArg.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+                if (splitArg.size >= 2) {
+                    assetsPath = splitArg[1]
+                }
+            }
+        }
+
+        val caveGame = CaveGame(
+            gameDataDirectoryPath = System.getProperty("user.home") + "/.cavedroid",
+            isTouchScreen = touch,
+            isDebug = debug,
+            preferencesStore = DesktopPreferencesStore(),
+        )
+
+        Lwjgl3Application(caveGame, config)
+    }
+}
\ No newline at end of file
diff --git a/desktop/src/main/kotlin/ru/deadsoftware/cavedroid/desktop/DesktopPreferencesStore.kt b/desktop/src/main/kotlin/ru/deadsoftware/cavedroid/desktop/DesktopPreferencesStore.kt
new file mode 100644 (file)
index 0000000..03591bb
--- /dev/null
@@ -0,0 +1,18 @@
+package ru.deadsoftware.cavedroid.desktop
+
+import ru.deadsoftware.cavedroid.prefs.PreferencesStore
+import java.util.prefs.Preferences
+
+class DesktopPreferencesStore : PreferencesStore {
+
+    private val prefs = Preferences.userNodeForPackage(DesktopPreferencesStore::class.java)
+
+    override fun getPreference(key: String): String? {
+        return prefs.get(key, null)
+    }
+
+    override fun setPreference(key: String, value: String?) {
+        prefs.put(key, value)
+    }
+
+}
\ No newline at end of file
diff --git a/desktop/src/main/resources b/desktop/src/main/resources
new file mode 120000 (symlink)
index 0000000..2978ef3
--- /dev/null
@@ -0,0 +1 @@
+../../../assets
\ No newline at end of file
diff --git a/desktop/src/ru/deadsoftware/cavedroid/desktop/DesktopLauncher.java b/desktop/src/ru/deadsoftware/cavedroid/desktop/DesktopLauncher.java
deleted file mode 100644 (file)
index 2e9ae42..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-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 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);
-       }
-}
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..dc8c2b772fe0839531f9970e2e48985ab814e7f1 100644 (file)
@@ -1,3 +1,5 @@
 org.gradle.daemon=true
 org.gradle.jvmargs=-Xms128m -Xmx1500m
 org.gradle.configureondemand=true
 org.gradle.daemon=true
 org.gradle.jvmargs=-Xms128m -Xmx1500m
 org.gradle.configureondemand=true
+android.useAndroidX=true
+ksp.incremental=false
index 374585553662a55219dc2f375458e4cb4db35db0..b3497272b316d215d0731d4299d41673c6d1621f 100644 (file)
@@ -1,6 +1,6 @@
 #Thu Sep 26 22:30:23 NOVT 2019
 distributionBase=GRADLE_USER_HOME
 #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
 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
 
 #!/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
 ##############################################################################
 ##
 ##  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
 @if "%DEBUG%" == "" @echo off
 @rem ##########################################################################
 @rem
diff --git a/make-release.sh b/make-release.sh
new file mode 100755 (executable)
index 0000000..23acc64
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+if [[ ! $1 ]]; then
+  echo "usage: $0 versionName"
+  exit
+fi
+
+./require-clean-work-tree.sh "$0" || exit 1
+
+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-clean-work-tree.sh b/require-clean-work-tree.sh
new file mode 100755 (executable)
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/settings.gradle b/settings.gradle
deleted file mode 100644 (file)
index 77ae463..0000000
+++ /dev/null
@@ -1 +0,0 @@
-include 'desktop', 'android', 'core'
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644 (file)
index 0000000..8b9f274
--- /dev/null
@@ -0,0 +1,3 @@
+include("android")
+include("desktop")
+include("core")
diff --git a/up-version.sh b/up-version.sh
new file mode 100755 (executable)
index 0000000..80fa78e
--- /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/\(const val versionName = \)\".*\"/\1\"'"$new_version"'\"/g' buildSrc/src/main/kotlin/ApplicationInfo.kt
+sed -i 's/\(\s*const val versionCode = \)\([0-9]*\)/echo "\1$((\2+1))"/ge' buildSrc/src/main/kotlin/ApplicationInfo.kt
+sed -i 's/\(const val VERSION = \)\".*\"/\1\"'"$new_version_string"'\"/' core/src/main/kotlin/ru/deadsoftware/cavedroid/CaveGame.kt
+
+git add buildSrc/src/main/kotlin/ApplicationInfo.kt core/src/main/kotlin/ru/deadsoftware/cavedroid/CaveGame.kt
+
+git commit -m "Update version"
+git tag "$new_version"
+q
\ No newline at end of file