From: fredboy Date: Wed, 15 May 2024 16:57:25 +0000 (+0700) Subject: Fluids updater in kotlin X-Git-Url: http://deadsoftware.ru/gitweb?a=commitdiff_plain;ds=sidebyside;h=f094aae367211d5b5f48468762a9fe9696288e89;p=cavedroid.git Fluids updater in kotlin --- 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 index 416721f..0000000 --- a/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.java +++ /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, 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/GameWorldFluidsLogicControllerTask.kt b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.kt new file mode 100644 index 0000000..d0f4eaa --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/world/GameWorldFluidsLogicControllerTask.kt @@ -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, List>() + + private val updateQueue = PriorityQueue(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