DEADSOFTWARE

Fluids updater in kotlin
authorfredboy <fredboy@protonmail.com>
Wed, 15 May 2024 16:57:25 +0000 (23:57 +0700)
committerfredboy <fredboy@protonmail.com>
Wed, 15 May 2024 16:57:25 +0000 (23:57 +0700)
core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.java [deleted file]
core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.kt [new file with mode: 0644]

diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.java b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.java
deleted file mode 100644 (file)
index 416721f..0000000
+++ /dev/null
@@ -1,223 +0,0 @@
-package ru.deadsoftware.cavedroid.game.world;
-
-import com.badlogic.gdx.utils.Timer;
-import ru.deadsoftware.cavedroid.game.GameItemsHolder;
-import ru.deadsoftware.cavedroid.game.GameScope;
-import ru.deadsoftware.cavedroid.game.mobs.MobsController;
-import ru.deadsoftware.cavedroid.game.model.block.Block;
-
-import javax.annotation.CheckForNull;
-import javax.inject.Inject;
-import java.util.*;
-
-@GameScope
-public class GameWorldFluidsLogicControllerTask extends Timer.Task {
-
-    public static final float FLUID_UPDATE_INTERVAL_SEC = 0.25f;
-
-    private short mUpdateTick = 0;
-
-    private final GameWorld mGameWorld;
-    private final MobsController mMobsController;
-    private final GameItemsHolder mGameItemsHolder;
-
-    private final Map<Class<? extends Block.Fluid>, List<? extends Block.Fluid>> mFluidStatesMap;
-
-    private final class UpdateCommand {
-        final Runnable command;
-        final int priority;
-
-        private UpdateCommand(int priority, Runnable command) {
-            this.priority = priority;
-            this.command = command;
-        }
-
-        private UpdateCommand(Block block, int x, int y, int priority) {
-            this(priority, () -> mGameWorld.setForeMap(x, y, block));
-        }
-
-        private UpdateCommand(Block.Fluid fluid, int x, int y) {
-            this(fluid, x, y, ((5 -fluid.getState() )+ 1) * (fluid.isLava() ? 2 : 1));
-        }
-
-        private int getPriority() {
-            return priority;
-        }
-
-        private void exec() {
-           command.run();
-        }
-    }
-
-    private final PriorityQueue<UpdateCommand> mUpdateQueue
-            = new PriorityQueue<>(Comparator.comparingInt(UpdateCommand::getPriority));
-
-    @Inject
-    GameWorldFluidsLogicControllerTask(GameWorld gameWorld,
-                                       MobsController mobsController,
-                                       GameItemsHolder gameItemsHolder) {
-        mGameWorld = gameWorld;
-        mMobsController = mobsController;
-        mGameItemsHolder = gameItemsHolder;
-
-        final List<Block.Water> waters = mGameItemsHolder.getBlocksByType(Block.Water.class);
-        waters.sort(Comparator.comparingInt(Block.Water::getState));
-
-        final List<Block.Lava> lavas = mGameItemsHolder.getBlocksByType(Block.Lava.class);
-        lavas.sort(Comparator.comparingInt(Block.Lava::getState));
-
-        mFluidStatesMap = new HashMap<>();
-        mFluidStatesMap.put(Block.Water.class, waters);
-        mFluidStatesMap.put(Block.Lava.class, lavas);
-    }
-
-    @CheckForNull
-    private List<? extends Block.Fluid> getFluidStateList(Block.Fluid fluid) {
-        return mFluidStatesMap.get(fluid.getClass());
-    }
-
-    private int getCurrentStateIndex(Block.Fluid fluid) {
-        @CheckForNull final List<? extends Block.Fluid> stateList = getFluidStateList(fluid);
-
-        if (stateList == null) {
-            return -1;
-        }
-
-        return stateList.indexOf(fluid);
-    }
-
-    @CheckForNull
-    private Block.Fluid getNextStateBlock(Block.Fluid fluid) {
-        @CheckForNull final List<? extends Block.Fluid> stateList = getFluidStateList(fluid);
-
-        if (stateList == null) {
-            return null;
-        }
-
-        int currentState = stateList.indexOf(fluid);
-
-        if (currentState < 0) {
-            return null;
-        }
-
-        int nextState = currentState + 1;
-
-        if (nextState == 1) {
-            nextState++;
-        }
-
-        if (nextState < stateList.size()) {
-            return stateList.get(nextState);
-        }
-
-        return null;
-    }
-
-    private boolean noFluidNearby(int x, int y) {
-        return !mGameWorld.getForeMap(x, y - 1).isFluid() &&
-                (!mGameWorld.getForeMap(x - 1, y).isFluid() || ((Block.Fluid)mGameWorld.getForeMap(x - 1, y)).getState() >= ((Block.Fluid)mGameWorld.getForeMap(x, y)).getState()) &&
-                (!mGameWorld.getForeMap(x + 1, y).isFluid() || ((Block.Fluid)mGameWorld.getForeMap(x + 1, y)).getState() >= ((Block.Fluid)mGameWorld.getForeMap(x, y)).getState());
-    }
-
-    private boolean drainFluid(int x, int y) {
-        final Block block = mGameWorld.getForeMap(x, y);
-
-        if (!(block instanceof Block.Fluid fluid)) {
-            return true;
-        }
-
-        if (fluid.getState() > 0) {
-            if (noFluidNearby(x, y)) {
-                @CheckForNull final Block.Fluid nextState = getNextStateBlock(fluid);
-                if (nextState == null) {
-                    mUpdateQueue.offer(new UpdateCommand(-1, () -> mGameWorld.resetForeMap(x, y)));
-                    return true;
-                }
-
-                mUpdateQueue.offer(new UpdateCommand(nextState, x, y));
-            }
-        }
-        return false;
-    }
-
-    private boolean fluidCanFlowThere(Block.Fluid fluid, Block targetBlock) {
-        return targetBlock == mGameItemsHolder.getFallbackBlock() ||
-                (!targetBlock.getParams().getHasCollision() && !targetBlock.isFluid()) ||
-                (fluid.getClass() == targetBlock.getClass() && fluid.getState() < ((Block.Fluid)targetBlock).getState());
-    }
-
-    private void flowFluidTo(Block.Fluid currentFluid, int x, int y, Block.Fluid nextStateFluid) {
-        final Block targetBlock = mGameWorld.getForeMap(x, y);
-
-        if (fluidCanFlowThere(currentFluid, targetBlock)) {
-            mUpdateQueue.offer(new UpdateCommand(nextStateFluid, x, y));
-        } else if (currentFluid.isWater() && targetBlock.isLava()) {
-            if (((Block.Lava)targetBlock).getState() > 0) {
-                mUpdateQueue.offer(new UpdateCommand(100, () -> mGameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("cobblestone"))));
-            } else {
-                mUpdateQueue.offer(new UpdateCommand(300, () -> mGameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("obsidian"))));
-            }
-        } else if (currentFluid.isLava() && targetBlock.isWater()) {
-            mUpdateQueue.offer(new UpdateCommand(200, () -> mGameWorld.setForeMap(x, y, mGameItemsHolder.getBlock("stone"))));
-        }
-    }
-
-    private void flowFluid(int x, int y) {
-        Block.Fluid fluid = (Block.Fluid) mGameWorld.getForeMap(x, y);
-        @CheckForNull final List<? extends Block.Fluid> stateList = getFluidStateList(fluid);
-
-        if (stateList == null) {
-            return;
-        }
-
-        if (fluid.getState() < stateList.size() - 1 && mGameWorld.getForeMap(x, y + 1).hasCollision()) {
-            @CheckForNull Block.Fluid nextState = getNextStateBlock(fluid);
-
-            if (nextState == null) {
-                return;
-            }
-
-            flowFluidTo(fluid, x - 1, y, nextState);
-            flowFluidTo(fluid, x + 1, y, nextState);
-        } else {
-            flowFluidTo(fluid, x, y + 1, stateList.get(1));
-        }
-
-    }
-
-    private void updateFluids(int x, int y) {
-        final Block block = mGameWorld.getForeMap(x, y);
-        if (!block.isFluid() || (block.isLava() && mUpdateTick % 2 == 0)) {
-            return;
-        }
-        if (drainFluid(x, y)) {
-            return;
-        }
-        flowFluid(x, y);
-    }
-
-    private void fluidUpdater() {
-        int midScreen = (int) mMobsController.getPlayer().x / 16;
-        for (int y = mGameWorld.getHeight() - 1; y >= 0; y--) {
-            for (int x = 0; x <= Math.min(mGameWorld.getWidth() / 2, 32); x++) {
-                updateFluids(midScreen + x, y);
-                updateFluids(midScreen - x, y);
-            }
-        }
-
-        while (!mUpdateQueue.isEmpty()) {
-            final UpdateCommand command = mUpdateQueue.poll();
-            command.exec();
-        }
-    }
-
-    @Override
-    public void run() {
-        if (mUpdateTick < 0xFF) {
-            mUpdateTick ++;
-        } else {
-            mUpdateTick = 0;
-        }
-        fluidUpdater();
-    }
-}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.kt b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.kt
new file mode 100644 (file)
index 0000000..d0f4eaa
--- /dev/null
@@ -0,0 +1,189 @@
+package ru.deadsoftware.cavedroid.game.world
+
+import com.badlogic.gdx.utils.Timer
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.misc.utils.bl
+import java.util.PriorityQueue
+import javax.inject.Inject
+import kotlin.math.min
+import kotlin.reflect.KClass
+
+@GameScope
+class GameWorldFluidsLogicControllerTask @Inject constructor(
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+    private val gameItemsHolder: GameItemsHolder,
+) : Timer.Task() {
+
+    private var updateTick: Short = 0;
+
+    private val fluidStatesMap = mutableMapOf<KClass<out Block.Fluid>, List<Block.Fluid>>()
+
+    private val updateQueue = PriorityQueue<UpdateCommand>(16) { c1, c2 ->
+        c1.priority.compareTo(c2.priority)
+    }
+
+    init {
+        val waters = gameItemsHolder.getBlocksByType(Block.Water::class.java)
+            .sortedBy(Block.Water::state)
+        val lavas = gameItemsHolder.getBlocksByType(Block.Lava::class.java)
+            .sortedBy(Block.Lava::state)
+
+        fluidStatesMap[Block.Water::class] = waters
+        fluidStatesMap[Block.Lava::class] = lavas
+    }
+
+    private fun getNextStateBlock(fluid: Block.Fluid): Block.Fluid? {
+        val stateList = fluidStatesMap[fluid::class] ?: return null
+        val currentState = stateList.indexOf(fluid)
+            .takeIf { it >= 0 } ?: return null
+
+        var nextState = currentState + 1
+
+        if (nextState == 1) {
+            nextState++
+        }
+
+        if (nextState < stateList.size) {
+            return stateList[nextState]
+        }
+
+        return null
+    }
+
+    private fun noFluidNearby(x: Int, y: Int): Boolean {
+        val current = gameWorld.getForeMap(x, y)
+
+        if (current !is Block.Fluid) {
+            throw IllegalArgumentException("block at $x;$y is not a fluid")
+        }
+
+        val onTop = gameWorld.getForeMap(x, y - 1)
+        val onLeft = gameWorld.getForeMap(x - 1, y)
+        val onRight = gameWorld.getForeMap(x + 1, y)
+
+        return !onTop.isFluid() &&
+                (onLeft !is Block.Fluid || onLeft.state >= current.state) &&
+                (onRight !is Block.Fluid || onRight.state >= current.state)
+    }
+
+    private fun drainFluid(x: Int, y: Int): Boolean {
+        val fluid = (gameWorld.getForeMap(x, y) as? Block.Fluid)
+            ?: return true
+
+        if (fluid.state > 0) {
+            if (noFluidNearby(x, y)) {
+                val nexState = getNextStateBlock(fluid)
+                if (nexState == null) {
+                    updateQueue.offer(UpdateCommand(-1) { gameWorld.resetForeMap(x, y) })
+                    return true
+                }
+                updateQueue.offer(UpdateCommand(nexState, x, y))
+            }
+        }
+
+        return false
+    }
+
+    private fun fluidCanFlowThere(fluid: Block.Fluid, targetBlock: Block): Boolean {
+        return targetBlock.isNone() ||
+                (!targetBlock.params.hasCollision && !targetBlock.isFluid()) ||
+                (fluid::class == targetBlock::class && fluid.state < (targetBlock as Block.Fluid).state)
+    }
+
+    private fun flowFluidTo(currentFluid: Block.Fluid, x: Int, y: Int, nextStateFluid: Block.Fluid) {
+        val targetBlock = gameWorld.getForeMap(x, y)
+
+        val command = when {
+            fluidCanFlowThere(currentFluid, targetBlock) -> UpdateCommand(nextStateFluid, x, y)
+
+            currentFluid.isWater() && targetBlock is Block.Lava && targetBlock.state > 0 ->
+                UpdateCommand(100) { gameWorld.setForeMap(x, y, gameItemsHolder.getBlock("cobblestone")) }
+
+            currentFluid.isWater() && targetBlock.isLava() ->
+                UpdateCommand(100) { gameWorld.setForeMap(x, y, gameItemsHolder.getBlock("obsidian")) }
+
+            currentFluid.isLava() && targetBlock.isWater() ->
+                UpdateCommand(200) { gameWorld.setForeMap(x, y, gameItemsHolder.getBlock("stone")) }
+
+            else -> null
+        }
+
+        command?.let(updateQueue::offer)
+    }
+
+    private fun flowFluid(x: Int, y: Int) {
+        val fluid = gameWorld.getForeMap(x, y) as Block.Fluid
+        val stateList = fluidStatesMap[fluid::class] ?: return
+
+          if (fluid.state < stateList.lastIndex && gameWorld.getForeMap(x, y + 1).params.hasCollision) {
+              val nextState = getNextStateBlock(fluid) ?: return
+
+              flowFluidTo(fluid, x - 1, y, nextState)
+              flowFluidTo(fluid, x + 1, y, nextState)
+          } else {
+              flowFluidTo(fluid, x, y + 1, stateList[1])
+          }
+    }
+
+    fun updateFluids(x: Int, y: Int) {
+        val block = gameWorld.getForeMap(x, y)
+        if (!block.isFluid() || (block.isLava() && updateTick % 2 == 0)) {
+            return
+        }
+
+        if (drainFluid(x, y)) {
+            return
+        }
+
+        flowFluid(x, y)
+    }
+
+    private fun fluidUpdater() {
+        val midScreen = mobsController.player.x.bl
+
+        for (y in gameWorld.height - 1 downTo 0) {
+            for (x in 0 ..< min(gameWorld.width / 2, 32)) {
+                updateFluids(midScreen + x, y)
+                updateFluids(midScreen - x, y)
+            }
+        }
+
+        while (!updateQueue.isEmpty()) {
+            updateQueue.poll().exec()
+        }
+    }
+
+    override fun run() {
+        if (updateTick < 0xFF) {
+            updateTick++
+        } else {
+            updateTick = 1
+        }
+
+        fluidUpdater()
+    }
+
+    private inner class UpdateCommand(
+        val priority: Int,
+        val command: Runnable
+    ) {
+
+        constructor(block: Block, x: Int, y: Int, priority: Int) :
+                this(priority, Runnable { gameWorld.setForeMap(x, y, block) })
+
+        constructor(fluid: Block.Fluid, x: Int, y: Int) :
+                this(fluid, x, y, ((5 - fluid.state) + 1) * (if (fluid.isLava()) 2 else 1))
+
+        fun exec() = command.run()
+
+    }
+
+    companion object {
+        const val FLUID_UPDATE_INTERVAL_SEC = 0.25f
+    }
+
+}
\ No newline at end of file