Skip to content

Commit 890c370

Browse files
author
adam-arold
committed
Add vision and Fog of War
1 parent 35a534c commit 890c370

10 files changed

Lines changed: 115 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.hexworks.cavesofzircon.attributes
2+
3+
import org.hexworks.amethyst.api.Attribute
4+
5+
data class Vision(val radius: Int): Attribute
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.hexworks.cavesofzircon.attributes.flags
2+
3+
import org.hexworks.amethyst.api.Attribute
4+
5+
object VisionBlocker : Attribute

src/main/kotlin/org/hexworks/cavesofzircon/attributes/types/EntityTypes.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ object StairsDown : BaseEntityType(
1616

1717
object StairsUp : BaseEntityType(
1818
name = "stairs up")
19+
20+
object FogOfWarType : BaseEntityType()

src/main/kotlin/org/hexworks/cavesofzircon/builders/EntityFactory.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ import org.hexworks.cavesofzircon.attributes.EntityActions
88
import org.hexworks.cavesofzircon.attributes.EntityPosition
99
import org.hexworks.cavesofzircon.attributes.EntityTile
1010
import org.hexworks.cavesofzircon.attributes.FungusSpread
11+
import org.hexworks.cavesofzircon.attributes.Vision
1112
import org.hexworks.cavesofzircon.attributes.flags.BlockOccupier
13+
import org.hexworks.cavesofzircon.attributes.flags.VisionBlocker
1214
import org.hexworks.cavesofzircon.attributes.types.Fungus
1315
import org.hexworks.cavesofzircon.attributes.types.Player
1416
import org.hexworks.cavesofzircon.attributes.types.StairsDown
1517
import org.hexworks.cavesofzircon.attributes.types.StairsUp
1618
import org.hexworks.cavesofzircon.attributes.types.Wall
1719
import org.hexworks.cavesofzircon.commands.Attack
1820
import org.hexworks.cavesofzircon.commands.Dig
21+
import org.hexworks.cavesofzircon.entities.FogOfWar
1922
import org.hexworks.cavesofzircon.systems.Attackable
2023
import org.hexworks.cavesofzircon.systems.CameraMover
2124
import org.hexworks.cavesofzircon.systems.Destructible
@@ -25,15 +28,19 @@ import org.hexworks.cavesofzircon.systems.InputReceiver
2528
import org.hexworks.cavesofzircon.systems.Movable
2629
import org.hexworks.cavesofzircon.systems.StairClimber
2730
import org.hexworks.cavesofzircon.systems.StairDescender
31+
import org.hexworks.cavesofzircon.world.Game
2832
import org.hexworks.cavesofzircon.world.GameContext
2933

3034
fun <T : EntityType> newGameEntityOfType(type: T, init: EntityBuilder<T, GameContext>.() -> Unit) =
3135
Entities.newEntityOfType(type, init)
3236

3337
object EntityFactory {
3438

39+
fun newFogOfWar(game: Game) = FogOfWar(game)
40+
3541
fun newWall() = newGameEntityOfType(Wall) {
3642
attributes(
43+
VisionBlocker,
3744
EntityPosition(),
3845
BlockOccupier,
3946
EntityTile(GameTileRepository.WALL))
@@ -52,6 +59,7 @@ object EntityFactory {
5259

5360
fun newPlayer() = newGameEntityOfType(Player) {
5461
attributes(
62+
Vision(9),
5563
EntityPosition(),
5664
CombatStats.create(
5765
maxHp = 100,

src/main/kotlin/org/hexworks/cavesofzircon/builders/GameColors.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ object GameColors {
1212
val FUNGUS_COLOR = TileColors.fromString("#85DD1B")
1313

1414
val ACCENT_COLOR = TileColors.fromString("#FFCD22")
15+
16+
val UNREVEALED_COLOR = TileColors.fromString("#000000")
1517
}

src/main/kotlin/org/hexworks/cavesofzircon/builders/GameTileRepository.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@ object GameTileRepository {
4343
.withBackgroundColor(GameColors.FLOOR_BACKGROUND)
4444
.withForegroundColor(GameColors.FUNGUS_COLOR)
4545
.buildCharacterTile()
46+
47+
val UNREVEALED = Tiles.newBuilder()
48+
.withCharacter(' ')
49+
.withBackgroundColor(GameColors.UNREVEALED_COLOR)
50+
.buildCharacterTile()
4651
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.hexworks.cavesofzircon.entities
2+
3+
import org.hexworks.amethyst.api.base.BaseEntity
4+
import org.hexworks.cavesofzircon.attributes.types.FogOfWarType
5+
import org.hexworks.cavesofzircon.builders.GameTileRepository
6+
import org.hexworks.cavesofzircon.extensions.position
7+
import org.hexworks.cavesofzircon.world.Game
8+
import org.hexworks.cavesofzircon.world.GameContext
9+
import org.hexworks.zircon.api.Layers
10+
import org.hexworks.zircon.api.graphics.Layer
11+
import java.util.concurrent.ConcurrentHashMap
12+
13+
class FogOfWar(game: Game) : BaseEntity<FogOfWarType, GameContext>(FogOfWarType) {
14+
15+
private val world = game.world
16+
private val player = game.player
17+
private val size = game.world.actualSize()
18+
19+
private val fowPerLevel = ConcurrentHashMap<Int, Layer>().also { fows ->
20+
repeat(size.zLength) { level ->
21+
val fow = Layers.newBuilder()
22+
.withSize(size.to2DSize())
23+
.build()
24+
.fill(GameTileRepository.UNREVEALED)
25+
fows[level] = fow
26+
world.pushOverlayAt(fow, level)
27+
}
28+
}
29+
30+
init {
31+
updateFOW()
32+
}
33+
34+
private fun updateFOW() {
35+
world.findVisiblePositionsFor(player).forEach {
36+
fowPerLevel[player.position.z]?.setTileAt(it, GameTileRepository.EMPTY)
37+
}
38+
}
39+
40+
override fun update(context: GameContext): Boolean {
41+
updateFOW()
42+
return true
43+
}
44+
}

src/main/kotlin/org/hexworks/cavesofzircon/extensions/EntityExtensions.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.hexworks.cavesofzircon.attributes.EntityActions
88
import org.hexworks.cavesofzircon.attributes.EntityPosition
99
import org.hexworks.cavesofzircon.attributes.EntityTile
1010
import org.hexworks.cavesofzircon.attributes.flags.BlockOccupier
11+
import org.hexworks.cavesofzircon.attributes.flags.VisionBlocker
1112
import org.hexworks.cavesofzircon.attributes.types.Combatant
1213
import org.hexworks.cavesofzircon.attributes.types.Player
1314
import org.hexworks.cavesofzircon.attributes.types.combatStats
@@ -34,6 +35,9 @@ val AnyGameEntity.tile: Tile
3435
val AnyGameEntity.isPlayer: Boolean
3536
get() = this.type == Player
3637

38+
val AnyGameEntity.blocksVision: Boolean
39+
get() = this.findAttribute(VisionBlocker::class).isPresent
40+
3741
fun GameEntity<Combatant>.whenHasNoHealthLeft(fn: () -> Unit) {
3842
if (combatStats.hp <= 0) {
3943
fn()

src/main/kotlin/org/hexworks/cavesofzircon/world/GameBuilder.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ class GameBuilder(val worldSize: Size3D = WORLD_SIZE) {
2929
val player = addPlayer()
3030
addFungi()
3131

32-
return Game.create(
32+
val game = Game.create(
3333
player = player,
3434
world = world)
35+
36+
world.addWorldEntity(EntityFactory.newFogOfWar(game))
37+
38+
return game
3539
}
3640

3741
private fun prepareWorld() = also {

src/main/kotlin/org/hexworks/cavesofzircon/world/World.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,25 @@ import org.hexworks.amethyst.api.Engine
44
import org.hexworks.amethyst.api.Engines
55
import org.hexworks.amethyst.api.entity.Entity
66
import org.hexworks.amethyst.api.entity.EntityType
7+
import org.hexworks.cavesofzircon.attributes.Vision
78
import org.hexworks.cavesofzircon.blocks.GameBlock
89
import org.hexworks.cavesofzircon.builders.GameBlockFactory
910
import org.hexworks.cavesofzircon.extensions.GameEntity
11+
import org.hexworks.cavesofzircon.extensions.blocksVision
1012
import org.hexworks.cavesofzircon.extensions.position
1113
import org.hexworks.cobalt.datatypes.Maybe
1214
import org.hexworks.cobalt.datatypes.extensions.fold
1315
import org.hexworks.cobalt.datatypes.extensions.map
1416
import org.hexworks.zircon.api.Positions
1517
import org.hexworks.zircon.api.builder.game.GameAreaBuilder
18+
import org.hexworks.zircon.api.data.Position
1619
import org.hexworks.zircon.api.data.Tile
1720
import org.hexworks.zircon.api.data.impl.Position3D
1821
import org.hexworks.zircon.api.data.impl.Size3D
1922
import org.hexworks.zircon.api.game.GameArea
2023
import org.hexworks.zircon.api.screen.Screen
24+
import org.hexworks.zircon.api.shape.EllipseFactory
25+
import org.hexworks.zircon.api.shape.LineFactory
2126
import org.hexworks.zircon.api.uievent.UIEvent
2227

2328
class World(startingBlocks: Map<Position3D, GameBlock>,
@@ -57,6 +62,10 @@ class World(startingBlocks: Map<Position3D, GameBlock>,
5762
}
5863
}
5964

65+
fun addWorldEntity(entity: Entity<EntityType, GameContext>) {
66+
engine.addEntity(entity)
67+
}
68+
6069
fun moveEntity(entity: GameEntity<EntityType>, position: Position3D): Boolean {
6170
var success = false
6271
val oldBlock = fetchBlockAt(entity.position)
@@ -115,6 +124,32 @@ class World(startingBlocks: Map<Position3D, GameBlock>,
115124
return position
116125
}
117126

127+
fun isVisionBlockedAt(pos: Position3D): Boolean {
128+
return fetchBlockAt(pos).fold(whenEmpty = { false }, whenPresent = {
129+
it.entities.any(GameEntity<EntityType>::blocksVision)
130+
})
131+
}
132+
133+
fun findVisiblePositionsFor(entity: GameEntity<EntityType>): Iterable<Position> {
134+
val centerPos = entity.position.to2DPosition()
135+
return entity.findAttribute(Vision::class).map { (radius) ->
136+
EllipseFactory.buildEllipse(
137+
fromPosition = centerPos,
138+
toPosition = centerPos.withRelativeX(radius).withRelativeY(radius))
139+
.positions()
140+
.flatMap { ringPos ->
141+
val result = mutableListOf<Position>()
142+
val iter = LineFactory.buildLine(centerPos, ringPos).iterator()
143+
do {
144+
val next = iter.next()
145+
result.add(next)
146+
} while (iter.hasNext() &&
147+
isVisionBlockedAt(Position3D.from2DPosition(next, entity.position.z)).not())
148+
result
149+
}
150+
}.orElse(listOf())
151+
}
152+
118153
companion object {
119154
private val DEFAULT_BLOCK = GameBlockFactory.floor()
120155
}

0 commit comments

Comments
 (0)