diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/attributes/ZirconCounter.kt b/src/main/kotlin/org/hexworks/cavesofzircon/attributes/ZirconCounter.kt new file mode 100644 index 0000000..ab6aafd --- /dev/null +++ b/src/main/kotlin/org/hexworks/cavesofzircon/attributes/ZirconCounter.kt @@ -0,0 +1,23 @@ +package org.hexworks.cavesofzircon.attributes + +import org.hexworks.cobalt.databinding.api.createPropertyFrom +import org.hexworks.cobalt.databinding.api.expression.concat +import org.hexworks.cobalt.databinding.api.property.Property +import org.hexworks.zircon.api.Components +import org.hexworks.zircon.api.component.Component + +data class ZirconCounter(private val zirconCountProperty: Property = createPropertyFrom(0)) : DisplayableAttribute { + + var zirconCount: Int by zirconCountProperty.asDelegate() + + override fun toComponent(width: Int): Component { + val zirconProp = createPropertyFrom("Zircons: ") + .concat(zirconCountProperty) { it.value.toString() } + return Components.header() + .withText(zirconProp.value) + .withSize(width, 1) + .build().apply { + textProperty.updateFrom(zirconProp) + } + } +} diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/attributes/types/EntityTypes.kt b/src/main/kotlin/org/hexworks/cavesofzircon/attributes/types/EntityTypes.kt index bce0383..8cdeace 100644 --- a/src/main/kotlin/org/hexworks/cavesofzircon/attributes/types/EntityTypes.kt +++ b/src/main/kotlin/org/hexworks/cavesofzircon/attributes/types/EntityTypes.kt @@ -3,7 +3,7 @@ package org.hexworks.cavesofzircon.attributes.types import org.hexworks.amethyst.api.base.BaseEntityType object Player : BaseEntityType( - name = "player"), Combatant, ItemHolder, EnergyUser, EquipmentHolder, ExperienceGainer + name = "player"), Combatant, ItemHolder, EnergyUser, EquipmentHolder, ExperienceGainer, ZirconHolder object Wall : BaseEntityType( name = "wall") @@ -27,6 +27,9 @@ object StairsDown : BaseEntityType( object StairsUp : BaseEntityType( name = "stairs up") +object Exit : BaseEntityType( + name = "exit") + object Zircon : BaseEntityType( name = "Zircon", description = "A small piece of Zircon. Its value is unfathomable."), Item diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/attributes/types/ZirconHolder.kt b/src/main/kotlin/org/hexworks/cavesofzircon/attributes/types/ZirconHolder.kt new file mode 100644 index 0000000..126e5d1 --- /dev/null +++ b/src/main/kotlin/org/hexworks/cavesofzircon/attributes/types/ZirconHolder.kt @@ -0,0 +1,10 @@ +package org.hexworks.cavesofzircon.attributes.types + +import org.hexworks.amethyst.api.entity.EntityType +import org.hexworks.cavesofzircon.attributes.ZirconCounter +import org.hexworks.cavesofzircon.extensions.GameEntity + +interface ZirconHolder : EntityType + +val GameEntity.zirconCounter: ZirconCounter + get() = findAttribute(ZirconCounter::class).get() diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/builders/EntityFactory.kt b/src/main/kotlin/org/hexworks/cavesofzircon/builders/EntityFactory.kt index c6563b6..2ee1ffa 100644 --- a/src/main/kotlin/org/hexworks/cavesofzircon/builders/EntityFactory.kt +++ b/src/main/kotlin/org/hexworks/cavesofzircon/builders/EntityFactory.kt @@ -16,6 +16,7 @@ import org.hexworks.cavesofzircon.attributes.ItemCombatStats import org.hexworks.cavesofzircon.attributes.ItemIcon import org.hexworks.cavesofzircon.attributes.NutritionalValue import org.hexworks.cavesofzircon.attributes.Vision +import org.hexworks.cavesofzircon.attributes.ZirconCounter import org.hexworks.cavesofzircon.attributes.flags.BlockOccupier import org.hexworks.cavesofzircon.attributes.flags.VisionBlocker import org.hexworks.cavesofzircon.attributes.types.Armor @@ -23,6 +24,7 @@ import org.hexworks.cavesofzircon.attributes.types.Bat import org.hexworks.cavesofzircon.attributes.types.BatMeat import org.hexworks.cavesofzircon.attributes.types.Club import org.hexworks.cavesofzircon.attributes.types.Dagger +import org.hexworks.cavesofzircon.attributes.types.Exit import org.hexworks.cavesofzircon.attributes.types.Fungus import org.hexworks.cavesofzircon.attributes.types.HeavyArmor import org.hexworks.cavesofzircon.attributes.types.Jacket @@ -59,6 +61,7 @@ import org.hexworks.cavesofzircon.systems.Movable import org.hexworks.cavesofzircon.systems.StairClimber import org.hexworks.cavesofzircon.systems.StairDescender import org.hexworks.cavesofzircon.systems.Wanderer +import org.hexworks.cavesofzircon.systems.ZirconGatherer import org.hexworks.cavesofzircon.world.Game import org.hexworks.cavesofzircon.world.GameContext import org.hexworks.zircon.api.GraphicalTilesetResources @@ -91,6 +94,11 @@ object EntityFactory { EntityPosition()) } + fun newExit() = newGameEntityOfType(Exit) { + attributes(EntityTile(GameTileRepository.EXIT), + EntityPosition()) + } + fun newPlayer() = newGameEntityOfType(Player) { attributes( Experience(), @@ -107,10 +115,11 @@ object EntityFactory { EnergyLevel(1000, 1000), Equipment( initialWeapon = newClub(), - initialArmor = newJacket())) + initialArmor = newJacket()), + ZirconCounter()) behaviors(InputReceiver, EnergyExpender) facets(Movable, CameraMover, StairClimber, StairDescender, Attackable, ExperienceAccumulator, Destructible, - ItemPicker, InventoryInspector, ItemDropper, EnergyExpender, DigestiveSystem) + ZirconGatherer, ItemPicker, InventoryInspector, ItemDropper, EnergyExpender, DigestiveSystem) } fun newFungus(fungusSpread: FungusSpread = FungusSpread()) = newGameEntityOfType(Fungus) { diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/builders/GameTileRepository.kt b/src/main/kotlin/org/hexworks/cavesofzircon/builders/GameTileRepository.kt index ec69e0b..c6df2d9 100644 --- a/src/main/kotlin/org/hexworks/cavesofzircon/builders/GameTileRepository.kt +++ b/src/main/kotlin/org/hexworks/cavesofzircon/builders/GameTileRepository.kt @@ -33,6 +33,12 @@ object GameTileRepository { .withBackgroundColor(GameColors.FLOOR_BACKGROUND) .buildCharacterTile() + val EXIT = Tiles.newBuilder() + .withCharacter('+') + .withForegroundColor(GameColors.ACCENT_COLOR) + .withBackgroundColor(GameColors.FLOOR_BACKGROUND) + .buildCharacterTile() + val PLAYER = Tiles.newBuilder() .withCharacter('@') .withBackgroundColor(GameColors.FLOOR_BACKGROUND) diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/events/PlayerDied.kt b/src/main/kotlin/org/hexworks/cavesofzircon/events/PlayerDied.kt new file mode 100644 index 0000000..af3340e --- /dev/null +++ b/src/main/kotlin/org/hexworks/cavesofzircon/events/PlayerDied.kt @@ -0,0 +1,5 @@ +package org.hexworks.cavesofzircon.events + +import org.hexworks.cobalt.events.api.Event + +data class PlayerDied(val cause: String) : Event diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/events/PlayerWonTheGame.kt b/src/main/kotlin/org/hexworks/cavesofzircon/events/PlayerWonTheGame.kt new file mode 100644 index 0000000..b1dc445 --- /dev/null +++ b/src/main/kotlin/org/hexworks/cavesofzircon/events/PlayerWonTheGame.kt @@ -0,0 +1,5 @@ +package org.hexworks.cavesofzircon.events + +import org.hexworks.cobalt.events.api.Event + +data class PlayerWonTheGame(val zircons: Int) : Event diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/systems/Destructible.kt b/src/main/kotlin/org/hexworks/cavesofzircon/systems/Destructible.kt index 3dec41f..a9cb23d 100644 --- a/src/main/kotlin/org/hexworks/cavesofzircon/systems/Destructible.kt +++ b/src/main/kotlin/org/hexworks/cavesofzircon/systems/Destructible.kt @@ -5,15 +5,21 @@ import org.hexworks.amethyst.api.base.BaseFacet import org.hexworks.amethyst.api.entity.EntityType import org.hexworks.cavesofzircon.commands.Destroy import org.hexworks.cavesofzircon.commands.EntityDestroyed +import org.hexworks.cavesofzircon.events.PlayerDied import org.hexworks.cavesofzircon.extensions.GameCommand +import org.hexworks.cavesofzircon.extensions.isPlayer import org.hexworks.cavesofzircon.functions.logGameEvent import org.hexworks.cavesofzircon.world.GameContext +import org.hexworks.zircon.internal.Zircon object Destructible : BaseFacet() { override fun executeCommand(command: GameCommand) = command.responseWhenCommandIs(Destroy::class) { (context, destroyer, target, cause) -> context.world.removeEntity(target) destroyer.executeCommand(EntityDestroyed(context, target, destroyer)) + if (target.isPlayer) { + Zircon.eventBus.publish(PlayerDied("You died $cause")) + } logGameEvent("$target dies $cause.") Consumed } diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/systems/ItemPicker.kt b/src/main/kotlin/org/hexworks/cavesofzircon/systems/ItemPicker.kt index 5eb0498..d08d362 100644 --- a/src/main/kotlin/org/hexworks/cavesofzircon/systems/ItemPicker.kt +++ b/src/main/kotlin/org/hexworks/cavesofzircon/systems/ItemPicker.kt @@ -32,8 +32,4 @@ object ItemPicker : BaseFacet() { Consumed } - private fun World.findTopItem(position: Position3D) = - fetchBlockAt(position).flatMap { block -> - Maybe.ofNullable(block.entities.filterType().firstOrNull()) - } } diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/systems/StairDescender.kt b/src/main/kotlin/org/hexworks/cavesofzircon/systems/StairDescender.kt index b762df6..68e8564 100644 --- a/src/main/kotlin/org/hexworks/cavesofzircon/systems/StairDescender.kt +++ b/src/main/kotlin/org/hexworks/cavesofzircon/systems/StairDescender.kt @@ -3,27 +3,37 @@ package org.hexworks.cavesofzircon.systems import org.hexworks.amethyst.api.Consumed import org.hexworks.amethyst.api.base.BaseFacet import org.hexworks.amethyst.api.entity.EntityType +import org.hexworks.cavesofzircon.attributes.types.Exit +import org.hexworks.cavesofzircon.attributes.types.Player import org.hexworks.cavesofzircon.attributes.types.StairsDown +import org.hexworks.cavesofzircon.attributes.types.zirconCounter import org.hexworks.cavesofzircon.blocks.GameBlock import org.hexworks.cavesofzircon.commands.MoveDown +import org.hexworks.cavesofzircon.events.PlayerWonTheGame import org.hexworks.cavesofzircon.extensions.GameCommand import org.hexworks.cavesofzircon.extensions.position +import org.hexworks.cavesofzircon.extensions.whenTypeIs import org.hexworks.cavesofzircon.functions.logGameEvent import org.hexworks.cavesofzircon.world.GameContext import org.hexworks.cobalt.datatypes.extensions.map +import org.hexworks.zircon.internal.Zircon object StairDescender : BaseFacet() { - override fun executeCommand(command: GameCommand) = command.responseWhenCommandIs(MoveDown::class) { (context, player) -> + override fun executeCommand(command: GameCommand) = command.responseWhenCommandIs(MoveDown::class) { (context, source) -> val world = context.world - val playerPos = player.position - world.fetchBlockAt(playerPos).map { block -> - if (block.hasStairsDown) { - logGameEvent("You move down one level...") - world.moveEntity(player, playerPos.withRelativeZ(-1)) - world.scrollOneDown() - } else { - logGameEvent("You search for a trapdoor, but you find nothing.") + val pos = source.position + world.fetchBlockAt(pos).map { block -> + when { + block.hasStairsDown -> { + logGameEvent("You move down one level...") + world.moveEntity(source, pos.withRelativeZ(-1)) + world.scrollOneDown() + } + block.hasExit -> source.whenTypeIs { + Zircon.eventBus.publish(PlayerWonTheGame(it.zirconCounter.zirconCount)) + } + else -> logGameEvent("You search for a trapdoor, but you find nothing.") } } Consumed @@ -31,4 +41,7 @@ object StairDescender : BaseFacet() { private val GameBlock.hasStairsDown: Boolean get() = this.entities.any { it.type == StairsDown } + + private val GameBlock.hasExit: Boolean + get() = this.entities.any { it.type == Exit } } diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/systems/ZirconGatherer.kt b/src/main/kotlin/org/hexworks/cavesofzircon/systems/ZirconGatherer.kt new file mode 100644 index 0000000..061f628 --- /dev/null +++ b/src/main/kotlin/org/hexworks/cavesofzircon/systems/ZirconGatherer.kt @@ -0,0 +1,37 @@ +package org.hexworks.cavesofzircon.systems + +import org.hexworks.amethyst.api.Command +import org.hexworks.amethyst.api.Consumed +import org.hexworks.amethyst.api.Pass +import org.hexworks.amethyst.api.Response +import org.hexworks.amethyst.api.base.BaseFacet +import org.hexworks.amethyst.api.entity.EntityType +import org.hexworks.cavesofzircon.attributes.ZirconCounter +import org.hexworks.cavesofzircon.attributes.types.Zircon +import org.hexworks.cavesofzircon.attributes.types.ZirconHolder +import org.hexworks.cavesofzircon.attributes.types.zirconCounter +import org.hexworks.cavesofzircon.commands.PickItemUp +import org.hexworks.cavesofzircon.extensions.whenTypeIs +import org.hexworks.cavesofzircon.functions.logGameEvent +import org.hexworks.cavesofzircon.world.GameContext +import org.hexworks.cobalt.datatypes.extensions.map + +object ZirconGatherer : BaseFacet(ZirconCounter::class) { + + override fun executeCommand(command: Command) = command.responseWhenCommandIs(PickItemUp::class) { + val (context, source, position) = it + var response: Response = Pass + val world = context.world + world.findTopItem(position).map { item -> + source.whenTypeIs { zirconHolder -> + if (item.type == Zircon) { + zirconHolder.zirconCounter.zirconCount++ + world.removeEntity(item) + logGameEvent("$zirconHolder picked up a Zircon!") + response = Consumed + } + } + } + response + } +} diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/view/LoseView.kt b/src/main/kotlin/org/hexworks/cavesofzircon/view/LoseView.kt index 7880fc2..df331e3 100644 --- a/src/main/kotlin/org/hexworks/cavesofzircon/view/LoseView.kt +++ b/src/main/kotlin/org/hexworks/cavesofzircon/view/LoseView.kt @@ -1,43 +1,49 @@ package org.hexworks.cavesofzircon.view -import org.hexworks.zircon.api.ColorThemes +import org.hexworks.cavesofzircon.GameConfig +import org.hexworks.cavesofzircon.GameConfig.WORLD_SIZE +import org.hexworks.cavesofzircon.world.GameBuilder import org.hexworks.zircon.api.Components import org.hexworks.zircon.api.component.ComponentAlignment +import org.hexworks.zircon.api.component.ComponentAlignment.BOTTOM_LEFT +import org.hexworks.zircon.api.component.ComponentAlignment.BOTTOM_RIGHT import org.hexworks.zircon.api.extensions.onComponentEvent import org.hexworks.zircon.api.graphics.BoxType import org.hexworks.zircon.api.mvc.base.BaseView import org.hexworks.zircon.api.uievent.ComponentEventType import org.hexworks.zircon.api.uievent.Processed -class LoseView : BaseView() { +class LoseView(private val causeOfDeath: String) : BaseView() { - override val theme = ColorThemes.arc() + override val theme = GameConfig.THEME override fun onDock() { val msg = "Game Over" val header = Components.textBox() .withContentWidth(30) .addHeader(msg) + .addParagraph(causeOfDeath) + .addNewLine() .withAlignmentWithin(screen, ComponentAlignment.CENTER) .build() val restartButton = Components.button() - .withAlignmentAround(header, ComponentAlignment.BOTTOM_LEFT) + .withAlignmentAround(header, BOTTOM_LEFT) .withText("Restart") .wrapSides(false) .wrapWithBox() .withBoxType(BoxType.SINGLE) .build() val exitButton = Components.button() - .withAlignmentAround(header, ComponentAlignment.BOTTOM_RIGHT) + .withAlignmentAround(header, BOTTOM_RIGHT) .withText("Quit") .wrapSides(false) .wrapWithBox() .withBoxType(BoxType.SINGLE) .build() - // TODO: tutorial restartButton.onComponentEvent(ComponentEventType.ACTIVATED) { - replaceWith(PlayView()) + replaceWith(PlayView(GameBuilder( + worldSize = WORLD_SIZE).buildGame())) close() Processed } diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/view/PlayView.kt b/src/main/kotlin/org/hexworks/cavesofzircon/view/PlayView.kt index 27df160..cb8e1fd 100644 --- a/src/main/kotlin/org/hexworks/cavesofzircon/view/PlayView.kt +++ b/src/main/kotlin/org/hexworks/cavesofzircon/view/PlayView.kt @@ -3,7 +3,9 @@ package org.hexworks.cavesofzircon.view import org.hexworks.cavesofzircon.GameConfig import org.hexworks.cavesofzircon.blocks.GameBlock import org.hexworks.cavesofzircon.events.GameLogEvent +import org.hexworks.cavesofzircon.events.PlayerDied import org.hexworks.cavesofzircon.events.PlayerGainedLevel +import org.hexworks.cavesofzircon.events.PlayerWonTheGame import org.hexworks.cavesofzircon.view.dialog.LevelUpDialog import org.hexworks.cavesofzircon.view.fragment.PlayerStatsFragment import org.hexworks.cavesofzircon.world.Game @@ -60,7 +62,14 @@ class PlayView(private val game: Game = GameBuilder.defaultGame()) : BaseView() Zircon.eventBus.subscribe { screen.openModal(LevelUpDialog(screen, game.player)) } - + Zircon.eventBus.subscribe { + replaceWith(WinView(it.zircons)) + close() + } + Zircon.eventBus.subscribe { + replaceWith(LoseView(it.cause)) + close() + } val gameComponent = GameComponents.newGameComponentBuilder() .withGameArea(game.world) .withVisibleSize(game.world.visibleSize()) diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/view/WinView.kt b/src/main/kotlin/org/hexworks/cavesofzircon/view/WinView.kt index 562ad44..60d87e1 100644 --- a/src/main/kotlin/org/hexworks/cavesofzircon/view/WinView.kt +++ b/src/main/kotlin/org/hexworks/cavesofzircon/view/WinView.kt @@ -1,6 +1,7 @@ package org.hexworks.cavesofzircon.view -import org.hexworks.zircon.api.ColorThemes +import org.hexworks.cavesofzircon.GameConfig +import org.hexworks.cavesofzircon.world.GameBuilder import org.hexworks.zircon.api.Components import org.hexworks.zircon.api.component.ComponentAlignment import org.hexworks.zircon.api.extensions.onComponentEvent @@ -9,15 +10,18 @@ import org.hexworks.zircon.api.mvc.base.BaseView import org.hexworks.zircon.api.uievent.ComponentEventType import org.hexworks.zircon.api.uievent.Processed -class WinView : BaseView() { +class WinView(private val zircons: Int) : BaseView() { - override val theme = ColorThemes.arc() + override val theme = GameConfig.THEME override fun onDock() { val msg = "You won!" val header = Components.textBox() - .withContentWidth(30) + .withContentWidth(GameConfig.WINDOW_WIDTH / 2) .addHeader(msg) + .addNewLine() + .addParagraph("Congratulations! You have escaped from Caves of Zircon!", withNewLine = false) + .addParagraph("You've managed to find $zircons Zircons.") .withAlignmentWithin(screen, ComponentAlignment.CENTER) .build() val restartButton = Components.button() @@ -35,9 +39,9 @@ class WinView : BaseView() { .withBoxType(BoxType.SINGLE) .build() - // TODO: tutorial restartButton.onComponentEvent(ComponentEventType.ACTIVATED) { - replaceWith(PlayView()) + replaceWith(PlayView(GameBuilder( + worldSize = GameConfig.WORLD_SIZE).buildGame())) close() Processed } @@ -52,4 +56,3 @@ class WinView : BaseView() { screen.addComponent(exitButton) } } - diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/world/GameBuilder.kt b/src/main/kotlin/org/hexworks/cavesofzircon/world/GameBuilder.kt index a0eb533..480824b 100644 --- a/src/main/kotlin/org/hexworks/cavesofzircon/world/GameBuilder.kt +++ b/src/main/kotlin/org/hexworks/cavesofzircon/world/GameBuilder.kt @@ -32,6 +32,7 @@ class GameBuilder(val worldSize: Size3D = WORLD_SIZE) { addBats() addZircons() addZombies() + addExit() val game = Game.create( player = player, @@ -84,6 +85,10 @@ class GameBuilder(val worldSize: Size3D = WORLD_SIZE) { } } + private fun addExit() = also { + EntityFactory.newExit().addToWorld(0) + } + private fun GameEntity.addToWorld( atLevel: Int, atArea: Size = world.actualSize().to2DSize()): GameEntity { diff --git a/src/main/kotlin/org/hexworks/cavesofzircon/world/World.kt b/src/main/kotlin/org/hexworks/cavesofzircon/world/World.kt index ca5ceba..5a38f3b 100644 --- a/src/main/kotlin/org/hexworks/cavesofzircon/world/World.kt +++ b/src/main/kotlin/org/hexworks/cavesofzircon/world/World.kt @@ -5,12 +5,15 @@ import org.hexworks.amethyst.api.Engines import org.hexworks.amethyst.api.entity.Entity import org.hexworks.amethyst.api.entity.EntityType import org.hexworks.cavesofzircon.attributes.Vision +import org.hexworks.cavesofzircon.attributes.types.Item import org.hexworks.cavesofzircon.blocks.GameBlock import org.hexworks.cavesofzircon.builders.GameBlockFactory import org.hexworks.cavesofzircon.extensions.GameEntity import org.hexworks.cavesofzircon.extensions.blocksVision +import org.hexworks.cavesofzircon.extensions.filterType import org.hexworks.cavesofzircon.extensions.position import org.hexworks.cobalt.datatypes.Maybe +import org.hexworks.cobalt.datatypes.extensions.flatMap import org.hexworks.cobalt.datatypes.extensions.fold import org.hexworks.cobalt.datatypes.extensions.map import org.hexworks.zircon.api.Positions @@ -81,6 +84,11 @@ class World(startingBlocks: Map, return success } + fun findTopItem(position: Position3D) = + fetchBlockAt(position).flatMap { block -> + Maybe.ofNullable(block.entities.filterType().firstOrNull()) + } + fun removeEntity(entity: Entity) { fetchBlockAt(entity.position).map { it.removeEntity(entity)