From: fredboy Date: Tue, 14 May 2024 21:46:01 +0000 (+0700) Subject: Update README X-Git-Url: https://deadsoftware.ru/gitweb?p=cavedroid.git;a=commitdiff_plain;h=refs%2Fheads%2Fmaster;hp=16b10981ea5bc34da1a8d63c70acf03faef8fc92 Update README --- diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..6621b01 --- /dev/null +++ b/.github/workflows/android.yml @@ -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 diff --git a/.gitignore b/.gitignore index ac624d2..b24a8ed 100644 --- a/.gitignore +++ b/.gitignore @@ -116,3 +116,8 @@ Thumbs.db !/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 37400b6..90ecac3 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,13 @@ 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/ + +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 diff --git a/README.md b/README.md index b0f7525..bf5e371 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,20 @@ # 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)
+[![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)
2D Minecraft clone for Android and Desktop.
Written in Java using libGDX framework.
+
+ Screenshot + +![Screenshot](https://fredboy.ru/pub/cavedroid/screenshot.png) + +
+ ## Binary releases You can download apk and jar from here:
## Build instructions +You need to publish [my ksp processor](https://github.com/fredboy/automultibind) to mavenLocal repository first. ### Android To build for Android use
`./gradlew android:assemble`
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 47eaf7a..9328a4e 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,6 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> variant.outputs.all { outputFileName = "android-${versionName}.apk" } } + + signingConfigs { + release_config { + storeFile file(keystoreProperties['releaseKeystorePath']) + storePassword keystoreProperties['releaseKeystorePassword'] + keyAlias keystoreProperties['releaseKeyAlias'] + keyPassword keystoreProperties['releaseKeyPassword'] + } + } + buildTypes { release { minifyEnabled false + signingConfig signingConfigs.release_config } debug { applicationIdSuffix ".debug" } } + buildFeatures { + buildConfig true + } } @@ -101,4 +128,18 @@ task run(type: Exec) { def adb = path + "/platform-tools/adb" commandLine "$adb", 'shell', 'am', 'start', '-n', 'ru.deadsoftware.cavedroid/ru.deadsoftware.cavedroid.AndroidLauncher' +} + +dependencies { + implementation project(":core") + implementation platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion") + api "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64" + + configurations.implementation { + exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8' + } } \ No newline at end of file diff --git a/android/src/ru/deadsoftware/cavedroid/AndroidLauncher.java b/android/src/ru/deadsoftware/cavedroid/AndroidLauncher.java index fcf0831..3f38ad4 100644 --- a/android/src/ru/deadsoftware/cavedroid/AndroidLauncher.java +++ b/android/src/ru/deadsoftware/cavedroid/AndroidLauncher.java @@ -10,7 +10,6 @@ public class AndroidLauncher extends AndroidApplication { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); - config.hideStatusBar = true; config.useImmersiveMode = true; String gameFolder = ""; try { @@ -19,7 +18,8 @@ public class AndroidLauncher extends AndroidApplication { e.printStackTrace(); exit(); } - CaveGame caveGame = new CaveGame(gameFolder, true); + CaveGame caveGame = new CaveGame(gameFolder, true, + new AndroidPreferencesStore(getApplicationContext()), null); caveGame.setDebug(BuildConfig.DEBUG); initialize(caveGame, config); } diff --git a/android/src/ru/deadsoftware/cavedroid/AndroidPreferencesStore.kt b/android/src/ru/deadsoftware/cavedroid/AndroidPreferencesStore.kt new file mode 100644 index 0000000..4b16659 --- /dev/null +++ b/android/src/ru/deadsoftware/cavedroid/AndroidPreferencesStore.kt @@ -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/build.gradle b/build.gradle index ed846ef..1e317b0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,78 +1,43 @@ buildscript { + ext { + appName = "CaveDroid" + + gdxVersion = '1.12.0' + + guavaVersion = '28.1' + + daggerVersion = '2.51.1' + + kotlinVersion = '1.9.24' + kspVersion = '1.9.24-1.0.20' + kotlinSerializationVersion = '1.6.3' + + kotlinpoetKspVersion = '1.16.0' + } repositories { mavenLocal() mavenCentral() maven { url "https://plugins.gradle.org/m2/" } maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } - jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72" + classpath 'com.android.tools.build:gradle:8.2.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } } allprojects { - - version = 'alpha0.4' - ext { - appName = "CaveDroid" - gdxVersion = '1.9.10' - roboVMVersion = '2.3.7' - box2DLightsVersion = '1.4' - ashleyVersion = '1.7.0' - aiVersion = '1.8.0' - guavaVersion = '28.1' - } + version = 'alpha0.9.2' repositories { mavenLocal() mavenCentral() - jcenter() google() maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } maven { url "https://oss.sonatype.org/content/repositories/releases/" } + maven { url "https://jitpack.io" } } } - -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/core/build.gradle b/core/build.gradle index f17b102..7aff468 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,12 +1,26 @@ plugins { + id "java-library" id "org.jetbrains.kotlin.jvm" - id "java" + id "kotlin" id "idea" - id "net.ltgt.apt" version "0.21" + id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlinVersion" + id 'com.google.devtools.ksp' version "$kspVersion" } -sourceCompatibility = 1.8 +java.targetCompatibility = JavaVersion.VERSION_17 +java.sourceCompatibility = JavaVersion.VERSION_17 -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' +sourceSets.main.java.srcDirs = ["src/"] -sourceSets.main.java.srcDirs = [ "src/" ] \ No newline at end of file +dependencies { + implementation "ru.fredboy:automultibind-annotations:1.0.0" + ksp "ru.fredboy:automultibind-ksp:1.0.0" + + api "com.badlogicgames.gdx:gdx:$gdxVersion" + api "com.google.guava:guava:$guavaVersion-android" + api "com.google.dagger:dagger:$daggerVersion" + implementation 'org.jetbrains:annotations:23.1.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion" + annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/CaveGame.java b/core/src/ru/deadsoftware/cavedroid/CaveGame.java index 031b613..9ca34b8 100644 --- a/core/src/ru/deadsoftware/cavedroid/CaveGame.java +++ b/core/src/ru/deadsoftware/cavedroid/CaveGame.java @@ -1,30 +1,49 @@ package ru.deadsoftware.cavedroid; +import com.badlogic.gdx.Application; import com.badlogic.gdx.Game; import com.badlogic.gdx.Gdx; -import ru.deadsoftware.cavedroid.game.GameItems; +import com.badlogic.gdx.Graphics; import ru.deadsoftware.cavedroid.game.GameScreen; import ru.deadsoftware.cavedroid.misc.Assets; +import ru.deadsoftware.cavedroid.misc.utils.AssetLoader; +import ru.deadsoftware.cavedroid.prefs.PreferencesStore; + +import javax.annotation.Nullable; public class CaveGame extends Game { private static final String TAG = "CaveGame"; - public static final String VERSION = "alpha 0.4"; + public static final String VERSION = "alpha 0.9.2"; private final MainConfig mMainConfig; private final MainComponent mMainComponent; + private final AssetLoader mAssetLoader; private final String mGameFolder; private final boolean mTouch; private boolean mDebug; - public CaveGame(String gameFolder, boolean touch) { + @Nullable + private final String mAssetsPackPath; + + public CaveGame(String gameFolder, + boolean touch, + PreferencesStore preferencesStore, + @Nullable String assetsPackPath) { mGameFolder = gameFolder; mTouch = touch; + mAssetsPackPath = assetsPackPath; + + mMainComponent = DaggerMainComponent + .builder() + .caveGame(this) + .preferencesStore(preferencesStore) + .build(); - mMainComponent = DaggerMainComponent.builder().caveGame(this).build(); mMainConfig = mMainComponent.getMainConfig(); + mAssetLoader = mMainComponent.getAssetLoader(); } public void setDebug(boolean debug) { @@ -32,7 +51,7 @@ public class CaveGame extends Game { } private void initConfig() { - int width = mTouch ? 320 : 480; + int width = 480; int height = (int) (width * ((float) Gdx.graphics.getHeight() / Gdx.graphics.getWidth())); mMainConfig.setMainComponent(mMainComponent); @@ -41,11 +60,26 @@ public class CaveGame extends Game { mMainConfig.setWidth(width); mMainConfig.setHeight(height); mMainConfig.setShowInfo(mDebug); + mMainConfig.setAssetsPackPath(mAssetsPackPath); + + if (mDebug) { + Gdx.app.setLogLevel(Application.LOG_DEBUG); + } else { + Gdx.app.setLogLevel(Application.LOG_ERROR); + } + + mMainConfig.setFullscreenToggleListener((value) -> { + if (value) { + Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); + } else { + Gdx.graphics.setWindowedMode(width, height); + } + }); } - public void newGame() { + public void newGame(int gameMode) { GameScreen gameScreen = mMainComponent.getGameScreen(); - gameScreen.newGame(); + gameScreen.newGame(gameMode); setScreen(gameScreen); } @@ -56,20 +90,27 @@ public class CaveGame extends Game { } public void quitGame() { + if (screen != null) { + screen.dispose(); + } setScreen(mMainComponent.getMenuScreen()); } @Override public void create() { - Gdx.app.log(TAG, mGameFolder); Gdx.files.absolute(mGameFolder).mkdirs(); - - Assets.load(); - GameItems.load(); - initConfig(); + Gdx.app.debug(TAG, mGameFolder); + Assets.load(mAssetLoader); setScreen(mMainComponent.getMenuScreen()); } + @Override + public void dispose() { + if (screen != null) { + screen.dispose(); + } + Assets.dispose(); + } } diff --git a/core/src/ru/deadsoftware/cavedroid/MainComponent.java b/core/src/ru/deadsoftware/cavedroid/MainComponent.java index d8a05da..b487c6d 100644 --- a/core/src/ru/deadsoftware/cavedroid/MainComponent.java +++ b/core/src/ru/deadsoftware/cavedroid/MainComponent.java @@ -3,15 +3,19 @@ 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) +@Component(dependencies = {CaveGame.class, PreferencesStore.class}) public interface MainComponent { GameScreen getGameScreen(); MenuScreen getMenuScreen(); MainConfig getMainConfig(); + + AssetLoader getAssetLoader(); } diff --git a/core/src/ru/deadsoftware/cavedroid/MainConfig.java b/core/src/ru/deadsoftware/cavedroid/MainConfig.java index 8cd0ffd..3ffb506 100644 --- a/core/src/ru/deadsoftware/cavedroid/MainConfig.java +++ b/core/src/ru/deadsoftware/cavedroid/MainConfig.java @@ -1,19 +1,32 @@ package ru.deadsoftware.cavedroid; import ru.deadsoftware.cavedroid.game.GameUiWindow; +import ru.deadsoftware.cavedroid.game.input.Joystick; +import ru.deadsoftware.cavedroid.prefs.PreferencesStore; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; +import java.util.HashMap; @Singleton public class MainConfig { + private final HashMap mPreferencesCache = new HashMap<>(); + + @CheckForNull + private FullscreenToggleListener mFullscreenToggleListener = null; + private final CaveGame mCaveGame; + private final PreferencesStore mPreferencesStore; @CheckForNull private MainComponent mMainComponent; + @CheckForNull + private Joystick mJoystick; + private GameUiWindow mGameUiWindow; private String mGameFolder; @@ -24,9 +37,13 @@ public class MainConfig { private float mWidth; private float mHeight; + @Nullable + private String mAssetsPackPath = null; + @Inject - public MainConfig(CaveGame caveGame) { + public MainConfig(CaveGame caveGame, PreferencesStore preferencesStore) { mCaveGame = caveGame; + mPreferencesStore = preferencesStore; mGameUiWindow = GameUiWindow.NONE; mGameFolder = ""; @@ -49,10 +66,6 @@ public class MainConfig { return mGameUiWindow == gameUiWindow; } - public GameUiWindow getGameUiWindow() { - return mGameUiWindow; - } - public void setGameUiWindow(GameUiWindow gameUiWindow) { mGameUiWindow = gameUiWindow; } @@ -104,4 +117,55 @@ public class MainConfig { public void setShowMap(boolean showMap) { mShowMap = showMap; } + + @Nullable + public String getAssetsPackPath() { + return mAssetsPackPath; + } + + public void setAssetsPackPath(@Nullable String assetsPackPath) { + mAssetsPackPath = assetsPackPath; + } + + @CheckForNull + public Joystick getJoystick() { + return mJoystick; + } + + public void setJoystick(@CheckForNull Joystick joystick) { + mJoystick = joystick; + } + + @CheckForNull + 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/ru/deadsoftware/cavedroid/game/GameComponent.java b/core/src/ru/deadsoftware/cavedroid/game/GameComponent.java index 25779f0..94d37d4 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/GameComponent.java +++ b/core/src/ru/deadsoftware/cavedroid/game/GameComponent.java @@ -2,11 +2,21 @@ 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) +@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(); - GameInputProcessor getGameInputProcessor(); + GameItemsHolder getGameItemsHolder(); } diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameFluidsThread.java b/core/src/ru/deadsoftware/cavedroid/game/GameFluidsThread.java deleted file mode 100644 index c3cdd47..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/GameFluidsThread.java +++ /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 index ca60317..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/GameInput.java +++ /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 index 51cc897..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/GameInputProcessor.java +++ /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 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 index 85f4b9c..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/GameItems.java +++ /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 blocksIds = new HashMap<>(); - private static final HashMap itemsIds = new HashMap<>(); - - private static final ArrayMap blocks = new ArrayMap<>(); - private static final ArrayMap items = new ArrayMap<>(); - - public static boolean isFluid(int id) { - return getBlock(id).isFluid(); - } - - public static boolean isWater(int id) { - return getBlock(id).getMeta().equals("water"); - } - - public static boolean isLava(int id) { - return getBlock(id).getMeta().equals("lava"); - } - - public static boolean isSlab(int id) { - return getBlock(id).getMeta().equals("slab"); - } - - public static boolean fluidCanFlowThere(int thisId, int thatId) { - return thatId == 0 || (!getBlock(thatId).hasCollision() && !isFluid(thatId)) || - (isWater(thisId) && isWater(thatId) && thisId < thatId) || - (isLava(thisId) && isLava(thatId) && thisId < thatId); - } - - public static Block getBlock(int id) { - return blocks.getValueAt(id); - } - - public static Item getItem(int id) { - return items.getValueAt(id); - } - - public static Block getBlock(String key) { - return blocks.getValueAt(blocksIds.get(key)); - } - - public static Item getItem(String key) { - return items.getValueAt(itemsIds.get(key)); - } - - public static int getBlockId(String key) { - return blocksIds.get(key); - } - - public static int getItemId(String key) { - return itemsIds.get(key); - } - - public static String getBlockKey(int id) { - return blocks.getKeyAt(id); - } - - public static String getItemKey(int id) { - return items.getKeyAt(id); - } - - public static int getBlockIdByItemId(int id) { - return getBlockId(items.getKeyAt(id)); - } - - public static int getBlocksSize() { - return blocks.size; - } - - public static int getItemsSize() { - return items.size; - } - - public static Sprite getBlockTex(int id) { - return getBlock(id).getTexture(); - } - - public static Sprite getItemTex(int id) { - return items.getValueAt(id).getType().equals("block") ? getBlockTex(id) : getItem(id).getTexture(); - } - - public static void load() { - JsonValue json = Assets.jsonReader.parse(Gdx.files.internal("json/game_items.json")); - for (JsonValue block = json.get("blocks").child(); block != null; block = block.next()) { - try { - String key = block.name(); - int left = Assets.getIntFromJson(block, "left", 0); - int right = Assets.getIntFromJson(block, "right", 0); - int top = Assets.getIntFromJson(block, "top", 0); - int bottom = Assets.getIntFromJson(block, "bottom", 0); - int clipX = Assets.getIntFromJson(block, "sprite_left", 0); - int clipY = Assets.getIntFromJson(block, "sprite_top", 0); - int clipWidth = Assets.getIntFromJson(block, "sprite_right", 0); - int clipHeight = Assets.getIntFromJson(block, "sprite_bottom", 0); - int hp = Assets.getIntFromJson(block, "hp", -1); - boolean collision = Assets.getBooleanFromJson(block, "collision", true); - boolean background = Assets.getBooleanFromJson(block, "background", false); - boolean transparent = Assets.getBooleanFromJson(block, "transparent", false); - boolean requiresBlock = Assets.getBooleanFromJson(block, "block_required", false); - boolean fluid = Assets.getBooleanFromJson(block, "fluid", false); - String drop = Assets.getStringFromJson(block, "drop", key); - String meta = Assets.getStringFromJson(block, "meta", ""); - String tex = Assets.getStringFromJson(block, "texture", key); - Texture texture = tex.equals("none") ? null : - new Texture(Gdx.files.internal("textures/blocks/" + tex + ".png")); - boolean animated = Assets.getBooleanFromJson(block, "animated", false); - int frames = Assets.getIntFromJson(block, "frames", 0); - - Block newBlock = new Block( - left, - top, - right, - bottom, - hp, - drop, - collision, - background, - transparent, - requiresBlock, - fluid, - meta, - texture, - animated, - frames, - clipX, - clipY, - clipWidth, - clipHeight - ); - - blocksIds.put(key, blocks.size); - blocks.put(key, newBlock); - } catch (GdxRuntimeException e) { - Gdx.app.error(TAG, e.getMessage()); - } - } - for (JsonValue item = json.get("items").child(); item != null; item = item.next()) { - try { - String key = item.name(); - String name = Assets.getStringFromJson(item, "name", key); - String type = Assets.getStringFromJson(item, "type", "item"); - String texture = Assets.getStringFromJson(item, "texture", key); - Sprite sprite = type.equals("block") ? null : - new Sprite(new Texture(Gdx.files.internal("textures/items/" + texture + ".png"))); - itemsIds.put(key, items.size); - items.put(key, new Item(name, type, sprite)); - } catch (GdxRuntimeException e) { - Gdx.app.error(TAG, e.getMessage()); - } - } - } - -} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt b/core/src/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt new file mode 100644 index 0000000..050ae6f --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt @@ -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() + private val itemsMap = LinkedHashMap() + private val craftingRecipes = LinkedList() + + lateinit var fallbackBlock: Block + private set + lateinit var fallbackItem: Item + private set + + init { + initialize() + } + + private fun loadBlocks(dtoMap: Map) { + 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) { + 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>(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(jsonString) + + loadBlocks(gameItemsDto.blocks) + loadItems(gameItemsDto.items) + + _initialized = true + + loadCraftingRecipes() + } + + private fun Map.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): 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 { + 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 getBlocksByType(type: Class): List { + 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/ru/deadsoftware/cavedroid/game/GameModule.java b/core/src/ru/deadsoftware/cavedroid/game/GameModule.java index d7ccc2d..d57ab33 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/GameModule.java +++ b/core/src/ru/deadsoftware/cavedroid/game/GameModule.java @@ -4,7 +4,11 @@ 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 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.ui.TooltipManager; +import ru.deadsoftware.cavedroid.game.world.GameWorld; import javax.annotation.CheckForNull; @@ -14,8 +18,14 @@ public class GameModule { @CheckForNull private static GameSaver.Data data; - public static void load(MainConfig mainConfig) { - data = GameSaver.load(mainConfig); + public static boolean loaded = false; + + private static void load(MainConfig mainConfig, GameItemsHolder gameItemsHolder) { + if (loaded) { + return; + } + data = GameSaver.load(mainConfig, gameItemsHolder); + loaded = true; } private static void makeDataNullIfEmpty() { @@ -26,27 +36,50 @@ public class GameModule { @Provides @GameScope - public static DropController provideDropController() { + public static DropController provideDropController(MainConfig mainConfig, GameItemsHolder gameItemsHolder) { + load(mainConfig, gameItemsHolder); DropController controller = data != null ? data.retrieveDropController() : new DropController(); makeDataNullIfEmpty(); + controller.initDrops(gameItemsHolder); + return controller; + } + + @Provides + @GameScope + public static ContainerController provideFurnaceController(MainConfig mainConfig, DropController dropController, GameItemsHolder gameItemsHolder) { + load(mainConfig, gameItemsHolder); + ContainerController controller = data != null ? data.retrieveFurnaceController() : new ContainerController(dropController, gameItemsHolder); + makeDataNullIfEmpty(); + controller.init(dropController, gameItemsHolder); return controller; } @Provides @GameScope - public static MobsController provideMobsController() { - MobsController controller = data != null ? data.retrieveMobsController() : new MobsController(); + public static MobsController provideMobsController(MainConfig mainConfig, + GameItemsHolder gameItemsHolder, + TooltipManager tooltipManager) { + load(mainConfig, gameItemsHolder); + MobsController controller = data != null + ? data.retrieveMobsController() + : new MobsController(gameItemsHolder, tooltipManager); makeDataNullIfEmpty(); + controller.getPlayer().initInventory(gameItemsHolder, tooltipManager); return controller; } @Provides @GameScope - public static GameWorld provideGameWorld(DropController dropController, MobsController mobsController) { - int[][] fm = data != null ? data.retrieveForeMap() : null; - int[][] bm = data != null ? data.retrieveBackMap() : null; + public static GameWorld provideGameWorld(MainConfig mainConfig, + DropController dropController, + MobsController mobsController, + GameItemsHolder gameItemsHolder, + ContainerController containerController) { + load(mainConfig, gameItemsHolder); + Block[][] fm = data != null ? data.retrieveForeMap() : null; + Block[][] bm = data != null ? data.retrieveBackMap() : null; makeDataNullIfEmpty(); - return new GameWorld(dropController, mobsController, fm, bm); + return new GameWorld(dropController, mobsController, gameItemsHolder, containerController, fm, bm); } } diff --git a/core/src/ru/deadsoftware/cavedroid/game/GamePhysics.java b/core/src/ru/deadsoftware/cavedroid/game/GamePhysics.java index d6b5a9a..cb5b37e 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/GamePhysics.java +++ b/core/src/ru/deadsoftware/cavedroid/game/GamePhysics.java @@ -7,35 +7,42 @@ 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 ru.deadsoftware.cavedroid.game.mobs.player.Player; +import ru.deadsoftware.cavedroid.game.model.block.Block; +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.annotation.CheckForNull; import javax.inject.Inject; import java.util.Iterator; @GameScope -class GamePhysics { +public class GamePhysics { - static final int PL_SPEED = 2; + public static final float PL_JUMP_VELOCITY = -133.332f; + public static final float PL_TERMINAL_VELOCITY = 1254.4f; - private final Vector2 gravity = new Vector2(0, .9f); + private final Vector2 gravity = new Vector2(0, 444.44f); private final GameWorld mGameWorld; private final MainConfig mMainConfig; private final MobsController mMobsController; private final DropController mDropController; + private final GameItemsHolder mGameItemsHolder; @Inject public GamePhysics(GameWorld gameWorld, MainConfig mainConfig, MobsController mobsController, - DropController dropController) { + DropController dropController, + GameItemsHolder gameItemsHolder) { mGameWorld = gameWorld; mMainConfig = mainConfig; mMobsController = mobsController; mDropController = dropController; + mGameItemsHolder = gameItemsHolder; } /** @@ -47,17 +54,21 @@ class GamePhysics { int dir = mob.looksLeft() ? 0 : 1; int blX = (int) (mob.getX() + mob.getWidth() * dir - 8 + 16 * dir); int blY = (int) (mob.getY() + mob.getHeight() - 8); - int block = mGameWorld.getForeMap(blX / 16, blY / 16); + Block block = mGameWorld.getForeMap(blX / 16, blY / 16); - if (checkColl(new Rectangle(blX, mob.getY() - 18, mob.getWidth(), mob.getHeight()))) { - block = 0; + if (checkColl(new Rectangle(blX, mob.getY() - 18, mob.getWidth(), mob.getHeight())) != null) { + return false; } - return (block > 0 && GameItems.getBlock(block).toJump() && - (mob.getY() + mob.getHeight()) - GameItems.getBlock(block).getRectangle(blX / 16, blY / 16).y > 8); + return (block.toJump() && + (mob.getY() + mob.getHeight()) - block.getRectangle(blX / 16, blY / 16).y > 8); } - private boolean checkColl(Rectangle rect) { + /** + * @return colliding rect or null if no collision + */ + @CheckForNull + 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; @@ -71,79 +82,138 @@ class GamePhysics { maxY = mGameWorld.getHeight(); } - int block; + Block block; for (int y = minY; y < maxY; y++) { for (int x = minX; x < maxX; x++) { + if (!mGameWorld.hasForeAt(x, y)) { + continue; + } block = mGameWorld.getForeMap(x, y); - if (block > 0 && GameItems.getBlock(block).hasCollision()) { - if (Intersector.overlaps(rect, GameItems.getBlock(block).getRectangle(x, y))) { - return true; + if (block.hasCollision()) { + final Rectangle blockRect = block.getRectangle(x, y); + if (Intersector.overlaps(rect, blockRect)) { + return blockRect; } } } } - return false; + return null; } - private int getBlock(Rectangle rect) { + private Block getBlock(Rectangle rect) { return mGameWorld.getForeMap((int) (rect.x + rect.width / 2) / 16, (int) (rect.y + rect.height / 8 * 7) / 16); } - private void dropPhy(Drop drop) { - int dropToPlayer = drop.closeToPlayer(mGameWorld, mMobsController.getPlayer()); - if (dropToPlayer > 0) { - drop.moveToPlayer(mGameWorld, mMobsController.getPlayer(), dropToPlayer); + private Rectangle getShiftedPlayerRect(float shift) { + final Player player = mMobsController.getPlayer(); + return new Rectangle(player.x + shift, player.y, player.width, player.height); + } + + /** + * @return Rectangle representing magneting target for this drop + */ + @CheckForNull + private Rectangle getShiftedMagnetingPlayerRect(Drop drop) { + final Player player = mMobsController.getPlayer(); + + if (!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 { - if (drop.getMove().x >= .5f) { - drop.getMove().x -= .5f; - } else if (drop.getMove().x <= -.5f) { - drop.getMove().x += .5f; - } else { - drop.getMove().x = 0; - } - if (drop.getMove().y < 9) { - drop.getMove().y += gravity.y / 4; - } + dropVelocity.y += gravity.y * delta; } - drop.move(); + dropVelocity.x = MathUtils.clamp(dropVelocity.x, -Drop.MAGNET_VELOCITY, Drop.MAGNET_VELOCITY); + dropVelocity.y = MathUtils.clamp(dropVelocity.y, -Drop.MAGNET_VELOCITY, Drop.MAGNET_VELOCITY); + + drop.x += dropVelocity.x * delta; + drop.y += dropVelocity.y * delta; - if (checkColl(drop)) { - drop.getMove().set(0, -1); + if (checkColl(drop) != null) { + dropVelocity.setZero(); do { - drop.move(); - } while (checkColl(drop)); - drop.getMove().setZero(); + drop.y--; + } while (checkColl(drop) != null); + } + + if (playerMagnetTarget != null) { + pickUpDropIfPossible(playerMagnetTarget, drop); } } private void mobXColl(Mob mob) { - if (checkColl(mob)) { - if (mob.canJump() && !mob.isFlyMode()) { - mob.y -= 8; + if (mob.getVelocity().x == 0f) { + return; + } + + @CheckForNull 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; } - if (checkColl(mob)) { - if (mob.canJump() && !mob.isFlyMode()) { - mob.y += 8; - } + collidingRect = checkColl(mob); + if (collidingRect != null) { int d = 0; - if (mob.getMove().x < 0) { + if (mob.getVelocity().x < 0) { d = 1; - } else if (mob.getMove().x > 0) { + } else if (mob.getVelocity().x > 0) { d = -1; } - mob.x = MathUtils.round(mob.getX()); - - while (checkColl(mob)) { - mob.x += d; + 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(); } @@ -154,28 +224,43 @@ class GamePhysics { } private void mobYColl(Mob mob) { - if (checkColl(mob)) { + @CheckForNull final Rectangle collidingRect = checkColl(mob); + if (collidingRect != null) { int d = -1; - if (mob.getMove().y < 0) { + if (mob.getVelocity().y < 0) { d = 1; } if (d == -1) { mob.setCanJump(true); mob.setFlyMode(false); - } - mob.y = MathUtils.round(mob.getY()); + int dmg = ((int)Math.max(0f, (((mob.getVelocity().y * mob.getVelocity().y) / (2 * gravity.y)) - 48f) / 16f)); + if (dmg > 0) { + mob.damage(dmg); + } + } - while (checkColl(mob)) { - mob.y += d; + if (d < 0) { + mob.y = collidingRect.y - mob.height; + } else { + mob.y = collidingRect.y + collidingRect.height; } - mob.getMove().y = 0; + +// mob.y = MathUtils.round(mob.getY()); +// +// while (checkColl(mob)) { +// mob.y += d; +// } + + mob.getVelocity().y = 0; } else { - mob.setCanJump(false); + mob.y += 1; + mob.setCanJump(checkColl(mob) != null); + mob.y -= 1; } if (mob.getY() > mGameWorld.getHeightPx()) { @@ -183,105 +268,103 @@ class GamePhysics { } } - private void playerPhy(Player player) { - player.y += player.getMove().y; - mobYColl(player); - + private void playerPhy(Player player, float delta) { if (player.isDead()) { return; } - if (GameItems.isFluid(getBlock(player))) { - if (mMainConfig.isTouch() && player.getMove().x != 0 && !player.swim && !player.isFlyMode()) { + if (getBlock(player).isFluid()) { + if (mMainConfig.isTouch() && player.getVelocity().x != 0 && !player.swim && !player.isFlyMode()) { player.swim = true; } if (!player.swim) { - if (!player.isFlyMode() && player.getMove().y < 4.5f) { - player.getMove().add(gravity.x / 4, gravity.y / 4); + if (!player.isFlyMode() && player.getVelocity().y < 32f) { + player.getVelocity().y += gravity.y * delta; } - if (!player.isFlyMode() && player.getMove().y > 4.5f) { - player.getMove().add(0, -1f); + if (!player.isFlyMode() && player.getVelocity().y > 32f) { + player.getVelocity().y -= player.getVelocity().y * 32f * delta; } } else { - player.getMove().add(0, -.5f); - if (player.getMove().y < -3) { - player.getMove().y = -3; + player.getVelocity().y += PL_JUMP_VELOCITY * delta; + if (player.getVelocity().y < -player.getSpeed()) { + player.getVelocity().y = -player.getSpeed(); } } } else { - if (!player.isFlyMode() && player.getMove().y < 18) { - player.getMove().add(gravity); + if (!player.isFlyMode() && player.getVelocity().y < PL_TERMINAL_VELOCITY) { + player.getVelocity().y += gravity.y * delta; } } - player.x += player.getMove().x * (player.isFlyMode() ? 1.5f : 1) * - (GameItems.isFluid(getBlock(player)) && !player.isFlyMode() ? .8f : 1); + player.y += player.getVelocity().y * delta; + mobYColl(player); + + player.x += player.getVelocity().x * (player.isFlyMode() ? 1.5f : 1) * + (getBlock(player).isFluid() && !player.isFlyMode() ? .8f : 1) * delta; mobXColl(player); - if (mMainConfig.isTouch() && !player.isFlyMode() && player.canJump() && player.getMove().x != 0 && checkJump(player)) { - player.getMove().add(0, -8); + if (mMainConfig.isTouch() && !player.isFlyMode() && player.canJump() && player.getVelocity().x != 0 && checkJump(player)) { + player.jump(); player.setCanJump(false); } } - private void mobPhy(Mob mob) { - if (mob.getType() == Mob.Type.MOB && GameItems.isFluid(getBlock(mob))) { - if (mob.getMove().y > 9) { - mob.getMove().add(0, -.9f); + private void mobPhy(Mob mob, float delta) { + if (mob.getType() == Mob.Type.MOB && getBlock(mob).isFluid()) { + if (mob.getVelocity().y > 32f) { + mob.getVelocity().y -= mob.getVelocity().y * 32f * delta; } - mob.getMove().add(0, -.5f); + mob.getVelocity().y += PL_JUMP_VELOCITY * delta; - if (mob.getMove().y < -3) { - mob.getMove().y = -3; + if (mob.getVelocity().y < -mob.getSpeed()) { + mob.getVelocity().y = -mob.getSpeed(); } - } else if (!mob.isFlyMode() && mob.getMove().y < 18) { - mob.getMove().add(gravity); + } else if (!mob.isFlyMode() && mob.getVelocity().y < PL_TERMINAL_VELOCITY) { + mob.getVelocity().y += gravity.y * delta; } - mob.y += mob.getMove().y; + mob.y += mob.getVelocity().y * delta; mobYColl(mob); if (mob.isDead()) { return; } - mob.x += mob.getMove().x; + mob.x += mob.getVelocity().x * delta; mobXColl(mob); - if (mob.canJump() && mob.getMove().x != 0 && checkJump(mob)) { - mob.getMove().add(0, -8); + if (mob.canJump() && mob.getVelocity().x != 0 && checkJump(mob)) { + mob.jump(); mob.setCanJump(false); } } - void update() { + void update(float delta) { Player player = mMobsController.getPlayer(); for (Iterator it = mDropController.getIterator(); it.hasNext(); ) { Drop drop = it.next(); - dropPhy(drop); - if (Intersector.overlaps(drop, player)) { - drop.pickUpDrop(player); - } - if (drop.isPickedUp()) { + dropPhy(drop, delta); + if (drop.getPickedUp()) { it.remove(); } } - for (Iterator it = mMobsController.getIterator(); it.hasNext(); ) { + for (Iterator it = mMobsController.getMobs().iterator(); it.hasNext(); ) { Mob mob = it.next(); - mob.ai(mGameWorld); - mobPhy(mob); + mob.ai(mGameWorld, mGameItemsHolder, mMobsController, delta); + mobPhy(mob, delta); if (mob.isDead()) { it.remove(); } } - playerPhy(player); + playerPhy(player, delta); + player.ai(mGameWorld, mGameItemsHolder, mMobsController, delta); if (player.isDead()) { - player.respawn(mGameWorld); + player.respawn(mGameWorld, mGameItemsHolder); } } diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameProc.java b/core/src/ru/deadsoftware/cavedroid/game/GameProc.java index 44a4687..29062a4 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/GameProc.java +++ b/core/src/ru/deadsoftware/cavedroid/game/GameProc.java @@ -1,41 +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 { - public static final int MAX_CREATIVE_SCROLL = GameItems.getItemsSize() / 8; - - private final GameWorld mGameWorld; private final GamePhysics mGamePhysics; - private final GameInput mGameInput; private final GameRenderer mGameRenderer; + private final MobsController mMobsController; + private final 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(GameWorld gameWorld, + public GameProc(MainConfig mainConfig, GamePhysics gamePhysics, - GameInput gameInput, - GameRenderer gameRenderer) { - mGameWorld = gameWorld; + GameRenderer gameRenderer, + MobsController mobsController, + ContainerController containerController, + GameItemsHolder gameItemsHolder, + GameWorldFluidsLogicControllerTask gameWorldFluidsLogicControllerTask, + GameWorldBlocksLogicControllerTask gameWorldBlocksLogicControllerTask, + GameWorldMobDamageControllerTask gameWorldMobDamageControllerTask + ) { mGamePhysics = gamePhysics; - mGameInput = gameInput; mGameRenderer = gameRenderer; + mMobsController = mobsController; + mContainerController = containerController; + mGameItemsHolder = gameItemsHolder; + mGameWorldFluidsLogicControllerTask = gameWorldFluidsLogicControllerTask; + mGameWorldBlocksLogicControllerTask = gameWorldBlocksLogicControllerTask; + mGameWorldMobDamageControllerTask = gameWorldMobDamageControllerTask; - mGameWorld.startFluidsThread(); + mobsController.getPlayer().controlMode = mainConfig.isTouch() ? Player.ControlMode.WALK : Player.ControlMode.CURSOR; + + mWorldLogicTimer.scheduleTask(gameWorldFluidsLogicControllerTask, 0, + GameWorldFluidsLogicControllerTask.FLUID_UPDATE_INTERVAL_SEC); + mWorldLogicTimer.scheduleTask(gameWorldBlocksLogicControllerTask, 0, + GameWorldBlocksLogicControllerTask.WORLD_BLOCKS_LOGIC_UPDATE_INTERVAL_SEC); + mWorldLogicTimer.scheduleTask(gameWorldMobDamageControllerTask, 0, + GameWorldMobDamageControllerTask.ENVIRONMENTAL_MOB_DAMAGE_INTERVAL_SEC); + } + + public void setPlayerGameMode(int gameMode) { + mMobsController.getPlayer().gameMode = gameMode; } public void update(float delta) { - mGamePhysics.update(); - mGameInput.update(); - mGameWorld.update(); + mGamePhysics.update(delta); mGameRenderer.render(delta); + mContainerController.update(); + } + + public void show() { + Gdx.input.setInputProcessor(mGameRenderer); } @Override public void dispose() { - mGameWorld.dispose(); + mWorldLogicTimer.stop(); + mGameWorldFluidsLogicControllerTask.cancel(); + mGameWorldBlocksLogicControllerTask.cancel(); + mGameWorldMobDamageControllerTask.cancel(); } } diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameRenderer.java b/core/src/ru/deadsoftware/cavedroid/game/GameRenderer.java index 19f2681..0dcbefc 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/GameRenderer.java +++ b/core/src/ru/deadsoftware/cavedroid/game/GameRenderer.java @@ -1,303 +1,384 @@ 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.graphics.g2d.TextureRegion; -import com.badlogic.gdx.graphics.glutils.ShapeRenderer; 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 ru.deadsoftware.cavedroid.MainConfig; -import ru.deadsoftware.cavedroid.game.mobs.Mob; +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; -import ru.deadsoftware.cavedroid.game.objects.Drop; -import ru.deadsoftware.cavedroid.game.objects.DropController; -import ru.deadsoftware.cavedroid.misc.ControlMode; +import ru.deadsoftware.cavedroid.game.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.annotation.CheckForNull; import javax.inject.Inject; - -import static ru.deadsoftware.cavedroid.misc.Assets.guiMap; -import static ru.deadsoftware.cavedroid.misc.Assets.textureRegions; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; @GameScope public class GameRenderer extends Renderer { + private static final 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 GameInput mGameInput; - private final GameWorld mGameWorld; private final MobsController mMobsController; - private final DropController mDropController; + private final GameWorld mGameWorld; + private final List mRenderers; + private final CursorMouseInputHandler mCursorMouseInputHandler; + private final MouseInputActionMapper mMouseInputActionMapper; + private final KeyboardInputActionMapper mKeyboardInputActionMapper; + private final Set mMouseInputHandlers; + private final Set 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, - GameInput gameInput, - GameWorld gameWorld, MobsController mobsController, - DropController dropController) { + GameWorld gameWorld, + Set renderers, + CursorMouseInputHandler cursorMouseInputHandler, + MouseInputActionMapper mouseInputActionMapper, + KeyboardInputActionMapper keyboardInputActionMapper, + Set mouseInputHandlers, + Set keyboardInputHandlers, + GameWindowsManager gameWindowsManager, + TooltipManager tooltipManager) { super(mainConfig.getWidth(), mainConfig.getHeight()); mMainConfig = mainConfig; - mGameInput = gameInput; - mGameWorld = gameWorld; mMobsController = mobsController; - mDropController = dropController; + mGameWorld = gameWorld; + mRenderers = new ArrayList<>(renderers); + mRenderers.sort(Comparator.comparingInt(IGameRenderer::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 float drawX(int x) { - return x * 16 - getCamX(); - } + private void updateDynamicCameraPosition(float delta) { + Player player = mMobsController.getPlayer(); - private float drawY(int y) { - return y * 16 - getCamY(); - } + 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; - 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()); + 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; } - } - 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())); - } - } + 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 (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())); - } + + if (TimeUtils.timeSinceMillis(mCameraDelayMs) < 500L && !player.getVelocity().isZero()) { + updateStaticCameraPosition(plTargetX - mCamCenterToPlayer.x, + camCenterY + moveVector.y * delta * 2); + return; } - } - 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; + 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(); } - if (maxY > mGameWorld.getHeight()) { - maxY = mGameWorld.getHeight(); + + 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; } - for (int y = minY; y < maxY; y++) { - for (int x = minX; x < maxX; x++) { - drawBlock(x, y, bg); - } + + if (camY + getHeight() / 2 > plTargetY + MAX_CAM_DISTANCE_FROM_PLAYER) { + camY = plTargetY + MAX_CAM_DISTANCE_FROM_PLAYER - getHeight() / 2; } - 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(); + + 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 drawMob(Mob mob) { - float mobDrawX = mob.getX() - getCamX(); - float mobDrawY = mob.getY() - getCamY(); + private void updateStaticCameraPosition(float targetX, float targetY) { + setCamPos(targetX - getWidth() / 2, targetY - getHeight() / 2); + } - 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; + 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(); } + } - mob.draw(spriter, mobDrawX, mobDrawY); + private float transformScreenX(int screenX) { + return getWidth() / Gdx.graphics.getWidth() * screenX; } - private void drawDrop(Drop drop) { + private float transformScreenY(int screenY) { + return getHeight() / Gdx.graphics.getHeight() * screenY; } - @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); - } - } + 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(@CheckForNull MouseInputAction action) { + if (action == null) { + return false; } - 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); - } + + 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) { + @CheckForNull MouseInputAction action = mMouseInputActionMapper + .map((float) mouseX, (float) mouseY, getCameraViewport(), button, touchUp, pointer); + return handleMouseAction(action); } - private void drawGUI() { - TextureRegion cursor = textureRegions.get("cursor"); - TextureRegion hotbar = textureRegions.get("hotbar"); - TextureRegion hotbarSelector = textureRegions.get("hotbar_selector"); + @Override + public boolean touchUp(int screenX, int screenY, int pointer, int button) { + float touchX = transformScreenX(screenX); + float touchY = transformScreenY(screenY); - 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); - } + 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()); } } - spriter.draw(hotbarSelector, - getWidth() / 2 - (float) hotbar.getRegionWidth() / 2 - 1 + 20 * mMobsController.getPlayer().slot, - -1); + + return onMouseActionEvent(screenX, screenY, button, true, pointer); } - 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); + private TouchButton getTouchedKey(float touchX, float touchY) { + if (mGameWindowsManager.getCurrentWindowType() != GameUiWindow.NONE) { + return nullButton; } - if (mGameInput.getControlMode() == ControlMode.CURSOR) { - spriter.draw(textureRegions.get("shade"), 83, getHeight() - 21); + for (ObjectMap.Entry entry : Assets.guiMap) { + TouchButton button = entry.value; + if (button.getRect().contains(touchX, touchY)) { + return button; + } } - } - private void drawGamePlay() { - Player player = mMobsController.getPlayer(); + if (mouseLeftTouchButton.getRect().contains(touchX, touchY)) { + return mouseLeftTouchButton; + } - drawWorld(true); - player.draw(spriter, player.getX() - getCamX() - player.getWidth() / 2, player.getY() - getCamY()); - mMobsController.forEach(this::drawMob); - mDropController.forEach(this::drawDrop); - drawWorld(false); - drawGUI(); - } + if (mouseRightTouchButton.getRect().contains(touchX, touchY)) { + return mouseRightTouchButton; + } - private void updateCameraPosition() { - Player player = mMobsController.getPlayer(); - setCamPos(player.getX() + player.getWidth() / 2 - getWidth() / 2, - player.getY() + player.getHeight() / 2 - getHeight() / 2); + return nullButton; } @Override - public void render(float delta) { - int fps = (int) (1 / delta); - updateCameraPosition(); - mGameInput.moveCursor(this); + public boolean touchDown(int screenX, int screenY, int pointer, int button) { + float touchX = transformScreenX(screenX); + float touchY = transformScreenY(screenY); - Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + mTouchDownX = touchX; + mTouchDownY = touchY; - spriter.begin(); + if (mMainConfig.isTouch()) { + TouchButton touchedKey = getTouchedKey(touchX, touchY); + if (touchedKey.isMouse()) { + return onMouseActionEvent(screenX, screenY, touchedKey.getCode(), false, pointer); + } else { + return keyDown(touchedKey.getCode()); + } + } - drawGamePlay(); + return onMouseActionEvent(screenX, screenY, button, false, pointer); + } - switch (mMainConfig.getGameUiWindow()) { - case CREATIVE_INVENTORY: - drawCreative(); - break; - //TODO draw other ui windows + @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; } + @CheckForNull MouseInputAction action = + mMouseInputActionMapper.mapDragged(screenX, screenY, getCameraViewport(), pointer); + return handleMouseAction(action); + } - if (mMainConfig.isTouch()) { - drawTouchGui(); + @Override + public boolean scrolled(float amountX, float amountY) { + @CheckForNull MouseInputAction action = mMouseInputActionMapper + .mapScrolled(Gdx.input.getX(), Gdx.input.getY(), amountX, amountY, getCameraViewport()); + return handleMouseAction(action); + } + + private boolean handleKeyboardAction(int keycode, boolean isKeyDown) { + @CheckForNull final KeyboardInputAction action = mKeyboardInputActionMapper + .map(keycode, isKeyDown); + + if (action == null) { + return false; } - spriter.end(); + boolean anyProcessed = false; - if (mMainConfig.isShowMap()) { - //DRAW MAP - shaper.begin(ShapeRenderer.ShapeType.Filled); - shaper.setColor(Color.LIGHT_GRAY); - shaper.rect(0, 0, mGameWorld.getWidth(), 128); - for (int y = 128; y < 256; y++) { - for (int x = 0; x < getWidth(); x++) { - if (mGameWorld.hasForeAt(x, y) || mGameWorld.hasBackAt(x, y)) { - if (GameItems.isWater(mGameWorld.getForeMap(x, y))) { - shaper.setColor(Color.BLUE); - } else if (GameItems.isLava(mGameWorld.getForeMap(x, y))) { - shaper.setColor(Color.RED); - } else { - if (mGameWorld.hasForeAt(x, y)) { - shaper.setColor(Color.BLACK); - } else { - shaper.setColor(Color.DARK_GRAY); - } - } - shaper.rect(x, y - 128, 1, 1); - } - } + for (IKeyboardInputHandler handler : mKeyboardInputHandlers) { + final boolean conditions = handler.checkConditions(action); + if (conditions) { + anyProcessed = true; + handler.handle(action); + break; } - 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(); + 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(); + mRenderers.forEach(iGameRenderer -> iGameRenderer.draw(spriter, shaper, getCameraViewport(), delta)); + handleMousePosition(); + spriter.end(); + } } diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameSaver.java b/core/src/ru/deadsoftware/cavedroid/game/GameSaver.java index 1ff2bad..39d2278 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/GameSaver.java +++ b/core/src/ru/deadsoftware/cavedroid/game/GameSaver.java @@ -4,25 +4,39 @@ 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 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.world.GameWorld; import javax.annotation.CheckForNull; import java.io.*; import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; public class GameSaver { + private static final String TAG = "GameSaver"; + public static class Data { @CheckForNull private MobsController mMobsController; @CheckForNull private DropController mDropController; @CheckForNull - private int[][] mForeMap, mBackMap; + private ContainerController mContainerController; + @CheckForNull + private Block[][] mForeMap, mBackMap; - public Data(MobsController mobsController, DropController dropController, int[][] foreMap, int[][] backMap) { + public Data(MobsController mobsController, + DropController dropController, + ContainerController containerController, + Block[][] foreMap, + Block[][] backMap) { mMobsController = mobsController; mDropController = dropController; + mContainerController = containerController; mForeMap = foreMap; mBackMap = backMap; } @@ -41,22 +55,33 @@ public class GameSaver { return dropController; } - public int[][] retrieveForeMap() { + public ContainerController retrieveFurnaceController() { + assert mContainerController != null; + ContainerController containerController = mContainerController; + mContainerController = null; + return containerController; + } + + public Block[][] retrieveForeMap() { assert mForeMap != null; - int[][] foreMap = mForeMap; + Block[][] foreMap = mForeMap; mForeMap = null; return foreMap; } - public int[][] retrieveBackMap() { + public Block[][] retrieveBackMap() { assert mBackMap != null; - int[][] backMap = mBackMap; + Block[][] backMap = mBackMap; mBackMap = null; return backMap; } public boolean isEmpty() { - return mMobsController == null && mDropController == null && mForeMap == null && mBackMap == null; + return mMobsController == null && + mDropController == null && + mContainerController == null && + mForeMap == null && + mBackMap == null; } } @@ -66,56 +91,96 @@ public class GameSaver { return ByteBuffer.allocate(4).putInt(i).array(); } - private static void saveMap(FileHandle file, int[][] map) throws IOException { + private static Map buildBlocksDictionary(Block[][] foreMap, Block[][] backMap) { + final HashMap dict = new HashMap<>(); + + int id = 0; + for (int i = 0; i < foreMap.length; i++) { + for (int j = 0; j < foreMap[i].length; j++) { + for (int k = 0; k < 2; k++) { + final Block block = k == 0 ? foreMap[i][j] : backMap[i][j]; + final String key = block.getParams().getKey(); + if (!dict.containsKey(key)) { + dict.put(key, id++); + } + } + } + } + + return dict; + } + + private static void saveDict(FileHandle file, Map dict) { + final String[] arr = new String[dict.size()]; + + for (Map.Entry entry : dict.entrySet()) { + arr[entry.getValue()] = entry.getKey(); + } + + final StringBuilder builder = new StringBuilder(); + for (String key : arr) { + builder.append(key); + builder.append('\n'); + } + + file.writeString(builder.toString(), false); + } + + private static String[] loadDict(FileHandle file) { + return file.readString().split("\n"); + } + + private static void saveMap(FileHandle file, Block[][] map, Map dict) throws IOException { int run, block; int width = map.length; int height = map[0].length; BufferedOutputStream out = new BufferedOutputStream(file.write(false)); - out.write(intToBytes(SAVE_VERSION)); + out.write(SAVE_VERSION); out.write(intToBytes(width)); out.write(intToBytes(height)); for (int y = 0; y < height; y++) { - block = map[0][y]; + block = dict.get(map[0][y].getParams().getKey()); run = 0; - for (int[] ints : map) { - if (ints[y] != block) { - out.write(intToBytes(run)); - out.write(intToBytes(block)); + for (Block[] blocks : map) { + int newValue = dict.get(blocks[y].getParams().getKey()); + if (run >= 0xFF || newValue != block) { + out.write(run); + out.write(block); run = 0; - block = ints[y]; + block = dict.get(blocks[y].getParams().getKey()); } run++; } - out.write(intToBytes(run)); - out.write(intToBytes(block)); + out.write(run); + out.write(block); } out.flush(); out.close(); } - private static int[][] loadMap(FileHandle file) throws Exception { - int[][] map; + private static Block[][] loadMap(GameItemsHolder gameItemsHolder, FileHandle file, String[] dict) throws Exception { + Block[][] map; int version, width, height; int run, block; DataInputStream in = new DataInputStream(file.read()); - version = in.readInt(); + version = in.readByte(); if (SAVE_VERSION == version) { width = in.readInt(); height = in.readInt(); - map = new int[width][height]; + map = new Block[width][height]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x += run) { - run = in.readInt(); - block = in.readInt(); + run = in.readUnsignedByte(); + block = in.readUnsignedByte(); for (int i = x; i < x + run; i++) { - map[i][y] = block; + map[i][y] = gameItemsHolder.getBlock(dict[block]); } } } @@ -128,7 +193,7 @@ public class GameSaver { } @CheckForNull - public static Data load(MainConfig mainConfig) { + public static Data load(MainConfig mainConfig, GameItemsHolder gameItemsHolder) { String folder = mainConfig.getGameFolder(); FileHandle file = Gdx.files.absolute(folder + "/saves/game.sav"); @@ -137,24 +202,27 @@ public class GameSaver { int version = in.readInt(); DropController dropController; MobsController mobsController; + ContainerController containerController; if (SAVE_VERSION == version) { dropController = (DropController) in.readObject(); mobsController = (MobsController) in.readObject(); + containerController = (ContainerController) 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")); + final String[] dict = loadDict(Gdx.files.absolute(mainConfig.getGameFolder() + "/saves/dict")); + Block[][] foreMap = loadMap(gameItemsHolder, Gdx.files.absolute(mainConfig.getGameFolder() + "/saves/foremap.sav"), dict); + Block[][] backMap = loadMap(gameItemsHolder, Gdx.files.absolute(mainConfig.getGameFolder() + "/saves/backmap.sav"), dict); if (dropController == null || mobsController == null) { throw new Exception("couldn't load"); } - return new Data(mobsController, dropController, foreMap, backMap); + return new Data(mobsController, dropController, containerController, foreMap, backMap); } catch (Exception e) { Gdx.app.error("GameSaver", e.getMessage()); } @@ -165,21 +233,30 @@ public class GameSaver { public static void save(MainConfig mainConfig, DropController dropController, MobsController mobsController, + ContainerController containerController, GameWorld gameWorld) { - String folder = mainConfig.getGameFolder(); FileHandle file = Gdx.files.absolute(folder + "/saves/"); file.mkdirs(); file = Gdx.files.absolute(folder + "/saves/game.sav"); + final Block[][] foreMap, backMap; + foreMap = gameWorld.getFullForeMap(); + backMap = gameWorld.getFullBackMap(); + + final Map dict = buildBlocksDictionary(foreMap, backMap); + try { ObjectOutputStream out = new ObjectOutputStream(file.write(false)); out.writeInt(SAVE_VERSION); out.writeObject(dropController); out.writeObject(mobsController); + out.writeObject(containerController); out.close(); - saveMap(Gdx.files.absolute(folder + "/saves/foremap.sav"), gameWorld.getFullForeMap()); - saveMap(Gdx.files.absolute(folder + "/saves/backmap.sav"), gameWorld.getFullBackMap()); + + saveDict(Gdx.files.absolute(folder + "/saves/dict"), dict); + saveMap(Gdx.files.absolute(folder + "/saves/foremap.sav"), gameWorld.getFullForeMap(), dict); + saveMap(Gdx.files.absolute(folder + "/saves/backmap.sav"), gameWorld.getFullBackMap(), dict); } catch (Exception e) { e.printStackTrace(); } diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameScreen.java b/core/src/ru/deadsoftware/cavedroid/game/GameScreen.java index 370d8bd..749d7d4 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/GameScreen.java +++ b/core/src/ru/deadsoftware/cavedroid/game/GameScreen.java @@ -16,25 +16,26 @@ public class GameScreen implements Screen { @CheckForNull private GameProc mGameProc; @CheckForNull - private GameInputProcessor mGameInputProcessor; + private GameItemsHolder mGameItemsHolder; @Inject public GameScreen(MainConfig mainConfig) { mMainConfig = mainConfig; } - public void newGame() { + public void newGame(int gameMode) { if (mGameProc != null) { mGameProc.dispose(); } + GameModule.loaded = true; + GameComponent gameComponent = DaggerGameComponent.builder() .mainComponent(mMainConfig.getMainComponent()).build(); mGameProc = gameComponent.getGameProc(); - mGameInputProcessor = gameComponent.getGameInputProcessor(); - Gdx.input.setInputProcessor(gameComponent.getGameInputProcessor()); + mGameProc.setPlayerGameMode(gameMode); } public void loadGame() { @@ -42,15 +43,12 @@ public class GameScreen implements Screen { mGameProc.dispose(); } - GameModule.load(mMainConfig); + GameModule.loaded = false; GameComponent gameComponent = DaggerGameComponent.builder() .mainComponent(mMainConfig.getMainComponent()).build(); mGameProc = gameComponent.getGameProc(); - mGameInputProcessor = gameComponent.getGameInputProcessor(); - - Gdx.input.setInputProcessor(gameComponent.getGameInputProcessor()); } @Override @@ -60,7 +58,8 @@ public class GameScreen implements Screen { @Override public void show() { - Gdx.input.setInputProcessor(mGameInputProcessor); +// Gdx.input.setInputProcessor(mGameInputProcessor); + mGameProc.show(); } @Override @@ -85,6 +84,9 @@ public class GameScreen implements Screen { @Override public void dispose() { + if (mGameProc != null) { + mGameProc.dispose(); + } } } diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameWorld.java b/core/src/ru/deadsoftware/cavedroid/game/GameWorld.java deleted file mode 100644 index 3bf4981..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/GameWorld.java +++ /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 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 index c157a4e..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/GameWorldGenerator.kt +++ /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> { - val random = Random(seed) - val foreMap = Array(width) { IntArray(height) } - val backMap = Array(width) { IntArray(width) } - val heightsMap = generateHeights(width, height / 2, height * 3 / 4, random) - - for (x in 0 until width) { - val xHeight = heightsMap[x] - - foreMap[x][xHeight] = GameItems.getBlockId("grass") - foreMap[x][height - 1] = GameItems.getBlockId("bedrock") - backMap[x][xHeight] = GameItems.getBlockId("grass") - backMap[x][height - 1] = GameItems.getBlockId("bedrock") - - for (y in xHeight + 1 until height - 1) { - foreMap[x][y] = when { - y < xHeight + random.nextInt(5, 8) -> GameItems.getBlockId("dirt") - else -> GameItems.getBlockId("stone") - } - backMap[x][y] = foreMap[x][y] - } - } - return Pair(foreMap, backMap) -} diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/CommonBlockActionUtils.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/CommonBlockActionUtils.kt new file mode 100644 index 0000000..2803ef0 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/CommonBlockActionUtils.kt @@ -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.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.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.getRequiresBlockAction(): IUpdateBlockAction { + return requireNotNull(get(UpdateRequiresBlockAction.ACTION_KEY)) { "action requires_block not found" } +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/IPlaceBlockAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/IPlaceBlockAction.kt new file mode 100644 index 0000000..06c70c4 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/IPlaceBlockAction.kt @@ -0,0 +1,9 @@ +package ru.deadsoftware.cavedroid.game.actions.placeblock + +import ru.deadsoftware.cavedroid.game.model.item.Item + +interface IPlaceBlockAction { + + fun place(placeable: Item.Placeable, x: Int, y: Int) + +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToBackgroundAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToBackgroundAction.kt new file mode 100644 index 0000000..7258301 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToBackgroundAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToForegroundAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToForegroundAction.kt new file mode 100644 index 0000000..ec249fb --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceBlockItemToForegroundAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceSlabAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceSlabAction.kt new file mode 100644 index 0000000..00c62d7 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/placeblock/PlaceSlabAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/updateblock/IUpdateBlockAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/IUpdateBlockAction.kt new file mode 100644 index 0000000..9cc4aea --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/IUpdateBlockAction.kt @@ -0,0 +1,7 @@ +package ru.deadsoftware.cavedroid.game.actions.updateblock + +interface IUpdateBlockAction { + + fun update(x: Int, y: Int) + +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedLeftAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedLeftAction.kt new file mode 100644 index 0000000..5507550 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedLeftAction.kt @@ -0,0 +1,28 @@ +package ru.deadsoftware.cavedroid.game.actions.updateblock + +import ru.deadsoftware.cavedroid.game.GameItemsHolder +import ru.deadsoftware.cavedroid.game.GameScope +import ru.deadsoftware.cavedroid.game.mobs.FallingGravel +import ru.deadsoftware.cavedroid.game.mobs.MobsController +import ru.deadsoftware.cavedroid.game.world.GameWorld +import 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/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedRightAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedRightAction.kt new file mode 100644 index 0000000..42f551e --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateBedRightAction.kt @@ -0,0 +1,28 @@ +package ru.deadsoftware.cavedroid.game.actions.updateblock + +import ru.deadsoftware.cavedroid.game.GameItemsHolder +import ru.deadsoftware.cavedroid.game.GameScope +import ru.deadsoftware.cavedroid.game.mobs.FallingGravel +import ru.deadsoftware.cavedroid.game.mobs.MobsController +import ru.deadsoftware.cavedroid.game.world.GameWorld +import 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/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGrassAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGrassAction.kt new file mode 100644 index 0000000..476235a --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGrassAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGravelAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGravelAction.kt new file mode 100644 index 0000000..3c52418 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateGravelAction.kt @@ -0,0 +1,30 @@ +package ru.deadsoftware.cavedroid.game.actions.updateblock + +import ru.deadsoftware.cavedroid.game.GameScope +import ru.deadsoftware.cavedroid.game.mobs.FallingGravel +import ru.deadsoftware.cavedroid.game.mobs.MobsController +import ru.deadsoftware.cavedroid.game.world.GameWorld +import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUpdateBlockAction +import javax.inject.Inject + +@GameScope +@BindUpdateBlockAction(stringKey = UpdateGravelAction.BLOCK_KEY) +class UpdateGravelAction @Inject constructor( + private val gameWorld: GameWorld, + private val mobsController: MobsController, +) : IUpdateBlockAction { + + override fun update(x: Int, y: Int) { + val shouldFall = gameWorld.getForeMap(x, y + 1).params.hasCollision.not() + + if (shouldFall) { + gameWorld.resetForeMap(x, y) + FallingGravel(x * 16f, y * 16f) + .apply { attachToController(mobsController) } + } + } + + companion object { + const val BLOCK_KEY = "gravel" + } +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateRequiresBlockAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateRequiresBlockAction.kt new file mode 100644 index 0000000..05d9863 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateRequiresBlockAction.kt @@ -0,0 +1,23 @@ +package ru.deadsoftware.cavedroid.game.actions.updateblock + +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 = UpdateRequiresBlockAction.ACTION_KEY) +class UpdateRequiresBlockAction @Inject constructor( + private val gameWorld: GameWorld, +) : IUpdateBlockAction { + + override fun update(x: Int, y: Int) { + if (gameWorld.getForeMap(x, y + 1).params.hasCollision.not()) { + gameWorld.destroyForeMap(x, y) + } + } + + companion object { + const val ACTION_KEY = "requires_block" + } +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSandAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSandAction.kt new file mode 100644 index 0000000..ac1ff8a --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSandAction.kt @@ -0,0 +1,30 @@ +package ru.deadsoftware.cavedroid.game.actions.updateblock + +import ru.deadsoftware.cavedroid.game.GameScope +import ru.deadsoftware.cavedroid.game.mobs.FallingSand +import ru.deadsoftware.cavedroid.game.mobs.MobsController +import ru.deadsoftware.cavedroid.game.world.GameWorld +import ru.deadsoftware.cavedroid.misc.annotations.multibinding.BindUpdateBlockAction +import javax.inject.Inject + +@GameScope +@BindUpdateBlockAction(stringKey = UpdateSandAction.BLOCK_KEY) +class UpdateSandAction @Inject constructor( + private val gameWorld: GameWorld, + private val mobsController: MobsController, +) : IUpdateBlockAction { + + override fun update(x: Int, y: Int) { + val shouldFall = gameWorld.getForeMap(x, y + 1).params.hasCollision.not() + + if (shouldFall) { + gameWorld.resetForeMap(x, y) + FallingSand(x * 16f, y * 16f) + .apply { attachToController(mobsController) } + } + } + + companion object { + const val BLOCK_KEY = "sand" + } +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSnowedGrassAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSnowedGrassAction.kt new file mode 100644 index 0000000..2cf5a57 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/updateblock/UpdateSnowedGrassAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/useblock/IUseBlockAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useblock/IUseBlockAction.kt new file mode 100644 index 0000000..ddc70c5 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/useblock/IUseBlockAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/useblock/UseChestAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useblock/UseChestAction.kt new file mode 100644 index 0000000..93684e7 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/useblock/UseChestAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/useblock/UseCraftingTableAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useblock/UseCraftingTableAction.kt new file mode 100644 index 0000000..6d6c1e9 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/useblock/UseCraftingTableAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/useblock/UseFurnaceAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useblock/UseFurnaceAction.kt new file mode 100644 index 0000000..0b8ba53 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/useblock/UseFurnaceAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/useitem/IUseItemAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/IUseItemAction.kt new file mode 100644 index 0000000..d963aee --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/IUseItemAction.kt @@ -0,0 +1,9 @@ +package ru.deadsoftware.cavedroid.game.actions.useitem + +import ru.deadsoftware.cavedroid.game.model.item.Item + +interface IUseItemAction { + + fun perform(item: Item.Usable, x: Int, y: Int) + +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseBedAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseBedAction.kt new file mode 100644 index 0000000..f48115e --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseBedAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/useitem/UseEmptyBucketAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseEmptyBucketAction.kt new file mode 100644 index 0000000..6992af7 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseEmptyBucketAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/useitem/UseLavaBucketAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseLavaBucketAction.kt new file mode 100644 index 0000000..ec1e585 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseLavaBucketAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/useitem/UsePigSpawnEggAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UsePigSpawnEggAction.kt new file mode 100644 index 0000000..5eddc5e --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UsePigSpawnEggAction.kt @@ -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/ru/deadsoftware/cavedroid/game/actions/useitem/UseWaterBucketAction.kt b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseWaterBucketAction.kt new file mode 100644 index 0000000..739ec28 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/actions/useitem/UseWaterBucketAction.kt @@ -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/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt b/core/src/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt new file mode 100644 index 0000000..51dc335 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt @@ -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 { + 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/ru/deadsoftware/cavedroid/game/input/IGameInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/IGameInputHandler.kt new file mode 100644 index 0000000..2c5f118 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/IGameInputHandler.kt @@ -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 + +interface IMouseInputHandler : IGameInputHandler + +interface IGameInputHandler { + + /** + * Implementation should check if conditions for handling an input are satisfied + * For example - inventory input handler should return false if inventory is closed + */ + fun checkConditions(action: A): Boolean + + /** + * Handle given input action. + * This will not be called if [checkConditions] returned false + */ + fun handle(action: A) + +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/InputUtils.kt b/core/src/ru/deadsoftware/cavedroid/game/input/InputUtils.kt new file mode 100644 index 0000000..425822a --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/InputUtils.kt @@ -0,0 +1,21 @@ +package ru.deadsoftware.cavedroid.game.input + +import com.badlogic.gdx.graphics.g2d.TextureRegion +import com.badlogic.gdx.math.Rectangle +import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction +import ru.deadsoftware.cavedroid.misc.Assets + +fun isInsideHotbar(action: MouseInputAction): Boolean { + val hotbar = requireNotNull(Assets.textureRegions["hotbar"]) + + return action.screenY <= hotbar.regionHeight && + action.screenX >= action.cameraViewport.width / 2 - hotbar.regionWidth / 2 && + action.screenX <= action.cameraViewport.width / 2 + hotbar.regionWidth / 2 +} + +fun isInsideWindow(action: MouseInputAction, windowTexture: TextureRegion): Boolean { + return action.screenY > action.cameraViewport.height / 2 - windowTexture.regionHeight / 2 && + action.screenY < action.cameraViewport.height / 2 + windowTexture.regionHeight / 2 && + action.screenX > action.cameraViewport.width / 2 - windowTexture.regionWidth / 2 && + action.screenX < action.cameraViewport.width / 2 + windowTexture.regionWidth / 2 +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/Joystick.kt b/core/src/ru/deadsoftware/cavedroid/game/input/Joystick.kt new file mode 100644 index 0000000..6cbc052 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/Joystick.kt @@ -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/ru/deadsoftware/cavedroid/game/input/action/IGameInputAction.kt b/core/src/ru/deadsoftware/cavedroid/game/input/action/IGameInputAction.kt new file mode 100644 index 0000000..b982a92 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/action/IGameInputAction.kt @@ -0,0 +1,3 @@ +package ru.deadsoftware.cavedroid.game.input.action + +interface IGameInputAction \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/action/KeyboardInputAction.kt b/core/src/ru/deadsoftware/cavedroid/game/input/action/KeyboardInputAction.kt new file mode 100644 index 0000000..416d2e0 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/action/KeyboardInputAction.kt @@ -0,0 +1,8 @@ +package ru.deadsoftware.cavedroid.game.input.action + +import ru.deadsoftware.cavedroid.game.input.action.keys.KeyboardInputActionKey + +data class KeyboardInputAction( + val actionKey: KeyboardInputActionKey, + val isKeyDown: Boolean, +) : IGameInputAction \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/action/MouseInputAction.kt b/core/src/ru/deadsoftware/cavedroid/game/input/action/MouseInputAction.kt new file mode 100644 index 0000000..122ffda --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/action/MouseInputAction.kt @@ -0,0 +1,11 @@ +package ru.deadsoftware.cavedroid.game.input.action + +import com.badlogic.gdx.math.Rectangle +import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey + +data class MouseInputAction( + val screenX: Float, + val screenY: Float, + val actionKey: MouseInputActionKey, + val cameraViewport: Rectangle +) : IGameInputAction \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/action/keys/KeyboardInputActionKey.kt b/core/src/ru/deadsoftware/cavedroid/game/input/action/keys/KeyboardInputActionKey.kt new file mode 100644 index 0000000..fd14fc4 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/action/keys/KeyboardInputActionKey.kt @@ -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/ru/deadsoftware/cavedroid/game/input/action/keys/MouseInputActionKey.kt b/core/src/ru/deadsoftware/cavedroid/game/input/action/keys/MouseInputActionKey.kt new file mode 100644 index 0000000..3b2744f --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/action/keys/MouseInputActionKey.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/CloseGameWindowKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/CloseGameWindowKeyboardInputHandler.kt new file mode 100644 index 0000000..8bf9b1c --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/CloseGameWindowKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/DropItemKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/DropItemKeyboardInputHandler.kt new file mode 100644 index 0000000..e9c8eb0 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/DropItemKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyDownKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyDownKeyboardInputHandler.kt new file mode 100644 index 0000000..ae5f4c3 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyDownKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyUpKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyUpKeyboardInputHandler.kt new file mode 100644 index 0000000..444fe09 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/FlyUpKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoLeftKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoLeftKeyboardInputHandler.kt new file mode 100644 index 0000000..2b40d46 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoLeftKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoRightKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoRightKeyboardInputHandler.kt new file mode 100644 index 0000000..ec38218 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/GoRightKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/JumpKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/JumpKeyboardInputHandler.kt new file mode 100644 index 0000000..bcacbf5 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/JumpKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/MoveCursorControlsModeKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/MoveCursorControlsModeKeyboardInputHandler.kt new file mode 100644 index 0000000..84c5b90 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/MoveCursorControlsModeKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenInventoryKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenInventoryKeyboardInputHandler.kt new file mode 100644 index 0000000..70ae158 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/OpenInventoryKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/PauseGameKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/PauseGameKeyboardInputHandler.kt new file mode 100644 index 0000000..cedc78a --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/PauseGameKeyboardInputHandler.kt @@ -0,0 +1,42 @@ +package ru.deadsoftware.cavedroid.game.input.handler.keyboard + +import ru.deadsoftware.cavedroid.MainConfig +import ru.deadsoftware.cavedroid.game.GameSaver +import ru.deadsoftware.cavedroid.game.GameScope +import ru.deadsoftware.cavedroid.game.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.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 + } + + GameSaver.save(mainConfig, dropController, mobsController, containerController, gameWorld) + mainConfig.caveGame.quitGame() + } +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SelectHotbarSlotKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SelectHotbarSlotKeyboardInputHandler.kt new file mode 100644 index 0000000..d22cd94 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SelectHotbarSlotKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/StopSwimKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/StopSwimKeyboardInputHandler.kt new file mode 100644 index 0000000..d8c5acc --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/StopSwimKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SwimUpKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SwimUpKeyboardInputHandler.kt new file mode 100644 index 0000000..20cc82e --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/SwimUpKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleControlsModeKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleControlsModeKeyboardInputHandler.kt new file mode 100644 index 0000000..f8e0177 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleControlsModeKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleDebugInfoKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleDebugInfoKeyboardInputHandler.kt new file mode 100644 index 0000000..7cf08ed --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleDebugInfoKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleGameModeKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleGameModeKeyboardInputHandler.kt new file mode 100644 index 0000000..64a1116 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleGameModeKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleMinimapKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleMinimapKeyboardInputHandler.kt new file mode 100644 index 0000000..2cd6638 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/ToggleMinimapKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/keyboard/TurnOnFlyModeKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/TurnOnFlyModeKeyboardInputHandler.kt new file mode 100644 index 0000000..43a7122 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/TurnOnFlyModeKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/mouse/AbstractInventoryItemsMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/AbstractInventoryItemsMouseInputHandler.kt new file mode 100644 index 0000000..3b925b0 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/AbstractInventoryItemsMouseInputHandler.kt @@ -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, + 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, + 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/ru/deadsoftware/cavedroid/game/input/handler/mouse/AttackMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/AttackMouseInputHandler.kt new file mode 100644 index 0000000..52333eb --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/AttackMouseInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/mouse/CloseGameWindowMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CloseGameWindowMouseInputHandler.kt new file mode 100644 index 0000000..4fb0879 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CloseGameWindowMouseInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/mouse/CreativeInventoryScrollMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CreativeInventoryScrollMouseInputHandler.kt new file mode 100644 index 0000000..8acdb7e --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CreativeInventoryScrollMouseInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/mouse/CursorMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CursorMouseInputHandler.kt new file mode 100644 index 0000000..c1f2af5 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CursorMouseInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/mouse/HotbarMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/HotbarMouseInputHandler.kt new file mode 100644 index 0000000..6a66338 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/HotbarMouseInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectChestInventoryItemMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectChestInventoryItemMouseInputHandler.kt new file mode 100644 index 0000000..62a4836 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectChestInventoryItemMouseInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCraftingInventoryItemMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCraftingInventoryItemMouseInputHandler.kt new file mode 100644 index 0000000..aefea54 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCraftingInventoryItemMouseInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCreativeInventoryItemMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCreativeInventoryItemMouseInputHandler.kt new file mode 100644 index 0000000..976f443 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCreativeInventoryItemMouseInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectFurnaceInventoryItemMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectFurnaceInventoryItemMouseInputHandler.kt new file mode 100644 index 0000000..eac8cfa --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectFurnaceInventoryItemMouseInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectSurvivalInventoryItemMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectSurvivalInventoryItemMouseInputHandler.kt new file mode 100644 index 0000000..58687fc --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectSurvivalInventoryItemMouseInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/handler/mouse/UseItemMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/UseItemMouseInputHandler.kt new file mode 100644 index 0000000..541bba7 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/UseItemMouseInputHandler.kt @@ -0,0 +1,131 @@ +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.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, + private val placeBlockActionMap: Map, + private val useBlockActionMap: Map, + private val gameWindowsManager: GameWindowsManager, + private val gameWorld: GameWorld, +) : 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 { + 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/ru/deadsoftware/cavedroid/game/input/handler/touch/JoystickInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/touch/JoystickInputHandler.kt new file mode 100644 index 0000000..d717c18 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/handler/touch/JoystickInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/game/input/mapper/KeyboardInputActionMapper.kt b/core/src/ru/deadsoftware/cavedroid/game/input/mapper/KeyboardInputActionMapper.kt new file mode 100644 index 0000000..8d18d07 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/mapper/KeyboardInputActionMapper.kt @@ -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/ru/deadsoftware/cavedroid/game/input/mapper/MouseInputActionMapper.kt b/core/src/ru/deadsoftware/cavedroid/game/input/mapper/MouseInputActionMapper.kt new file mode 100644 index 0000000..5ef450a --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/input/mapper/MouseInputActionMapper.kt @@ -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/ru/deadsoftware/cavedroid/game/mobs/FallingGravel.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/FallingGravel.java index 23cf1dc..2ad4287 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/mobs/FallingGravel.java +++ b/core/src/ru/deadsoftware/cavedroid/game/mobs/FallingGravel.java @@ -1,9 +1,14 @@ package ru.deadsoftware.cavedroid.game.mobs; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Vector2; -import ru.deadsoftware.cavedroid.game.GameItems; -import ru.deadsoftware.cavedroid.game.GameWorld; +import ru.deadsoftware.cavedroid.game.GameItemsHolder; +import ru.deadsoftware.cavedroid.game.world.GameWorld; +import ru.deadsoftware.cavedroid.misc.Assets; + +import javax.annotation.CheckForNull; /** * Falling gravel is actually a mob, that spawns in place of gravel when there is no block under it, @@ -11,6 +16,8 @@ import ru.deadsoftware.cavedroid.game.GameWorld; */ public class FallingGravel extends Mob { + private static final String TAG = "FallingGravel"; + /** * Creates a FallingGravel mob at coordinates * @@ -18,14 +25,24 @@ public class FallingGravel extends Mob { * @param y Y in pixels */ public FallingGravel(float x, float y) { - super(x, y, 16, 16, Direction.LEFT, Type.GRAVEL); - mMove = new Vector2(0, 1); + super(x, y, 16, 16, Direction.LEFT, Type.GRAVEL, Integer.MAX_VALUE); + mVelocity = new Vector2(0, 1); + } + + @Override + public float getSpeed() { + return 0; + } + + @Override + public void jump() { + // no-op } @Override - public void ai(GameWorld gameWorld) { - if (mMove.isZero()) { - gameWorld.setForeMap(getMapX(), getMiddleMapY(), 11); + public void ai(GameWorld gameWorld, GameItemsHolder gameItemsHolder, MobsController mobsController, float delta) { + if (mVelocity.isZero()) { + gameWorld.setForeMap(getMapX(), getUpperMapY(), gameItemsHolder.getBlock("gravel")); kill(); } } @@ -35,8 +52,21 @@ public class FallingGravel extends Mob { } @Override - public void draw(SpriteBatch spriteBatch, float x, float y) { - spriteBatch.draw(GameItems.getBlockTex(11), x, y); + public void damage(int damage) { + // no-op + } + + @Override + public void draw(SpriteBatch spriteBatch, float x, float y, float delta) { + @CheckForNull final Texture texture = Assets.blockTextures.get("gravel"); + + if (texture == null) { + Gdx.app.error(TAG, "Couldn't draw: texture not found"); + kill(); + return; + } + + spriteBatch.draw(texture, x, y); } } diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/FallingSand.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/FallingSand.java index 9a383a2..970ff53 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/mobs/FallingSand.java +++ b/core/src/ru/deadsoftware/cavedroid/game/mobs/FallingSand.java @@ -1,9 +1,14 @@ package ru.deadsoftware.cavedroid.game.mobs; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Vector2; -import ru.deadsoftware.cavedroid.game.GameItems; -import ru.deadsoftware.cavedroid.game.GameWorld; +import ru.deadsoftware.cavedroid.game.GameItemsHolder; +import ru.deadsoftware.cavedroid.game.world.GameWorld; +import ru.deadsoftware.cavedroid.misc.Assets; + +import javax.annotation.CheckForNull; /** @@ -12,6 +17,8 @@ import ru.deadsoftware.cavedroid.game.GameWorld; */ public class FallingSand extends Mob { + private static final String TAG = "FallingSand"; + /** * Creates a FallingSand mob at coordinates * @@ -19,14 +26,24 @@ public class FallingSand extends Mob { * @param y Y in pixels */ public FallingSand(float x, float y) { - super(x, y, 16, 16, Direction.LEFT, Type.SAND); - mMove = new Vector2(0, 1); + super(x, y, 16, 16, Direction.LEFT, Type.SAND, Integer.MAX_VALUE); + mVelocity = new Vector2(0, 1); + } + + @Override + public float getSpeed() { + return 0; + } + + @Override + public void jump() { + // no-op } @Override - public void ai(GameWorld gameWorld) { - if (mMove.isZero()) { - gameWorld.setForeMap(getMapX(), getMiddleMapY(), 10); + public void ai(GameWorld gameWorld, GameItemsHolder gameItemsHolder, MobsController mobsController, float delta) { + if (mVelocity.isZero()) { + gameWorld.setForeMap(getMapX(), getUpperMapY(), gameItemsHolder.getBlock("sand")); kill(); } } @@ -36,8 +53,21 @@ public class FallingSand extends Mob { } @Override - public void draw(SpriteBatch spriteBatch, float x, float y) { - spriteBatch.draw(GameItems.getBlockTex(10), x, y); + public void damage(int damage) { + // no-op + } + + @Override + public void draw(SpriteBatch spriteBatch, float x, float y, float delta) { + @CheckForNull final Texture texture = Assets.blockTextures.get("sand"); + + if (texture == null) { + Gdx.app.error(TAG, "Couldn't draw: texture not found"); + kill(); + return; + } + + spriteBatch.draw(texture, x, y); } } diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/Mob.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/Mob.java index 0f76592..62ddc50 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/mobs/Mob.java +++ b/core/src/ru/deadsoftware/cavedroid/game/mobs/Mob.java @@ -1,11 +1,16 @@ 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 ru.deadsoftware.cavedroid.game.GameWorld; +import com.badlogic.gdx.utils.Timer; +import ru.deadsoftware.cavedroid.game.GameItemsHolder; +import ru.deadsoftware.cavedroid.game.world.GameWorld; +import javax.annotation.CheckForNull; import java.io.Serializable; /** @@ -13,6 +18,13 @@ import java.io.Serializable; */ public abstract class Mob extends Rectangle implements Serializable { + 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, SAND, @@ -20,20 +32,57 @@ public abstract class Mob extends Rectangle implements Serializable { } public enum Direction { - LEFT, - RIGHT + + LEFT(0, -1), + RIGHT(1, 1); + + private final int index; + private final int basis; + + /** + * Index for this direction (left = 0, right = 1) + */ + public final int getIndex() { + return index; + } + + /** + * Basis for this direction (left = -1, right = 1) + */ + public final int getBasis() { + return basis; + } + + Direction(int index, int basis) { + this.index = index; + this.basis = basis; + } + } + + private class ResetTakeDamageTask extends Timer.Task { + + @Override + public void run() { + mTakingDamage = false; + } } - protected Vector2 mMove; + protected Vector2 mVelocity; protected Type mType; - protected int mAnimDelta = 6; - protected int mAnim; + protected int mAnimDelta = ANIMATION_SPEED; + protected float mAnim; private Direction mDirection; - private boolean mDead; + protected boolean mDead; private boolean mCanJump; private boolean mFlyMode; + private final int mMaxHealth; + private int mHealth; + + private transient boolean mTakingDamage = false; + @CheckForNull private transient ResetTakeDamageTask mResetTakeDamageTask = null; + /** * @param x in pixels * @param y in pixels @@ -41,19 +90,57 @@ public abstract class Mob extends Rectangle implements Serializable { * @param height in pixels * @param mDirection Direction in which mob is looking */ - protected Mob(float x, float y, float width, float height, Direction mDirection, Type type) { + protected Mob(float x, float y, float width, float height, Direction mDirection, Type type, int maxHealth) { super(x, y, width, height); - mMove = new Vector2(0, 0); + mVelocity = new Vector2(0, 0); mCanJump = false; mDead = false; this.mDirection = mDirection; this.mType = type; + this.mMaxHealth = maxHealth; + this.mHealth = mMaxHealth; } protected static Direction randomDir() { return MathUtils.randomBoolean(.5f) ? Direction.LEFT : Direction.RIGHT; } + private boolean isAnimationIncreasing() { + return mAnim > 0 && mAnimDelta > 0 || mAnim < 0 && mAnimDelta < 0; + } + + private void checkHealth() { + mHealth = MathUtils.clamp(mHealth, 0, mMaxHealth); + + if (mHealth <= 0) { + kill(); + } + } + + protected final void updateAnimation(float delta) { + 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 */ @@ -112,15 +199,11 @@ public abstract class Mob extends Rectangle implements Serializable { mDirection = looksLeft() ? Direction.RIGHT : Direction.LEFT; } - protected final int dirMultiplier() { - return looksLeft() ? 0 : 1; - } - public final boolean isDead() { return mDead; } - public final int getAnim() { + public final float getAnim() { return mAnim; } @@ -131,13 +214,17 @@ public abstract class Mob extends Rectangle implements Serializable { mDead = true; } - public final void move() { - x += mMove.x; - y += mMove.y; + public final void move(float delta) { + x += mVelocity.x * delta; + y += mVelocity.y * delta; } - public final Vector2 getMove() { - return mMove; + public final Vector2 getVelocity() { + return mVelocity; + } + + protected final void setVelocity(Vector2 velocity) { + mVelocity = velocity; } public final boolean canJump() { @@ -160,7 +247,7 @@ public abstract class Mob extends Rectangle implements Serializable { return mType; } - public void checkWorldBounds(GameWorld gameWorld) { + public final void checkWorldBounds(GameWorld gameWorld) { if (x + width / 2 < 0) { x += gameWorld.getWidthPx(); } @@ -169,9 +256,81 @@ public abstract class Mob extends Rectangle implements Serializable { } } - public abstract void draw(SpriteBatch spriteBatch, float x, float y); + public final int getHealth() { + return mHealth; + } - public abstract void ai(GameWorld gameWorld); + 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 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(); } 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 index cda54ee..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/mobs/MobsController.java +++ /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 mMobs = new LinkedList<>(); - - @Inject - public MobsController() { - mPlayer = new Player(); - } - - public Player getPlayer() { - return mPlayer; - } - - public void addMob(Class 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 getIterator() { - return mMobs.iterator(); - } - -} diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/MobsController.kt b/core/src/ru/deadsoftware/cavedroid/game/mobs/MobsController.kt new file mode 100644 index 0000000..158d22a --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/mobs/MobsController.kt @@ -0,0 +1,32 @@ +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.ui.TooltipManager +import java.io.Serializable +import java.util.* +import javax.inject.Inject + +@GameScope +class MobsController @Inject constructor( + gameItemsHolder: GameItemsHolder, + tooltipManager: TooltipManager, +) : Serializable { + + private val _mobs = LinkedList() + + val player: Player = + Player(gameItemsHolder, tooltipManager) + + val mobs: List + get() = _mobs + + fun addMob(mob: Mob) { + _mobs.add(mob) + } + + companion object { + private const val TAG = "MobsController" + } +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.java deleted file mode 100644 index b362da7..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.java +++ /dev/null @@ -1,62 +0,0 @@ -package ru.deadsoftware.cavedroid.game.mobs; - -import com.badlogic.gdx.graphics.g2d.SpriteBatch; -import com.badlogic.gdx.math.MathUtils; -import com.badlogic.gdx.math.Vector2; -import ru.deadsoftware.cavedroid.game.GameWorld; -import ru.deadsoftware.cavedroid.misc.Assets; - -import static ru.deadsoftware.cavedroid.misc.Assets.pigSprite; - -public class Pig extends Mob { - - public Pig(float x, float y) { - super(x, y, 25, 18, randomDir(), Type.MOB); - mMove = new Vector2(looksLeft() ? -1 : 1, 0); - } - - @Override - public void changeDir() { - switchDir(); - mMove.x = -1 + 2 * dirMultiplier(); - } - - @Override - public void ai(GameWorld gameWorld) { - if (MathUtils.randomBoolean(.0025f)) { - if (mMove.x != 0f) { - mMove.x = 0; - } else { - changeDir(); - } - } - - if (mMove.x != 0f) { - mAnim += mAnimDelta; - } else { - mAnim = 0; - } - - if (mAnim >= 60 || mAnim <= -60) { - mAnimDelta = -mAnimDelta; - } - } - - @Override - public void draw(SpriteBatch spriteBatch, float x, float y) { - pigSprite[0][1].setRotation(getAnim()); - pigSprite[1][1].setRotation(-getAnim()); - //back legs - pigSprite[1][1].setPosition(x + (9 - dirMultiplier() * 9), y + 12); - pigSprite[1][1].draw(spriteBatch); - pigSprite[1][1].setPosition(x + 21 - (9 * dirMultiplier()), y + 12); - pigSprite[1][1].draw(spriteBatch); - //head & body - spriteBatch.draw(Assets.pigSprite[dirMultiplier()][0], x, y); - //front legs - pigSprite[0][1].setPosition(x + (9 - dirMultiplier() * 9), y + 12); - pigSprite[0][1].draw(spriteBatch); - pigSprite[0][1].setPosition(x + 21 - (9 * dirMultiplier()), y + 12); - pigSprite[0][1].draw(spriteBatch); - } -} diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.kt b/core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.kt new file mode 100644 index 0000000..68c457e --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/mobs/Pig.kt @@ -0,0 +1,73 @@ +package ru.deadsoftware.cavedroid.game.mobs + +import com.badlogic.gdx.graphics.g2d.SpriteBatch +import com.badlogic.gdx.math.MathUtils +import com.badlogic.gdx.math.Vector2 +import ru.deadsoftware.cavedroid.game.GameItemsHolder +import ru.deadsoftware.cavedroid.game.world.GameWorld +import ru.deadsoftware.cavedroid.misc.utils.drawSprite +import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getBackgroundLeg +import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getBody +import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getForegroundLeg +import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getLeftLegRelativeX +import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getLegsRelativeY +import ru.deadsoftware.cavedroid.misc.utils.mobs.MobSprites.Pig.getRightLegRelativeX + +class Pig(x: Float, y: Float) : Mob(x, y, WIDTH, HEIGHT, randomDir(), Type.MOB, MAX_HEALTH) { + + override fun getSpeed(): Float { + return SPEED + } + + override fun changeDir() { + switchDir() + velocity = Vector2(direction.basis * speed, 0f) + } + + override fun jump() { + velocity.y = JUMP_VELOCITY + } + + override fun damage(damage: Int) { + super.damage(damage) + + if (damage > 0) { + if (canJump()) { + jump() + } + } + } + + override fun ai(world: GameWorld, gameItemsHolder: GameItemsHolder, mobsController: MobsController, delta: Float) { + if (MathUtils.randomBoolean(delta)) { + if (velocity.x != 0f) { + velocity.x = 0f + } else { + changeDir() + } + } + } + + override fun draw(spriteBatch: SpriteBatch, x: Float, y: Float, delta: Float) { + updateAnimation(delta) + + val leftLegX = x + getLeftLegRelativeX(direction) + val rightLegX = x + getRightLegRelativeX(direction) + val legY = y + getLegsRelativeY() + + spriteBatch.drawSprite(getBackgroundLeg(), leftLegX, legY, -anim, 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) + } + + + private companion object { + 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 + } +} \ No newline at end of file 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 index 0b7ab8b..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/mobs/Player.java +++ /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/player/Inventory.kt b/core/src/ru/deadsoftware/cavedroid/game/mobs/player/Inventory.kt new file mode 100644 index 0000000..3012d66 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/mobs/player/Inventory.kt @@ -0,0 +1,136 @@ +package ru.deadsoftware.cavedroid.game.mobs.player + +import ru.deadsoftware.cavedroid.game.GameItemsHolder +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 java.io.Serializable + +class Inventory( + val size: Int, + val hotbarSize: Int, + gameItemsHolder: GameItemsHolder, + tooltipManager: TooltipManager, +) : Serializable { + + @Suppress("UNNECESSARY_LATEINIT") + @Transient + private lateinit var tooltipManager: TooltipManager + + @Suppress("UNNECESSARY_LATEINIT") + @Transient + private lateinit var fallbackItem: 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") + } + } + + private val _items = Array(size) { InventoryItem(gameItemsHolder.fallbackItem) } + + val items get() = _items.asList() as MutableList + + val hotbarItems get() = items.subList(0, hotbarSize) + + var activeSlot = 0 + set(value) { + if (value in 0 ..< hotbarSize) { + field = 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) + } +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/mobs/player/Player.java b/core/src/ru/deadsoftware/cavedroid/game/mobs/player/Player.java new file mode 100644 index 0000000..8ea87c1 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/mobs/player/Player.java @@ -0,0 +1,442 @@ +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 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.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; + +import javax.annotation.CheckForNull; + +public class Player extends Mob { + + 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 final Inventory inventory; + + public int gameMode; + public boolean swim; + public float headRotation = 0f; + + public float blockDamage = 0f; + public int cursorX = 0; + public int cursorY = 0; + + @CheckForNull + private Vector2 spawnPoint = null; + + public ControlMode controlMode = ControlMode.WALK; + + public enum ControlMode { + WALK, + CURSOR + } + + public Player(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); + @CheckForNull 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); + } + +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt b/core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt new file mode 100644 index 0000000..fa99bfd --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt @@ -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? = 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/ru/deadsoftware/cavedroid/game/model/block/BlockAnimationInfo.kt b/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockAnimationInfo.kt new file mode 100644 index 0000000..8d25189 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockAnimationInfo.kt @@ -0,0 +1,5 @@ +package ru.deadsoftware.cavedroid.game.model.block + +data class BlockAnimationInfo( + val framesCount: Int +) diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockDropInfo.kt b/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockDropInfo.kt new file mode 100644 index 0000000..62df2f3 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockDropInfo.kt @@ -0,0 +1,6 @@ +package ru.deadsoftware.cavedroid.game.model.block + +data class BlockDropInfo( + val itemKey: String, + val count: Int, +) diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockMargins.kt b/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockMargins.kt new file mode 100644 index 0000000..dbd2999 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/block/BlockMargins.kt @@ -0,0 +1,15 @@ +package ru.deadsoftware.cavedroid.game.model.block + +data class BlockMargins( + val left: Int, + val top: Int, + val right: Int, + val bottom: Int +) { + + init { + assert(left + right < 16) + assert(top + bottom < 16) + } + +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/block/CommonBlockParams.kt b/core/src/ru/deadsoftware/cavedroid/game/model/block/CommonBlockParams.kt new file mode 100644 index 0000000..369f752 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/block/CommonBlockParams.kt @@ -0,0 +1,22 @@ +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?, + val damage: Int, + val tint: String?, +) diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/craft/CraftingRecipe.kt b/core/src/ru/deadsoftware/cavedroid/game/model/craft/CraftingRecipe.kt new file mode 100644 index 0000000..c8b2a86 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/craft/CraftingRecipe.kt @@ -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, + val output: CraftingResult +) + +data class CraftingResult( + val item: Item, + val amount: Int, +) { + fun toInventoryItem() = InventoryItem(item, amount) +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt b/core/src/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt new file mode 100644 index 0000000..69b1f18 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt @@ -0,0 +1,34 @@ +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, +) diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/dto/CraftingDto.kt b/core/src/ru/deadsoftware/cavedroid/game/model/dto/CraftingDto.kt new file mode 100644 index 0000000..469602a --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/dto/CraftingDto.kt @@ -0,0 +1,9 @@ +package ru.deadsoftware.cavedroid.game.model.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class CraftingDto( + val input: List, + val count: Int = 1, +) diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt b/core/src/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt new file mode 100644 index 0000000..aef3a13 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt @@ -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, + @SerialName("items") val items: Map, +) \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt b/core/src/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt new file mode 100644 index 0000000..373db46 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt @@ -0,0 +1,23 @@ +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, +) diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/item/CommonItemParams.kt b/core/src/ru/deadsoftware/cavedroid/game/model/item/CommonItemParams.kt new file mode 100644 index 0000000..00af08b --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/item/CommonItemParams.kt @@ -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/ru/deadsoftware/cavedroid/game/model/item/InventoryItem.kt b/core/src/ru/deadsoftware/cavedroid/game/model/item/InventoryItem.kt new file mode 100644 index 0000000..3cd406b --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/item/InventoryItem.kt @@ -0,0 +1,142 @@ +package ru.deadsoftware.cavedroid.game.model.item + +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.g2d.SpriteBatch +import com.badlogic.gdx.graphics.glutils.ShapeRenderer +import ru.deadsoftware.cavedroid.game.GameItemsHolder +import ru.deadsoftware.cavedroid.misc.Assets +import ru.deadsoftware.cavedroid.misc.utils.drawSprite +import ru.deadsoftware.cavedroid.misc.utils.drawString +import ru.deadsoftware.cavedroid.misc.utils.px +import java.io.Serializable +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +class InventoryItem @JvmOverloads constructor( + val itemKey: String, + _amount: Int = 1, +) : Serializable { + + var amount = _amount + set(value) { + field = if (value < 0) { + 0 + } else { + value + } + } + + @Transient + 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) + ) + } + } + + companion object { + + @OptIn(ExperimentalContracts::class) + fun InventoryItem?.isNoneOrNull(): Boolean { + contract { returns(false) implies(this@isNoneOrNull != null) } + return this?.item == null || this.item.isNone() + } + } +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/item/Item.kt b/core/src/ru/deadsoftware/cavedroid/game/model/item/Item.kt new file mode 100644 index 0000000..bf2a55c --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/item/Item.kt @@ -0,0 +1,140 @@ +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 + } + + @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() + +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt b/core/src/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt new file mode 100644 index 0000000..1b641ed --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt @@ -0,0 +1,107 @@ +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, + ) + } + + private fun mapToolType(dto: BlockDto): Class? { + return when(dto.toolType) { + "shovel" -> Item.Shovel::class.java + "sword" -> Item.Sword::class.java + "pickaxe" -> Item.Pickaxe::class.java + "axe" -> Item.Axe::class.java + "shears" -> Item.Shears::class.java + + else -> null + } + } + + private fun mapBlockDropInfo(dto: BlockDto): BlockDropInfo? { + val drop = dto.drop + val dropCount = dto.dropCount + + if (drop == GameItemsHolder.FALLBACK_ITEM_KEY || dropCount == 0) { + return null + } + + return BlockDropInfo( + itemKey = drop, + count = dropCount, + ) + } + + private fun mapBlockAnimationInfo(dto: BlockDto): BlockAnimationInfo? { + if (!dto.animated) { + return null + } + + return BlockAnimationInfo( + framesCount = dto.frames, + ) + } + + private fun getTexture(textureName: String): Texture? { + if (textureName == GameItemsHolder.FALLBACK_BLOCK_KEY) { + return null + } + + return Assets.resolveBlockTexture(assetLoader, textureName) + } + +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt b/core/src/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt new file mode 100644 index 0000000..a0949a0 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt @@ -0,0 +1,69 @@ +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)) + "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/ru/deadsoftware/cavedroid/game/model/world/Biome.kt b/core/src/ru/deadsoftware/cavedroid/game/model/world/Biome.kt new file mode 100644 index 0000000..ee214bd --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/world/Biome.kt @@ -0,0 +1,7 @@ +package ru.deadsoftware.cavedroid.game.model.world + +enum class Biome { + PLAINS, + DESERT, + WINTER +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/world/generator/WorldGeneratorConfig.kt b/core/src/ru/deadsoftware/cavedroid/game/model/world/generator/WorldGeneratorConfig.kt new file mode 100644 index 0000000..64da3fe --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/world/generator/WorldGeneratorConfig.kt @@ -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, + val minBiomeSize: Int, + val seaLevel: Int, +) { + + companion object { + + fun getDefault(): WorldGeneratorConfig { + return WorldGeneratorConfig( + width = 1024, + height = 256, + seed = TimeUtils.millis(), + maxSurfaceHeight = 208, + minSurfaceHeight = 128, + biomes = Biome.entries.toList(), + minBiomeSize = 64, + seaLevel = 192, + ) + } + + } + +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/objects/Block.kt b/core/src/ru/deadsoftware/cavedroid/game/objects/Block.kt deleted file mode 100644 index d44f017..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/objects/Block.kt +++ /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 - - 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 index 3b5c760..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/objects/Drop.java +++ /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 index 0e4c659..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/objects/DropController.java +++ /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 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 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 index 59d231d..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/objects/Item.kt +++ /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/container/Chest.kt b/core/src/ru/deadsoftware/cavedroid/game/objects/container/Chest.kt new file mode 100644 index 0000000..be820bc --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/objects/container/Chest.kt @@ -0,0 +1,14 @@ +package ru.deadsoftware.cavedroid.game.objects.container + +import ru.deadsoftware.cavedroid.game.GameItemsHolder + +class Chest(gameItemsHolder: GameItemsHolder) : Container(SIZE, gameItemsHolder) { + + override fun update(gameItemsHolder: GameItemsHolder) { + // no-op + } + + companion object { + private const val SIZE = 27 + } +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/objects/container/Container.kt b/core/src/ru/deadsoftware/cavedroid/game/objects/container/Container.kt new file mode 100644 index 0000000..1e13c64 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/objects/container/Container.kt @@ -0,0 +1,24 @@ +package ru.deadsoftware.cavedroid.game.objects.container + +import ru.deadsoftware.cavedroid.game.GameItemsHolder +import ru.deadsoftware.cavedroid.game.model.item.InventoryItem +import java.io.Serializable +import javax.annotation.OverridingMethodsMustInvokeSuper + +abstract class Container( + val size: Int, + gameItemsHolder: GameItemsHolder +) : Serializable { + + private val _items = Array(size) { gameItemsHolder.fallbackItem.toInventoryItem() } + + val items get() = _items.asList() as MutableList + + @OverridingMethodsMustInvokeSuper + open fun initItems(gameItemsHolder: GameItemsHolder) { + _items.forEach { it.init(gameItemsHolder) } + } + + abstract fun update(gameItemsHolder: GameItemsHolder) + +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/objects/container/ContainerController.kt b/core/src/ru/deadsoftware/cavedroid/game/objects/container/ContainerController.kt new file mode 100644 index 0000000..3facb6f --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/objects/container/ContainerController.kt @@ -0,0 +1,86 @@ +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.item.InventoryItem.Companion.isNoneOrNull +import ru.deadsoftware.cavedroid.game.objects.drop.DropController +import ru.deadsoftware.cavedroid.misc.utils.px +import java.io.Serializable +import javax.inject.Inject + +@GameScope +class ContainerController @Inject constructor( + _dropController: DropController, + _gameItemsHolder: GameItemsHolder +) : Serializable { + + @Suppress("UNNECESSARY_LATEINIT") + @Transient + private lateinit var dropController: DropController + + @Suppress("UNNECESSARY_LATEINIT") + @Transient + private lateinit var gameItemsHolder: GameItemsHolder + + private val containerMap = mutableMapOf() + + 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) { + 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) + } + } + + companion object { + private const val TAG = "ContainerController" + } + +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/objects/container/Furnace.kt b/core/src/ru/deadsoftware/cavedroid/game/objects/container/Furnace.kt new file mode 100644 index 0000000..1a75e89 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/objects/container/Furnace.kt @@ -0,0 +1,146 @@ +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.item.InventoryItem +import ru.deadsoftware.cavedroid.game.model.item.InventoryItem.Companion.isNoneOrNull +import ru.deadsoftware.cavedroid.game.model.item.Item + +class Furnace(gameItemsHolder: GameItemsHolder) : Container(SIZE, gameItemsHolder) { + + 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 + + @Transient + 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 + } + } + + companion object { + 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 + } + +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/objects/drop/Drop.kt b/core/src/ru/deadsoftware/cavedroid/game/objects/drop/Drop.kt new file mode 100644 index 0000000..edb3c81 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/objects/drop/Drop.kt @@ -0,0 +1,71 @@ +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.item.Item + +class Drop @JvmOverloads constructor( + x: Float, + y: Float, + _item: Item, + _amount: Int = 1, +) : Rectangle(x, y, DROP_SIZE, DROP_SIZE) { + + var amount: Int = _amount + private set + + val itemKey = _item.params.key + val velocity = getInitialVelocity() + var pickedUp = false + + @Suppress("UNNECESSARY_LATEINIT") + @Transient + lateinit var item: Item + private set + + init { + item = _item + } + + fun initItem(gameItemsHolder: GameItemsHolder) { + if (this::item.isInitialized) { + return + } + + item = gameItemsHolder.getItem(itemKey) + } + + fun canMagnetTo(rectangle: Rectangle): Boolean { + val magnetArea = getMagnetArea() + return Intersector.overlaps(magnetArea, rectangle) + } + + @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, + ) + } + + companion object { + private const val MAGNET_DISTANCE = 8f + + const val MAGNET_VELOCITY = 256f + const val DROP_SIZE = 8f + + private fun getInitialVelocity(): Vector2 = Vector2(0f, -1f) + } +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/objects/drop/DropController.java b/core/src/ru/deadsoftware/cavedroid/game/objects/drop/DropController.java new file mode 100644 index 0000000..6666fc8 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/objects/drop/DropController.java @@ -0,0 +1,58 @@ +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.item.InventoryItem; +import ru.deadsoftware.cavedroid.game.model.item.Item; + +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 mDrops = new LinkedList<>(); + + @Inject + public DropController() { + } + + public void initDrops(GameItemsHolder gameItemsHolder) { + mDrops.forEach((drop) -> 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) { + mDrops.forEach(callback::run); + } + + public Iterator getIterator() { + return mDrops.iterator(); + } + +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt new file mode 100644 index 0000000..0d34540 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt @@ -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/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt new file mode 100644 index 0000000..68af67b --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt @@ -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/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt new file mode 100644 index 0000000..0fa2756 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt @@ -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/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt new file mode 100644 index 0000000..d1fc65a --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt @@ -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/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt new file mode 100644 index 0000000..8353b0b --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt @@ -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/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt new file mode 100644 index 0000000..b6b22ae --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt @@ -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/ru/deadsoftware/cavedroid/game/render/IGameRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/IGameRenderer.kt new file mode 100644 index 0000000..88b5744 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/IGameRenderer.kt @@ -0,0 +1,20 @@ +package ru.deadsoftware.cavedroid.game.render + +import com.badlogic.gdx.graphics.g2d.SpriteBatch +import com.badlogic.gdx.graphics.glutils.ShapeRenderer +import com.badlogic.gdx.math.Rectangle + +interface IGameRenderer { + + val renderLayer: Int + + /** + * When called, [spriteBatch] is beginned! + */ + fun draw( + spriteBatch: SpriteBatch, + shapeRenderer: ShapeRenderer, + viewport: Rectangle, + delta: Float + ) +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt new file mode 100644 index 0000000..52ac971 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt @@ -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/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt new file mode 100644 index 0000000..f2198aa --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt @@ -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/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt new file mode 100644 index 0000000..945bb1e --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt @@ -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/ru/deadsoftware/cavedroid/game/render/windows/AbstractWindowRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/windows/AbstractWindowRenderer.kt new file mode 100644 index 0000000..57cf81b --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/windows/AbstractWindowRenderer.kt @@ -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 drawItemsGrid( + spriteBatch: SpriteBatch, + shapeRenderer: ShapeRenderer, + gridX: Float, + gridY: Float, + items: Iterable, + itemsInRow: Int, + cellWidth: Float, + cellHeight: Float + ) { + if (T::class != Item::class && T::class != InventoryItem::class) { + Gdx.app.log(_TAG, "Trying to draw items grid of not items") + return + } + + items.forEachIndexed { index, element -> + val item = element as? Item + val inventoryItem = element as? InventoryItem + + if (item == null && inventoryItem == null) { + throw IllegalStateException("This should be unreachable") + } + + if (item?.isNone() == true || inventoryItem?.item?.isNone() == true) { + return@forEachIndexed + } + + val itemX = gridX + (index % itemsInRow) * cellWidth + val itemY = gridY + (index / itemsInRow) * cellHeight + + inventoryItem?.draw(spriteBatch, shapeRenderer, itemX, itemY) + ?: item?.let { spriteBatch.drawSprite(it.sprite, itemX, itemY) } + } + } + + companion object { + protected const val _TAG = "AbstractWindowRenderer" + } + +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/windows/ChestWindowRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/windows/ChestWindowRenderer.kt new file mode 100644 index 0000000..78178bc --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/windows/ChestWindowRenderer.kt @@ -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/ru/deadsoftware/cavedroid/game/render/windows/CraftingWindowRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/windows/CraftingWindowRenderer.kt new file mode 100644 index 0000000..6772282 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/windows/CraftingWindowRenderer.kt @@ -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/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt new file mode 100644 index 0000000..6bfac42 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt @@ -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.. + if (index % 3 > 1 || index / 3 > 1) { + null + } else { + it ?: gameItemsHolder.fallbackItem.toInventoryItem() + } + }.asIterable(), + itemsInRow = GameWindowsConfigs.Survival.craftGridSize, + cellWidth = GameWindowsConfigs.Survival.itemsGridColWidth, + cellHeight = GameWindowsConfigs.Survival.itemsGridRowHeight, + ) + + window.craftResult?.draw( + spriteBatch = spriteBatch, + shapeRenderer = shapeRenderer, + x = windowX + GameWindowsConfigs.Survival.craftResultOffsetX, + y = windowY + GameWindowsConfigs.Survival.craftResultOffsetY + ) + + window.selectedItem?.drawSelected( + spriteBatch = spriteBatch, + x = Gdx.input.x * (viewport.width / Gdx.graphics.width), + y = Gdx.input.y * (viewport.height / Gdx.graphics.height) + ) + } + + companion object { + private const val SURVIVAL_WINDOW_KEY = "survival" + } +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/ui/TooltipManager.kt b/core/src/ru/deadsoftware/cavedroid/game/ui/TooltipManager.kt new file mode 100644 index 0000000..a8f5a9b --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/ui/TooltipManager.kt @@ -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/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsConfigs.kt b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsConfigs.kt new file mode 100644 index 0000000..2f1d264 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsConfigs.kt @@ -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/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsManager.kt b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsManager.kt new file mode 100644 index 0000000..8d32f94 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/GameWindowsManager.kt @@ -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/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindow.kt b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindow.kt new file mode 100644 index 0000000..ed15bac --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindow.kt @@ -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, + 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, 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/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindowWithCraftGrid.kt b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindowWithCraftGrid.kt new file mode 100644 index 0000000..066ca37 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/AbstractInventoryWindowWithCraftGrid.kt @@ -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 + + val craftResultList get() = items.subList(9, 10) as MutableList + + var craftResult: InventoryItem + get() = craftResultList[0] + set(value) { + craftResultList[0] = value + } + +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/ChestInventoryWindow.kt b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/ChestInventoryWindow.kt new file mode 100644 index 0000000..6f4a4fe --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/ChestInventoryWindow.kt @@ -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/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CraftingInventoryWindow.kt b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CraftingInventoryWindow.kt new file mode 100644 index 0000000..cd7c0ff --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CraftingInventoryWindow.kt @@ -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/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CreativeInventoryWindow.kt b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CreativeInventoryWindow.kt new file mode 100644 index 0000000..a38fd29 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/CreativeInventoryWindow.kt @@ -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/ru/deadsoftware/cavedroid/game/ui/windows/inventory/FurnaceInventoryWindow.kt b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/FurnaceInventoryWindow.kt new file mode 100644 index 0000000..42f7038 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/FurnaceInventoryWindow.kt @@ -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/ru/deadsoftware/cavedroid/game/ui/windows/inventory/SurvivalInventoryWindow.kt b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/SurvivalInventoryWindow.kt new file mode 100644 index 0000000..ce250fc --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/ui/windows/inventory/SurvivalInventoryWindow.kt @@ -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/ru/deadsoftware/cavedroid/game/world/GameWorld.java b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorld.java new file mode 100644 index 0000000..05a7d5f --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorld.java @@ -0,0 +1,314 @@ +package ru.deadsoftware.cavedroid.game.world; + +import com.badlogic.gdx.math.MathUtils; +import kotlin.Pair; +import ru.deadsoftware.cavedroid.game.GameItemsHolder; +import ru.deadsoftware.cavedroid.game.GameScope; +import ru.deadsoftware.cavedroid.game.mobs.MobsController; +import ru.deadsoftware.cavedroid.game.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.annotation.CheckForNull; +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, + @CheckForNull Block[][] foreMap, + @CheckForNull 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 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) 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()); + } + + @CheckForNull + private Container getContainerAt(int x, int y, int z) { + return mContainerController.getContainer(transformX(x), y, z); + } + + @CheckForNull + public Container getForegroundContainer(int x, int y) { + return getContainerAt(x, y, FOREGROUND_Z); + } + + @CheckForNull + public Container getBackgroundContainer(int x, int y) { + return getContainerAt(x, y, BACKGROUND_Z); + } + + @CheckForNull + public Furnace getForegroundFurnace(int x, int y) { + @CheckForNull + final Container container = getForegroundContainer(x, y); + + if (container instanceof Furnace) { + return (Furnace) container; + } + + return null; + } + + @CheckForNull + public Furnace getBackgroundFurnace(int x, int y) { + @CheckForNull + final Container container = getBackgroundContainer(x, y); + + if (container instanceof Furnace) { + return (Furnace) container; + } + + return null; + } +} \ No newline at end of file diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldBlocksLogicControllerTask.kt b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldBlocksLogicControllerTask.kt new file mode 100644 index 0000000..387407a --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldBlocksLogicControllerTask.kt @@ -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, + private val mobsController: MobsController, +) : Task() { + + private var currentRelativeChunk = 0 + + private fun getChunkStart(): Int { + val playerX = mobsController.player.mapX + val playerChunk = playerX / CHUNK_WIDTH + val currentChunk = playerChunk - CHUNKS / 2 + currentRelativeChunk + + return currentChunk * 16 + } + + private fun updateBlock(x: Int, y: Int) { + val block = gameWorld.getForeMap(x, y) + + if (block.isNone()) { + return + } + + val blockKey = block.params.key + val action = updateBlockActions[blockKey] + ?: updateBlockActions.getRequiresBlockAction().takeIf { block.params.requiresBlock } + + action?.update(x, y) + } + + override fun run() { + val startX = getChunkStart() + + for (y in gameWorld.height downTo 0) { + for (x in startX ..< startX + CHUNK_WIDTH) { + updateBlock(x, y) + } + } + + currentRelativeChunk = (currentRelativeChunk + 1) % CHUNKS + } + + companion object { + private const val TAG = "GameWorldBlocksLogicControllerTask" + + private const val CHUNK_WIDTH = 16 + private const val CHUNKS = 3 + + const val WORLD_BLOCKS_LOGIC_UPDATE_INTERVAL_SEC = .1f + } + +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.java b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.java new file mode 100644 index 0000000..416721f --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.java @@ -0,0 +1,223 @@ +package ru.deadsoftware.cavedroid.game.world; + +import com.badlogic.gdx.utils.Timer; +import ru.deadsoftware.cavedroid.game.GameItemsHolder; +import ru.deadsoftware.cavedroid.game.GameScope; +import ru.deadsoftware.cavedroid.game.mobs.MobsController; +import ru.deadsoftware.cavedroid.game.model.block.Block; + +import javax.annotation.CheckForNull; +import javax.inject.Inject; +import java.util.*; + +@GameScope +public class GameWorldFluidsLogicControllerTask extends Timer.Task { + + public static final float FLUID_UPDATE_INTERVAL_SEC = 0.25f; + + private short mUpdateTick = 0; + + private final GameWorld mGameWorld; + private final MobsController mMobsController; + private final GameItemsHolder mGameItemsHolder; + + private final Map, List> mFluidStatesMap; + + private final class UpdateCommand { + final Runnable command; + final int priority; + + private UpdateCommand(int priority, Runnable command) { + this.priority = priority; + this.command = command; + } + + private UpdateCommand(Block block, int x, int y, int priority) { + this(priority, () -> mGameWorld.setForeMap(x, y, block)); + } + + private UpdateCommand(Block.Fluid fluid, int x, int y) { + this(fluid, x, y, ((5 -fluid.getState() )+ 1) * (fluid.isLava() ? 2 : 1)); + } + + private int getPriority() { + return priority; + } + + private void exec() { + command.run(); + } + } + + private final PriorityQueue mUpdateQueue + = new PriorityQueue<>(Comparator.comparingInt(UpdateCommand::getPriority)); + + @Inject + GameWorldFluidsLogicControllerTask(GameWorld gameWorld, + MobsController mobsController, + GameItemsHolder gameItemsHolder) { + mGameWorld = gameWorld; + mMobsController = mobsController; + mGameItemsHolder = gameItemsHolder; + + final List waters = mGameItemsHolder.getBlocksByType(Block.Water.class); + waters.sort(Comparator.comparingInt(Block.Water::getState)); + + final List lavas = mGameItemsHolder.getBlocksByType(Block.Lava.class); + lavas.sort(Comparator.comparingInt(Block.Lava::getState)); + + mFluidStatesMap = new HashMap<>(); + mFluidStatesMap.put(Block.Water.class, waters); + mFluidStatesMap.put(Block.Lava.class, lavas); + } + + @CheckForNull + private List getFluidStateList(Block.Fluid fluid) { + return mFluidStatesMap.get(fluid.getClass()); + } + + private int getCurrentStateIndex(Block.Fluid fluid) { + @CheckForNull final List stateList = getFluidStateList(fluid); + + if (stateList == null) { + return -1; + } + + return stateList.indexOf(fluid); + } + + @CheckForNull + private Block.Fluid getNextStateBlock(Block.Fluid fluid) { + @CheckForNull final List stateList = getFluidStateList(fluid); + + if (stateList == null) { + return null; + } + + int currentState = stateList.indexOf(fluid); + + if (currentState < 0) { + return null; + } + + int nextState = currentState + 1; + + if (nextState == 1) { + nextState++; + } + + if (nextState < stateList.size()) { + return stateList.get(nextState); + } + + return null; + } + + private boolean noFluidNearby(int x, int y) { + return !mGameWorld.getForeMap(x, y - 1).isFluid() && + (!mGameWorld.getForeMap(x - 1, y).isFluid() || ((Block.Fluid)mGameWorld.getForeMap(x - 1, y)).getState() >= ((Block.Fluid)mGameWorld.getForeMap(x, y)).getState()) && + (!mGameWorld.getForeMap(x + 1, y).isFluid() || ((Block.Fluid)mGameWorld.getForeMap(x + 1, y)).getState() >= ((Block.Fluid)mGameWorld.getForeMap(x, y)).getState()); + } + + private boolean drainFluid(int x, int y) { + final Block block = mGameWorld.getForeMap(x, y); + + if (!(block instanceof Block.Fluid fluid)) { + return true; + } + + if (fluid.getState() > 0) { + if (noFluidNearby(x, y)) { + @CheckForNull final Block.Fluid nextState = getNextStateBlock(fluid); + if (nextState == null) { + mUpdateQueue.offer(new UpdateCommand(-1, () -> mGameWorld.resetForeMap(x, y))); + return true; + } + + mUpdateQueue.offer(new UpdateCommand(nextState, x, y)); + } + } + return false; + } + + private boolean fluidCanFlowThere(Block.Fluid fluid, Block targetBlock) { + return targetBlock == mGameItemsHolder.getFallbackBlock() || + (!targetBlock.getParams().getHasCollision() && !targetBlock.isFluid()) || + (fluid.getClass() == targetBlock.getClass() && fluid.getState() < ((Block.Fluid)targetBlock).getState()); + } + + private void flowFluidTo(Block.Fluid currentFluid, int x, int y, Block.Fluid nextStateFluid) { + final Block targetBlock = mGameWorld.getForeMap(x, y); + + if (fluidCanFlowThere(currentFluid, targetBlock)) { + mUpdateQueue.offer(new UpdateCommand(nextStateFluid, x, y)); + } else if (currentFluid.isWater() && targetBlock.isLava()) { + if (((Block.Lava)targetBlock).getState() > 0) { + mUpdateQueue.offer(new UpdateCommand(100, () -> mGameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("cobblestone")))); + } else { + mUpdateQueue.offer(new UpdateCommand(300, () -> mGameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("obsidian")))); + } + } else if (currentFluid.isLava() && targetBlock.isWater()) { + mUpdateQueue.offer(new UpdateCommand(200, () -> mGameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("stone")))); + } + } + + private void flowFluid(int x, int y) { + Block.Fluid fluid = (Block.Fluid) mGameWorld.getForeMap(x, y); + @CheckForNull final List stateList = getFluidStateList(fluid); + + if (stateList == null) { + return; + } + + if (fluid.getState() < stateList.size() - 1 && mGameWorld.getForeMap(x, y + 1).hasCollision()) { + @CheckForNull Block.Fluid nextState = getNextStateBlock(fluid); + + if (nextState == null) { + return; + } + + flowFluidTo(fluid, x - 1, y, nextState); + flowFluidTo(fluid, x + 1, y, nextState); + } else { + flowFluidTo(fluid, x, y + 1, stateList.get(1)); + } + + } + + private void updateFluids(int x, int y) { + final Block block = mGameWorld.getForeMap(x, y); + if (!block.isFluid() || (block.isLava() && mUpdateTick % 2 == 0)) { + return; + } + if (drainFluid(x, y)) { + return; + } + flowFluid(x, y); + } + + private void fluidUpdater() { + int midScreen = (int) mMobsController.getPlayer().x / 16; + for (int y = mGameWorld.getHeight() - 1; y >= 0; y--) { + for (int x = 0; x <= Math.min(mGameWorld.getWidth() / 2, 32); x++) { + updateFluids(midScreen + x, y); + updateFluids(midScreen - x, y); + } + } + + while (!mUpdateQueue.isEmpty()) { + final UpdateCommand command = mUpdateQueue.poll(); + command.exec(); + } + } + + @Override + public void run() { + if (mUpdateTick < 0xFF) { + mUpdateTick ++; + } else { + mUpdateTick = 0; + } + fluidUpdater(); + } +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt new file mode 100644 index 0000000..004a56b --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt @@ -0,0 +1,334 @@ +package ru.deadsoftware.cavedroid.game.world + +import com.google.common.primitives.Ints.min +import ru.deadsoftware.cavedroid.game.GameItemsHolder +import ru.deadsoftware.cavedroid.game.model.block.Block +import ru.deadsoftware.cavedroid.game.model.world.Biome +import ru.deadsoftware.cavedroid.game.model.world.generator.WorldGeneratorConfig +import kotlin.math.abs +import kotlin.math.max +import kotlin.random.Random + +class GameWorldGenerator( + private val config: WorldGeneratorConfig, + private val gameItemsHolder: GameItemsHolder, +) { + + private val random = Random(config.seed) + + private val foreMap by lazy { Array(config.width) { Array(config.height) { gameItemsHolder.fallbackBlock } } } + private val backMap by lazy { Array(config.width) { Array(config.height) { gameItemsHolder.fallbackBlock } } } + + private val heights by lazy { generateHeights() } + private val biomesMap by lazy { generateBiomes() } + + private val plainsPlants = listOf("dandelion", "rose", "tallgrass") + private val mushrooms = listOf("mushroom_brown", "mushroom_red",) + + private fun generateHeights(): IntArray { + val surfaceHeightRange = config.minSurfaceHeight .. config.maxSurfaceHeight + val result = IntArray(config.width) + + result[0] = surfaceHeightRange.random(random) + + for (x in 1 ..< config.width) { + val previous = result[x - 1] + var d = random.nextInt(-5, 6).let { if (it !in -4..4) it / abs(it) else 0 } + + if (previous + d !in surfaceHeightRange) { d = -d } + + if (result.lastIndex - x < abs(result[0] - previous) * 3) { + d = result[0].compareTo(previous).let { if (it != 0) it / abs(it) else 0 } + } + + result[x] = result[x - 1] + d + } + + return result + } + + private fun generateBiomes(): Map { + 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>> { + var biome = Biome.PLAINS + + for (x in 0 until config.width) { + biome = biomesMap[x] ?: biome + + when (biome) { + Biome.PLAINS -> plainsBiome(x) + Biome.DESERT -> desertBiome(x) + Biome.WINTER -> winterBiome(x) + } + + generateOres(x) + } + + fillWater() + + return Pair(foreMap, backMap) + } + +} diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldMobDamageControllerTask.kt b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldMobDamageControllerTask.kt new file mode 100644 index 0000000..f016c67 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldMobDamageControllerTask.kt @@ -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/ru/deadsoftware/cavedroid/menu/MenuProc.java b/core/src/ru/deadsoftware/cavedroid/menu/MenuProc.java index f96d4d4..aa3888a 100644 --- a/core/src/ru/deadsoftware/cavedroid/menu/MenuProc.java +++ b/core/src/ru/deadsoftware/cavedroid/menu/MenuProc.java @@ -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.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 java.lang.reflect.Method; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + 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) { - mMainConfig.getCaveGame().newGame(); + mMainConfig.getCaveGame().newGame(gameMode); } public void newGameClicked() { @@ -30,6 +32,10 @@ public class MenuProc extends Renderer { mMainConfig.getCaveGame().loadGame(); } + public void optionsClicked() { + mCurrentMenu = mMenuOptions; + } + public void quitClicked() { Gdx.app.exit(); } @@ -45,35 +51,67 @@ public class MenuProc extends Renderer { 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 MenuOptions mMenuOptions; 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(); - 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; } + 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); - 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 diff --git a/core/src/ru/deadsoftware/cavedroid/menu/objects/BooleanOptionButton.kt b/core/src/ru/deadsoftware/cavedroid/menu/objects/BooleanOptionButton.kt new file mode 100644 index 0000000..56329d5 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/menu/objects/BooleanOptionButton.kt @@ -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/ru/deadsoftware/cavedroid/menu/submenus/Menu.java b/core/src/ru/deadsoftware/cavedroid/menu/submenus/Menu.java index 9f8df4c..7e793b7 100644 --- a/core/src/ru/deadsoftware/cavedroid/menu/submenus/Menu.java +++ b/core/src/ru/deadsoftware/cavedroid/menu/submenus/Menu.java @@ -1,17 +1,21 @@ 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 kotlin.text.StringsKt; 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.misc.utils.AssetLoader; import java.util.HashMap; @@ -19,6 +23,7 @@ public abstract class Menu { protected final MainConfig mMainConfig; protected final MenuProc.Input mMenuInput; + protected final AssetLoader mAssetLoader; 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 */ - Menu(float width, float height, ButtonRenderer buttonRenderer, MainConfig mainConfig, MenuProc.Input menuInput) { + Menu(float width, + float height, + ButtonRenderer buttonRenderer, + MainConfig mainConfig, + MenuProc.Input menuInput, + AssetLoader assetLoader) { mWidth = width; mHeight = height; mButtonRenderer = buttonRenderer; mMainConfig = mainConfig; mMenuInput = menuInput; + mAssetLoader = assetLoader; initButtons(); } @@ -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) { - 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/ru/deadsoftware/cavedroid/menu/submenus/MenuMain.java b/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuMain.java index d2dedd2..6a40fc5 100644 --- a/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuMain.java +++ b/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuMain.java @@ -7,13 +7,20 @@ import ru.deadsoftware.cavedroid.menu.MenuProc; import ru.deadsoftware.cavedroid.menu.objects.Button; import ru.deadsoftware.cavedroid.menu.objects.ButtonEventListener; import ru.deadsoftware.cavedroid.menu.objects.ButtonRenderer; +import ru.deadsoftware.cavedroid.misc.utils.AssetLoader; +import javax.inject.Inject; import java.util.HashMap; public class MenuMain extends Menu { - public MenuMain(float width, float height, ButtonRenderer buttonRenderer, MainConfig mainConfig, MenuProc.Input menuInput) { - super(width, height, buttonRenderer, mainConfig, menuInput); + public MenuMain(float width, + float height, + ButtonRenderer buttonRenderer, + MainConfig mainConfig, + MenuProc.Input menuInput, + AssetLoader assetLoader) { + super(width, height, buttonRenderer, mainConfig, menuInput, assetLoader); } @Override @@ -21,15 +28,34 @@ public class MenuMain extends Menu { HashMap 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(Gdx.files.internal("json/menu_main_buttons.json")); + loadButtonsFromJson(mAssetLoader.getAssetHandle("json/menu_main_buttons.json")); if (GameSaver.exists(mMainConfig)) { getButtons().get("load_game").setType(Button.NORMAL); } } + + public static class Factory { + + private final MainConfig mMainConfig; + private final AssetLoader mAssetLoader; + + @Inject + public Factory(MainConfig mainConfig, AssetLoader assetLoader) { + mMainConfig = mainConfig; + mAssetLoader = assetLoader; + } + + public MenuMain get(float width, float height, ButtonRenderer buttonRenderer, MenuProc.Input menuInput) { + return new MenuMain(width, height, buttonRenderer, mMainConfig, menuInput, mAssetLoader); + } + + } + } diff --git a/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuNewGame.java b/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuNewGame.java index 2c3b38b..33985ff 100644 --- a/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuNewGame.java +++ b/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuNewGame.java @@ -1,17 +1,23 @@ package ru.deadsoftware.cavedroid.menu.submenus; -import com.badlogic.gdx.Gdx; import ru.deadsoftware.cavedroid.MainConfig; import ru.deadsoftware.cavedroid.menu.MenuProc; import ru.deadsoftware.cavedroid.menu.objects.ButtonEventListener; import ru.deadsoftware.cavedroid.menu.objects.ButtonRenderer; +import ru.deadsoftware.cavedroid.misc.utils.AssetLoader; +import javax.inject.Inject; import java.util.HashMap; public class MenuNewGame extends Menu { - public MenuNewGame(float width, float height, ButtonRenderer buttonRenderer, MainConfig mainConfig, MenuProc.Input menuInput) { - super(width, height, buttonRenderer, mainConfig, menuInput); + public MenuNewGame(float width, + float height, + ButtonRenderer buttonRenderer, + MainConfig mainConfig, + MenuProc.Input menuInput, + AssetLoader assetLoader) { + super(width, height, buttonRenderer, mainConfig, menuInput, assetLoader); } @Override @@ -25,6 +31,23 @@ public class MenuNewGame extends Menu { @Override protected void initButtons() { - loadButtonsFromJson(Gdx.files.internal("json/menu_new_game_buttons.json")); + loadButtonsFromJson(mAssetLoader.getAssetHandle("json/menu_new_game_buttons.json")); + } + + public static class Factory { + + private final MainConfig mMainConfig; + private final AssetLoader mAssetLoader; + + @Inject + public Factory(MainConfig mainConfig, AssetLoader assetLoader) { + mMainConfig = mainConfig; + mAssetLoader = assetLoader; + } + + public MenuNewGame get(float width, float height, ButtonRenderer buttonRenderer, MenuProc.Input menuInput) { + return new MenuNewGame(width, height, buttonRenderer, mMainConfig, menuInput, mAssetLoader); + } + } } diff --git a/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuOptions.kt b/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuOptions.kt new file mode 100644 index 0000000..7cc8a90 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenuOptions.kt @@ -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 { + val map = HashMap() + 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/ru/deadsoftware/cavedroid/menu/submenus/MenusFactory.kt b/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenusFactory.kt new file mode 100644 index 0000000..e31a9ad --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/menu/submenus/MenusFactory.kt @@ -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/ru/deadsoftware/cavedroid/misc/Assets.java b/core/src/ru/deadsoftware/cavedroid/misc/Assets.java index 335e3c0..71cad7a 100644 --- a/core/src/ru/deadsoftware/cavedroid/misc/Assets.java +++ b/core/src/ru/deadsoftware/cavedroid/misc/Assets.java @@ -1,27 +1,63 @@ package ru.deadsoftware.cavedroid.misc; -import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.ArrayMap; import com.badlogic.gdx.utils.JsonReader; import com.badlogic.gdx.utils.JsonValue; import ru.deadsoftware.cavedroid.game.objects.TouchButton; +import ru.deadsoftware.cavedroid.misc.utils.AssetLoader; +import java.io.File; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; public class Assets { + private static final int BLOCK_DAMAGE_STAGES = 10; + public static final JsonReader jsonReader = new JsonReader(); + + private static final List 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 textureRegions = new HashMap<>(); public static final ArrayMap guiMap = new ArrayMap<>(); private static final GlyphLayout glyphLayout = new GlyphLayout(); - static BitmapFont minecraftFont; + public static BitmapFont minecraftFont; + + public static Map blockTextures = new HashMap<>(); + public static Map 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(); + loadedTextures.forEach(Texture::dispose); + loadedTextures.clear(); + } + + private static Texture loadTexture(FileHandle fileHandle) { + Texture texture = new Texture(fileHandle); + loadedTextures.add(texture); + return texture; + } private static TextureRegion flippedRegion(Texture texture, int x, int y, int width, int height) { return new TextureRegion(texture, x, y + height, width, -height); @@ -39,27 +75,41 @@ public class Assets { return sprite; } - private static void loadMob(Sprite[][] sprite, String mob) { + private static void loadMob(AssetLoader assetLoader, Sprite[][] sprite, String mob) { for (int i = 0; i < sprite.length; i++) { for (int j = 0; j < sprite[i].length; j++) { - sprite[i][j] = flippedSprite(new Texture( - Gdx.files.internal("mobs/" + mob + "/" + i + "_" + j + ".png"))); + sprite[i][j] = flippedSprite(loadTexture( + assetLoader.getAssetHandle("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 json/texture_regions.json, cuts them to TextureRegions * and puts to {@link #textureRegions} HashMap */ - private static void loadJSON() { - JsonValue json = jsonReader.parse(Gdx.files.internal("json/texture_regions.json")); + private static void loadJSON(AssetLoader assetLoader) { + JsonValue json = jsonReader.parse(assetLoader.getAssetHandle("json/texture_regions.json")); for (JsonValue file = json.child(); file != null; file = file.next()) { - Texture texture = new Texture(Gdx.files.internal(file.name() + ".png")); + Texture texture = loadTexture(assetLoader.getAssetHandle(file.name() + ".png")); + final String[] pathSegments = file.name().split("/"); + final String name = pathSegments[pathSegments.length - 1]; if (file.size == 0) { - textureRegions.put(file.name(), - flippedRegion(texture, 0, 0, texture.getWidth(), texture.getHeight())); + 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); @@ -72,12 +122,103 @@ public class Assets { } } - public static void load() { - loadMob(playerSprite, "char"); - loadMob(pigSprite, "pig"); - loadJSON(); - minecraftFont = new BitmapFont(Gdx.files.internal("font.fnt"), true); + private static int getMouseKey(String name) { + switch (name) { + case "Left": + return Input.Buttons.LEFT; + case "Right": + return Input.Buttons.RIGHT; + case "Middle": + return Input.Buttons.MIDDLE; + case "Back": + return Input.Buttons.BACK; + case "Forward": + return Input.Buttons.FORWARD; + default: + return -1; + } + } + + private static void loadTouchButtonsFromJSON(AssetLoader assetLoader) { + JsonValue json = Assets.jsonReader.parse(assetLoader.getAssetHandle("json/touch_buttons.json")); + for (JsonValue key = json.child(); key != null; key = key.next()) { + float x = key.getFloat("x"); + float y = key.getFloat("y"); + float w = key.getFloat("w"); + float h = key.getFloat("h"); + boolean mouse = Assets.getBooleanFromJson(key, "mouse", false); + String name = key.getString("key"); + int code = mouse ? getMouseKey(name) : Input.Keys.valueOf(name); + if (x < 0) { + x = assetLoader.getGameRendererWidth() + x; + } + if (y < 0) { + y = assetLoader.getGameRendererHeight() + y; + } + Assets.guiMap.put(key.name(), new TouchButton(new Rectangle(x, y, w, h), code, mouse)); + } + + } + + private static Texture resolveTexture(AssetLoader assetLoader, String textureName, String lookUpPath, Map 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 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); } /** @@ -102,6 +243,10 @@ public class Assets { return json.has(name) ? json.getInt(name) : defaultValue; } + public static float getFloatFromJson(JsonValue json, String name, float defaultValue) { + return json.has(name) ? json.getFloat(name) : defaultValue; + } + public static String getStringFromJson(JsonValue json, String name, String defaultValue) { return json.has(name) ? json.getString(name) : defaultValue; } diff --git a/core/src/ru/deadsoftware/cavedroid/misc/ControlMode.java b/core/src/ru/deadsoftware/cavedroid/misc/ControlMode.java deleted file mode 100644 index dab3ac3..0000000 --- a/core/src/ru/deadsoftware/cavedroid/misc/ControlMode.java +++ /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/Renderer.java b/core/src/ru/deadsoftware/cavedroid/misc/Renderer.java index b59e977..c2577d3 100644 --- a/core/src/ru/deadsoftware/cavedroid/misc/Renderer.java +++ b/core/src/ru/deadsoftware/cavedroid/misc/Renderer.java @@ -5,12 +5,14 @@ import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.math.Rectangle; public abstract class Renderer implements InputProcessor { protected final ShapeRenderer shaper; protected final SpriteBatch spriter; private final OrthographicCamera camera; + private final Rectangle mCameraViewport; protected Renderer() { this(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); @@ -23,6 +25,9 @@ public abstract class Renderer implements InputProcessor { shaper.setProjectionMatrix(camera.combined); spriter = new SpriteBatch(); spriter.setProjectionMatrix(camera.combined); + + mCameraViewport = + new Rectangle(camera.position.x, camera.position.y, camera.viewportWidth, camera.viewportHeight); } public float getWidth() { @@ -43,6 +48,12 @@ public abstract class Renderer implements InputProcessor { public void setCamPos(float x, float y) { camera.position.set(x, y, 0); + mCameraViewport.x = x; + mCameraViewport.y = y; + } + + public Rectangle getCameraViewport() { + return mCameraViewport; } public void setFontScale(float scale) { @@ -101,8 +112,12 @@ public abstract class Renderer implements InputProcessor { } @Override - public boolean scrolled(int amount) { + public boolean scrolled(float amountX, float amountY) { return false; } + @Override + public boolean touchCancelled(int i, int i1, int i2, int i3) { + return false; + } } diff --git a/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindKeyboardInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindKeyboardInputHandler.kt new file mode 100644 index 0000000..558dc96 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindKeyboardInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindMouseInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindMouseInputHandler.kt new file mode 100644 index 0000000..af9b608 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindMouseInputHandler.kt @@ -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/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindPlaceBlockAction.kt b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindPlaceBlockAction.kt new file mode 100644 index 0000000..89f2dd0 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindPlaceBlockAction.kt @@ -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/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindRenderer.kt b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindRenderer.kt new file mode 100644 index 0000000..c1d96a9 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindRenderer.kt @@ -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/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindUpdateBlockAction.kt b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindUpdateBlockAction.kt new file mode 100644 index 0000000..310d69e --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindUpdateBlockAction.kt @@ -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/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindUseBlockAction.kt b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindUseBlockAction.kt new file mode 100644 index 0000000..46353ca --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindUseBlockAction.kt @@ -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/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindUseItemAction.kt b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindUseItemAction.kt new file mode 100644 index 0000000..7f17bfc --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/BindUseItemAction.kt @@ -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/ru/deadsoftware/cavedroid/misc/annotations/multibinding/MultibindingConfig.kt b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/MultibindingConfig.kt new file mode 100644 index 0000000..12427bd --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/annotations/multibinding/MultibindingConfig.kt @@ -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/ru/deadsoftware/cavedroid/misc/utils/ArrayMapExtensions.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/ArrayMapExtensions.kt new file mode 100644 index 0000000..70a5dc6 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/utils/ArrayMapExtensions.kt @@ -0,0 +1,9 @@ +package ru.deadsoftware.cavedroid.misc.utils + +import com.badlogic.gdx.utils.ObjectMap + +object ArrayMapExtensions { + operator fun ObjectMap.Entry.component1(): K = this.key + + operator fun ObjectMap.Entry.component2(): V = this.value +} diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt new file mode 100644 index 0000000..dff4d90 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt @@ -0,0 +1,35 @@ +package ru.deadsoftware.cavedroid.misc.utils + +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.files.FileHandle +import ru.deadsoftware.cavedroid.MainConfig +import ru.deadsoftware.cavedroid.game.GameScope +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AssetLoader @Inject constructor( + private val mainConfig: MainConfig, +) { + + fun getAssetHandle(path: String): FileHandle { + val texturePackPath = + mainConfig.assetsPackPath?.let { if (!it.endsWith(File.separator)) "$it${File.separator}" else it } + + return if (texturePackPath == null) { + Gdx.files.internal(path) + } else { + Gdx.files.absolute("$texturePackPath$path") + } + } + + fun getGameRendererWidth(): Float { + return mainConfig.width + } + + fun getGameRendererHeight(): Float { + return mainConfig.height + } + +} diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/ItemUtils.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/ItemUtils.kt new file mode 100644 index 0000000..8e1f730 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/utils/ItemUtils.kt @@ -0,0 +1,8 @@ +package ru.deadsoftware.cavedroid.misc.utils + +import ru.deadsoftware.cavedroid.game.GameItemsHolder.Companion.FALLBACK_ITEM_KEY +import ru.deadsoftware.cavedroid.game.model.item.Item + +fun Item.isFallback(): Boolean { + return this.params.key == FALLBACK_ITEM_KEY +} diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt new file mode 100644 index 0000000..24103e7 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt @@ -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/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt new file mode 100644 index 0000000..917f69b --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt @@ -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/ru/deadsoftware/cavedroid/misc/utils/SpriteOrigin.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteOrigin.kt new file mode 100644 index 0000000..d465ce1 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteOrigin.kt @@ -0,0 +1,31 @@ +package ru.deadsoftware.cavedroid.misc.utils + +import com.badlogic.gdx.graphics.g2d.Sprite + +/** + * An origin of a [com.badlogic.gdx.graphics.g2d.Sprite] + * + * x and y must be between 0 and 1 in percents from sprite size + */ +data class SpriteOrigin( + val x: Float, + val y: Float, +) { + + init { + assert(x in 0f..1f) + assert(y in 0f..1f) + } + + fun getFlipped(flipX: Boolean, flipY: Boolean): SpriteOrigin { + return SpriteOrigin( + x = if (flipX) 1 - x else x, + y = if (flipY) 1 - y else y, + ) + } + + fun applyToSprite(sprite: Sprite) { + sprite.setOrigin(sprite.width * x, sprite.height * y) + } + +} diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteUtils.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteUtils.kt new file mode 100644 index 0000000..a26c5b9 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteUtils.kt @@ -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/ru/deadsoftware/cavedroid/misc/utils/mobs/MobSprites.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/mobs/MobSprites.kt new file mode 100644 index 0000000..3b5eb4c --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/misc/utils/mobs/MobSprites.kt @@ -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/ru/deadsoftware/cavedroid/prefs/PreferencesStore.kt b/core/src/ru/deadsoftware/cavedroid/prefs/PreferencesStore.kt new file mode 100644 index 0000000..edc9517 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/prefs/PreferencesStore.kt @@ -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/desktop/build.gradle b/desktop/build.gradle index b3a6693..9127345 100644 --- a/desktop/build.gradle +++ b/desktop/build.gradle @@ -1,47 +1,50 @@ -apply plugin: "java" +plugins { + id 'java-library' + id 'kotlin' + id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlinVersion" +} + +java.targetCompatibility = JavaVersion.VERSION_17 +java.sourceCompatibility = JavaVersion.VERSION_1_8 -sourceCompatibility = 1.8 -sourceSets.main.java.srcDirs = [ "src/" ] +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) { +task run(dependsOn: build, type: JavaExec) { main = project.mainClassName classpath = sourceSets.main.runtimeClasspath standardInput = System.in workingDir = project.assetsDir ignoreExitValue = true as JavaExecSpec + args "--debug" } -task runTouch(dependsOn: classes, type: JavaExec) { +task runTouch(dependsOn: build, type: JavaExec) { main = project.mainClassName classpath = sourceSets.main.runtimeClasspath standardInput = System.in workingDir = project.assetsDir ignoreExitValue = true as JavaExecSpec - args "--touch" + args "--touch", "--debug" } -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) { +task dist(dependsOn: build, type: Jar) { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { attributes 'Main-Class': project.mainClassName } from { - configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } with jar } - -dist.dependsOn classes \ No newline at end of file +dependencies { + implementation project(":core") + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion" + api "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion" + api "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" +} diff --git a/desktop/gradle/wrapper/gradle-wrapper.properties b/desktop/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d884b3f --- /dev/null +++ b/desktop/gradle/wrapper/gradle-wrapper.properties @@ -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/ru/deadsoftware/cavedroid/desktop/DesktopLauncher.java b/desktop/src/ru/deadsoftware/cavedroid/desktop/DesktopLauncher.java index 2e9ae42..146d52a 100644 --- a/desktop/src/ru/deadsoftware/cavedroid/desktop/DesktopLauncher.java +++ b/desktop/src/ru/deadsoftware/cavedroid/desktop/DesktopLauncher.java @@ -1,25 +1,43 @@ package ru.deadsoftware.cavedroid.desktop; import com.badlogic.gdx.Files; -import com.badlogic.gdx.backends.lwjgl.LwjglApplication; -import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import ru.deadsoftware.cavedroid.CaveGame; class DesktopLauncher { - public static void main (String[] arg) { - LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); - config.addIcon("icons/icon256.png", Files.FileType.Internal); - config.addIcon("icons/icon128.png", Files.FileType.Internal); - config.foregroundFPS = 144; - config.title = "CaveDroid"; - config.width = 960; - config.height = 540; - config.forceExit = false; - - boolean touch = false; - for (String anArg : arg) { - if (anArg.equals("--touch")) touch = true; - } - new LwjglApplication(new CaveGame(System.getProperty("user.home") + "/.cavedroid", touch), config); - } + public static void main(String[] arg) { + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); + config.setWindowIcon(Files.FileType.Internal, "icons/icon256.png", "icons/icon128.png"); + config.setTitle("CaveDroid"); + config.setWindowedMode(960, 540); + config.useVsync(true); + + boolean touch = false; + boolean debug = false; + String assetsPath = null; + + for (String anArg : arg) { + if (anArg.equals("--touch")) { + touch = true; + } + + if (anArg.equals("--debug")) { + debug = true; + } + + if (anArg.startsWith("--assets")) { + String[] splitArg = anArg.split("="); + if (splitArg.length >= 2) { + assetsPath = splitArg[1]; + } + } + } + + CaveGame caveGame = new CaveGame(System.getProperty("user.home") + "/.cavedroid", touch, + new DesktopPreferencesStore(), assetsPath); + caveGame.setDebug(debug); + + new Lwjgl3Application(caveGame, config); + } } diff --git a/desktop/src/ru/deadsoftware/cavedroid/desktop/DesktopPreferencesStore.kt b/desktop/src/ru/deadsoftware/cavedroid/desktop/DesktopPreferencesStore.kt new file mode 100644 index 0000000..03591bb --- /dev/null +++ b/desktop/src/ru/deadsoftware/cavedroid/desktop/DesktopPreferencesStore.kt @@ -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/gen-changelog.sh b/gen-changelog.sh new file mode 100755 index 0000000..397e28d --- /dev/null +++ b/gen-changelog.sh @@ -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 '' + +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 '' diff --git a/gradle.properties b/gradle.properties index 339fa15..b7f66d4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms128m -Xmx1500m org.gradle.configureondemand=true +android.useAndroidX=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3745855..b349727 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Sep 26 22:30:23 NOVT 2019 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 4453cce..54f115a 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,22 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + ############################################################################## ## ## Gradle start up script for UN*X diff --git a/gradlew.bat b/gradlew.bat index f955316..c42c579 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem diff --git a/make-release.sh b/make-release.sh new file mode 100755 index 0000000..23acc64 --- /dev/null +++ b/make-release.sh @@ -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 index 0000000..cdd7e75 --- /dev/null +++ b/require-clean-work-tree.sh @@ -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 index 77ae463..99367df 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ -include 'desktop', 'android', 'core' \ No newline at end of file +include 'desktop', 'android', 'core' + diff --git a/up-version.sh b/up-version.sh new file mode 100755 index 0000000..7637355 --- /dev/null +++ b/up-version.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +new_version=$1 + +new_version_string=$(echo $new_version | sed 's/\(alpha\|beta\)\(.*\)/\1 \2/') + +sed -i 's/\(version\s=\s\)'"'"'.*'"'"'/\1'"'"''"$new_version"''"'"'/g' build.gradle +sed -i 's/\(versionName\s\)\".*\"/\1\"'"$new_version"'\"/g' android/build.gradle +sed -i 's/\(^\s*versionCode\s\)\([0-9]*\)/echo "\1$((\2+1))"/ge' android/build.gradle +sed -i 's/\(public static final String VERSION = \)\".*\"/\1\"'"$new_version_string"'\"/' core/src/ru/deadsoftware/cavedroid/CaveGame.java + +git add build.gradle android/build.gradle core/src/ru/deadsoftware/cavedroid/CaveGame.java + +git commit -m "Update version" +git tag "$new_version"