DEADSOFTWARE

Update version script
[cavedroid.git] / core / src / main / kotlin / ru / deadsoftware / cavedroid / game / world / GameWorldFluidsLogicControllerTask.kt
1 package ru.deadsoftware.cavedroid.game.world
3 import com.badlogic.gdx.utils.Timer
4 import ru.deadsoftware.cavedroid.game.GameItemsHolder
5 import ru.deadsoftware.cavedroid.game.GameScope
6 import ru.deadsoftware.cavedroid.game.mobs.MobsController
7 import ru.deadsoftware.cavedroid.game.model.block.Block
8 import ru.deadsoftware.cavedroid.misc.utils.bl
9 import java.util.PriorityQueue
10 import javax.inject.Inject
11 import kotlin.math.min
12 import kotlin.reflect.KClass
14 @GameScope
15 class GameWorldFluidsLogicControllerTask @Inject constructor(
16 private val gameWorld: GameWorld,
17 private val mobsController: MobsController,
18 private val gameItemsHolder: GameItemsHolder,
19 ) : Timer.Task() {
21 private var updateTick: Short = 0;
23 private val fluidStatesMap = mutableMapOf<KClass<out Block.Fluid>, List<Block.Fluid>>()
25 private val updateQueue = PriorityQueue<UpdateCommand>(16) { c1, c2 ->
26 c1.priority.compareTo(c2.priority)
27 }
29 init {
30 val waters = gameItemsHolder.getBlocksByType(Block.Water::class.java)
31 .sortedBy(Block.Water::state)
32 val lavas = gameItemsHolder.getBlocksByType(Block.Lava::class.java)
33 .sortedBy(Block.Lava::state)
35 fluidStatesMap[Block.Water::class] = waters
36 fluidStatesMap[Block.Lava::class] = lavas
37 }
39 private fun getNextStateBlock(fluid: Block.Fluid): Block.Fluid? {
40 val stateList = fluidStatesMap[fluid::class] ?: return null
41 val currentState = stateList.indexOf(fluid)
42 .takeIf { it >= 0 } ?: return null
44 var nextState = currentState + 1
46 if (nextState == 1) {
47 nextState++
48 }
50 if (nextState < stateList.size) {
51 return stateList[nextState]
52 }
54 return null
55 }
57 private fun noFluidNearby(x: Int, y: Int): Boolean {
58 val current = gameWorld.getForeMap(x, y)
60 if (current !is Block.Fluid) {
61 throw IllegalArgumentException("block at $x;$y is not a fluid")
62 }
64 val onTop = gameWorld.getForeMap(x, y - 1)
65 val onLeft = gameWorld.getForeMap(x - 1, y)
66 val onRight = gameWorld.getForeMap(x + 1, y)
68 return !onTop.isFluid() &&
69 (onLeft !is Block.Fluid || onLeft.state >= current.state) &&
70 (onRight !is Block.Fluid || onRight.state >= current.state)
71 }
73 private fun drainFluid(x: Int, y: Int): Boolean {
74 val fluid = (gameWorld.getForeMap(x, y) as? Block.Fluid)
75 ?: return true
77 if (fluid.state > 0) {
78 if (noFluidNearby(x, y)) {
79 val nexState = getNextStateBlock(fluid)
80 if (nexState == null) {
81 updateQueue.offer(UpdateCommand(-1) { gameWorld.resetForeMap(x, y) })
82 return true
83 }
84 updateQueue.offer(UpdateCommand(nexState, x, y))
85 }
86 }
88 return false
89 }
91 private fun fluidCanFlowThere(fluid: Block.Fluid, targetBlock: Block): Boolean {
92 return targetBlock.isNone() ||
93 (!targetBlock.params.hasCollision && !targetBlock.isFluid()) ||
94 (fluid::class == targetBlock::class && fluid.state < (targetBlock as Block.Fluid).state)
95 }
97 private fun flowFluidTo(currentFluid: Block.Fluid, x: Int, y: Int, nextStateFluid: Block.Fluid) {
98 val targetBlock = gameWorld.getForeMap(x, y)
100 val command = when {
101 fluidCanFlowThere(currentFluid, targetBlock) -> UpdateCommand(nextStateFluid, x, y)
103 currentFluid.isWater() && targetBlock is Block.Lava && targetBlock.state > 0 ->
104 UpdateCommand(100) { gameWorld.setForeMap(x, y, gameItemsHolder.getBlock("cobblestone")) }
106 currentFluid.isWater() && targetBlock.isLava() ->
107 UpdateCommand(100) { gameWorld.setForeMap(x, y, gameItemsHolder.getBlock("obsidian")) }
109 currentFluid.isLava() && targetBlock.isWater() ->
110 UpdateCommand(200) { gameWorld.setForeMap(x, y, gameItemsHolder.getBlock("stone")) }
112 else -> null
115 command?.let(updateQueue::offer)
118 private fun flowFluid(x: Int, y: Int) {
119 val fluid = gameWorld.getForeMap(x, y) as Block.Fluid
120 val stateList = fluidStatesMap[fluid::class] ?: return
122 if (fluid.state < stateList.lastIndex && gameWorld.getForeMap(x, y + 1).params.hasCollision) {
123 val nextState = getNextStateBlock(fluid) ?: return
125 flowFluidTo(fluid, x - 1, y, nextState)
126 flowFluidTo(fluid, x + 1, y, nextState)
127 } else {
128 flowFluidTo(fluid, x, y + 1, stateList[1])
132 fun updateFluids(x: Int, y: Int) {
133 val block = gameWorld.getForeMap(x, y)
134 if (!block.isFluid() || (block.isLava() && updateTick % 2 == 0)) {
135 return
138 if (drainFluid(x, y)) {
139 return
142 flowFluid(x, y)
145 private fun fluidUpdater() {
146 val midScreen = mobsController.player.x.bl
148 for (y in gameWorld.height - 1 downTo 0) {
149 for (x in 0 ..< min(gameWorld.width / 2, 32)) {
150 updateFluids(midScreen + x, y)
151 updateFluids(midScreen - x, y)
155 while (!updateQueue.isEmpty()) {
156 updateQueue.poll().exec()
160 override fun run() {
161 if (updateTick < 0xFF) {
162 updateTick++
163 } else {
164 updateTick = 1
167 fluidUpdater()
170 private inner class UpdateCommand(
171 val priority: Int,
172 val command: Runnable
173 ) {
175 constructor(block: Block, x: Int, y: Int, priority: Int) :
176 this(priority, Runnable { gameWorld.setForeMap(x, y, block) })
178 constructor(fluid: Block.Fluid, x: Int, y: Int) :
179 this(fluid, x, y, ((5 - fluid.state) + 1) * (if (fluid.isLava()) 2 else 1))
181 fun exec() = command.run()
185 companion object {
186 const val FLUID_UPDATE_INTERVAL_SEC = 0.25f