DEADSOFTWARE

73b9bcc0d3435fecc0eb80ffa23ddaf3cfa0b09d
[cavedroid.git] /
1 package ru.fredboy.cavedroid.ux.physics.task
2
3 import com.badlogic.gdx.utils.Timer
4 import ru.fredboy.cavedroid.common.di.GameScope
5 import ru.fredboy.cavedroid.common.utils.bl
6 import ru.fredboy.cavedroid.domain.items.model.block.Block
7 import ru.fredboy.cavedroid.domain.items.repository.ItemsRepository
8 import ru.fredboy.cavedroid.game.controller.mob.MobController
9 import ru.fredboy.cavedroid.game.world.GameWorld
10 import java.util.PriorityQueue
11 import javax.inject.Inject
12 import kotlin.math.min
13 import kotlin.reflect.KClass
14
15 @GameScope
16 class GameWorldFluidsLogicControllerTask @Inject constructor(
17 private val gameWorld: GameWorld,
18 private val mobController: MobController,
19 private val itemsRepository: ItemsRepository,
20 ) : Timer.Task() {
21
22 private var updateTick: Short = 0;
23
24 private val fluidStatesMap = mutableMapOf<KClass<out Block.Fluid>, List<Block.Fluid>>()
25
26 private val updateQueue = PriorityQueue<UpdateCommand>(16) { c1, c2 ->
27 c1.priority.compareTo(c2.priority)
28 }
29
30 init {
31 val waters = itemsRepository.getBlocksByType(Block.Water::class.java)
32 .sortedBy(Block.Water::state)
33 val lavas = itemsRepository.getBlocksByType(Block.Lava::class.java)
34 .sortedBy(Block.Lava::state)
35
36 fluidStatesMap[Block.Water::class] = waters
37 fluidStatesMap[Block.Lava::class] = lavas
38 }
39
40 private fun getNextStateBlock(fluid: Block.Fluid): Block.Fluid? {
41 val stateList = fluidStatesMap[fluid::class] ?: return null
42 val currentState = stateList.indexOf(fluid)
43 .takeIf { it >= 0 } ?: return null
44
45 var nextState = currentState + 1
46
47 if (nextState == 1) {
48 nextState++
49 }
50
51 if (nextState < stateList.size) {
52 return stateList[nextState]
53 }
54
55 return null
56 }
57
58 private fun noFluidNearby(x: Int, y: Int): Boolean {
59 val current = gameWorld.getForeMap(x, y)
60
61 if (current !is Block.Fluid) {
62 throw IllegalArgumentException("block at $x;$y is not a fluid")
63 }
64
65 val onTop = gameWorld.getForeMap(x, y - 1)
66 val onLeft = gameWorld.getForeMap(x - 1, y)
67 val onRight = gameWorld.getForeMap(x + 1, y)
68
69 return !onTop.isFluid() &&
70 (onLeft !is Block.Fluid || onLeft.state >= current.state) &&
71 (onRight !is Block.Fluid || onRight.state >= current.state)
72 }
73
74 private fun drainFluid(x: Int, y: Int): Boolean {
75 val fluid = (gameWorld.getForeMap(x, y) as? Block.Fluid)
76 ?: return true
77
78 if (fluid.state > 0) {
79 if (noFluidNearby(x, y)) {
80 val nexState = getNextStateBlock(fluid)
81 if (nexState == null) {
82 updateQueue.offer(UpdateCommand(-1) { gameWorld.resetForeMap(x, y) })
83 return true
84 }
85 updateQueue.offer(UpdateCommand(nexState, x, y))
86 }
87 }
88
89 return false
90 }
91
92 private fun fluidCanFlowThere(fluid: Block.Fluid, targetBlock: Block): Boolean {
93 return targetBlock.isNone() ||
94 (!targetBlock.params.hasCollision && !targetBlock.isFluid()) ||
95 (fluid::class == targetBlock::class && fluid.state < (targetBlock as Block.Fluid).state)
96 }
97
98 private fun flowFluidTo(currentFluid: Block.Fluid, x: Int, y: Int, nextStateFluid: Block.Fluid) {
99 val targetBlock = gameWorld.getForeMap(x, y)
100
101 val command = when {
102 fluidCanFlowThere(currentFluid, targetBlock) -> UpdateCommand(nextStateFluid, x, y)
103
104 currentFluid.isWater() && targetBlock is Block.Lava && targetBlock.state > 0 ->
105 UpdateCommand(100) { gameWorld.setForeMap(x, y, itemsRepository.getBlockByKey("cobblestone")) }
106
107 currentFluid.isWater() && targetBlock.isLava() ->
108 UpdateCommand(100) { gameWorld.setForeMap(x, y, itemsRepository.getBlockByKey("obsidian")) }
109
110 currentFluid.isLava() && targetBlock.isWater() ->
111 UpdateCommand(200) { gameWorld.setForeMap(x, y, itemsRepository.getBlockByKey("stone")) }
112
113 else -> null
114 }
115
116 command?.let(updateQueue::offer)
117 }
118
119 private fun flowFluid(x: Int, y: Int) {
120 val fluid = gameWorld.getForeMap(x, y) as Block.Fluid
121 val stateList = fluidStatesMap[fluid::class] ?: return
122
123 if (fluid.state < stateList.lastIndex && gameWorld.getForeMap(x, y + 1).params.hasCollision) {
124 val nextState = getNextStateBlock(fluid) ?: return
125
126 flowFluidTo(fluid, x - 1, y, nextState)
127 flowFluidTo(fluid, x + 1, y, nextState)
128 } else {
129 flowFluidTo(fluid, x, y + 1, stateList[1])
130 }
131 }
132
133 fun updateFluids(x: Int, y: Int) {
134 val block = gameWorld.getForeMap(x, y)
135 if (!block.isFluid() || (block.isLava() && updateTick % 2 == 0)) {
136 return
137 }
138
139 if (drainFluid(x, y)) {
140 return
141 }
142
143 flowFluid(x, y)
144 }
145
146 private fun fluidUpdater() {
147 val midScreen = mobController.player.x.bl
148
149 for (y in gameWorld.height - 1 downTo 0) {
150 for (x in 0 ..< min(gameWorld.width / 2, 32)) {
151 updateFluids(midScreen + x, y)
152 updateFluids(midScreen - x, y)
153 }
154 }
155
156 while (!updateQueue.isEmpty()) {
157 updateQueue.poll().exec()
158 }
159 }
160
161 override fun run() {
162 if (updateTick < 0xFF) {
163 updateTick++
164 } else {
165 updateTick = 1
166 }
167
168 fluidUpdater()
169 }
170
171 private inner class UpdateCommand(
172 val priority: Int,
173 val command: Runnable
174 ) {
175
176 constructor(block: Block, x: Int, y: Int, priority: Int) :
177 this(priority, Runnable { gameWorld.setForeMap(x, y, block) })
178
179 constructor(fluid: Block.Fluid, x: Int, y: Int) :
180 this(fluid, x, y, ((5 - fluid.state) + 1) * (if (fluid.isLava()) 2 else 1))
181
182 fun exec() = command.run()
183
184 }
185
186 companion object {
187 const val FLUID_UPDATE_INTERVAL_SEC = 0.25f
188 }
189
190 }