Skip to content

Commit

Permalink
Add experience and leveling up
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-arold committed Jun 26, 2019
1 parent 700c6a4 commit 8ad9f45
Show file tree
Hide file tree
Showing 14 changed files with 260 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ data class CombatStats(val maxHpProperty: Property<Int>,
val defenseValue: Int by defenseValueProperty.asDelegate()

override fun toComponent(width: Int) = Components.vbox()
.withSize(width, 5)
.withSize(width, 4)
.build().apply {
val hpLabel = Components.label()
.withSize(width, 1)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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

data class Experience(val currentXPProperty: Property<Int> = createPropertyFrom(0),
val currentLevelProperty: Property<Int> = createPropertyFrom(1)) : DisplayableAttribute {

var currentXP: Int by currentXPProperty.asDelegate()
var currentLevel: Int by currentLevelProperty.asDelegate()

override fun toComponent(width: Int) = Components.vbox()
.withSize(width, 3)
.build().apply {
val xpLabel = Components.label()
.withSize(width, 1)
.build()
val levelLabel = Components.label()
.withSize(width, 1)
.build()

xpLabel.textProperty.updateFrom(createPropertyFrom("XP: ")
.concat(currentXPProperty))

levelLabel.textProperty.updateFrom(createPropertyFrom("Lvl: ")
.concat(currentLevelProperty))

addComponent(Components.textBox()
.withContentWidth(width)
.addHeader("Experience", false))
addComponent(xpLabel)
addComponent(levelLabel)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package org.hexworks.cavesofzircon.attributes

import org.hexworks.amethyst.api.Attribute

data class Vision(val radius: Int) : Attribute
data class Vision(var radius: Int) : Attribute
Original file line number Diff line number Diff line change
Expand Up @@ -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
name = "player"), Combatant, ItemHolder, EnergyUser, EquipmentHolder, ExperienceGainer

object Wall : BaseEntityType(
name = "wall")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.hexworks.cavesofzircon.attributes.types

import org.hexworks.amethyst.api.entity.EntityType
import org.hexworks.cavesofzircon.attributes.CombatStats
import org.hexworks.cavesofzircon.attributes.Experience
import org.hexworks.cavesofzircon.extensions.GameEntity

interface ExperienceGainer : EntityType

val GameEntity<ExperienceGainer>.experience: Experience
get() = findAttribute(Experience::class).get()

val GameEntity<ExperienceGainer>.combatStats: CombatStats
get() = findAttribute(CombatStats::class).get()

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.hexworks.cavesofzircon.attributes.EntityActions
import org.hexworks.cavesofzircon.attributes.EntityPosition
import org.hexworks.cavesofzircon.attributes.EntityTile
import org.hexworks.cavesofzircon.attributes.Equipment
import org.hexworks.cavesofzircon.attributes.Experience
import org.hexworks.cavesofzircon.attributes.FungusSpread
import org.hexworks.cavesofzircon.attributes.Inventory
import org.hexworks.cavesofzircon.attributes.ItemCombatStats
Expand Down Expand Up @@ -46,6 +47,7 @@ import org.hexworks.cavesofzircon.systems.Destructible
import org.hexworks.cavesofzircon.systems.DigestiveSystem
import org.hexworks.cavesofzircon.systems.Diggable
import org.hexworks.cavesofzircon.systems.EnergyExpender
import org.hexworks.cavesofzircon.systems.ExperienceAccumulator
import org.hexworks.cavesofzircon.systems.FungusGrowth
import org.hexworks.cavesofzircon.systems.HunterSeeker
import org.hexworks.cavesofzircon.systems.InputReceiver
Expand Down Expand Up @@ -91,6 +93,7 @@ object EntityFactory {

fun newPlayer() = newGameEntityOfType(Player) {
attributes(
Experience(),
Vision(9),
EntityPosition(),
BlockOccupier,
Expand All @@ -106,7 +109,7 @@ object EntityFactory {
initialWeapon = newClub(),
initialArmor = newJacket()))
behaviors(InputReceiver, EnergyExpender)
facets(Movable, CameraMover, StairClimber, StairDescender, Attackable, Destructible,
facets(Movable, CameraMover, StairClimber, StairDescender, Attackable, ExperienceAccumulator, Destructible,
ItemPicker, InventoryInspector, ItemDropper, EnergyExpender, DigestiveSystem)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.hexworks.cavesofzircon.commands

import org.hexworks.amethyst.api.entity.EntityType
import org.hexworks.cavesofzircon.extensions.GameCommand
import org.hexworks.cavesofzircon.extensions.GameEntity
import org.hexworks.cavesofzircon.world.GameContext

data class EntityDestroyed(override val context: GameContext,
override val source: GameEntity<EntityType>,
val destroyer: GameEntity<EntityType>) : GameCommand<EntityType>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.hexworks.cavesofzircon.events

import org.hexworks.cobalt.events.api.Event

object PlayerGainedLevel : Event
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import org.hexworks.amethyst.api.Consumed
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.extensions.GameCommand
import org.hexworks.cavesofzircon.functions.logGameEvent
import org.hexworks.cavesofzircon.world.GameContext

object Destructible : BaseFacet<GameContext>() {

override fun executeCommand(command: GameCommand<out EntityType>) = command.responseWhenCommandIs(Destroy::class) { (context, attacker, target, cause) ->
override fun executeCommand(command: GameCommand<out EntityType>) = command.responseWhenCommandIs(Destroy::class) { (context, destroyer, target, cause) ->
context.world.removeEntity(target)

destroyer.executeCommand(EntityDestroyed(context, target, destroyer))
logGameEvent("$target dies $cause.")
Consumed
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.hexworks.cavesofzircon.systems

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.CombatStats
import org.hexworks.cavesofzircon.attributes.types.ExperienceGainer
import org.hexworks.cavesofzircon.attributes.types.combatStats
import org.hexworks.cavesofzircon.attributes.types.experience
import org.hexworks.cavesofzircon.commands.EntityDestroyed
import org.hexworks.cavesofzircon.events.PlayerGainedLevel
import org.hexworks.cavesofzircon.extensions.GameCommand
import org.hexworks.cavesofzircon.extensions.attackValue
import org.hexworks.cavesofzircon.extensions.defenseValue
import org.hexworks.cavesofzircon.extensions.isPlayer
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
import kotlin.math.min

object ExperienceAccumulator : BaseFacet<GameContext>() {

override fun executeCommand(command: GameCommand<out EntityType>) = command.responseWhenCommandIs(EntityDestroyed::class) { (_, defender, attacker) ->
var response: Response = Pass
attacker.whenTypeIs<ExperienceGainer> { experienceGainer ->
response = Consumed
val xp = experienceGainer.experience
val stats = experienceGainer.combatStats
val defenderHp = defender.findAttribute(CombatStats::class).map { it.maxHp }.orElse(0)
val amount = (defenderHp + defender.attackValue + defender.defenseValue) - xp.currentLevel * 2
if (amount > 0) {
xp.currentXP += amount
while (xp.currentXP > Math.pow(xp.currentLevel.toDouble(), 1.5) * 20) {
xp.currentLevel++
logGameEvent("$attacker advanced to level ${xp.currentLevel}.")
stats.hpProperty.value = min(stats.hp + xp.currentLevel * 2, stats.maxHp)
if (attacker.isPlayer) {
Zircon.eventBus.publish(PlayerGainedLevel)
}
}
}

}
response
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/org/hexworks/cavesofzircon/view/PlayView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ 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.PlayerGainedLevel
import org.hexworks.cavesofzircon.view.dialog.LevelUpDialog
import org.hexworks.cavesofzircon.view.fragment.PlayerStatsFragment
import org.hexworks.cavesofzircon.world.Game
import org.hexworks.cavesofzircon.world.GameBuilder
Expand Down Expand Up @@ -55,6 +57,9 @@ class PlayView(private val game: Game = GameBuilder.defaultGame()) : BaseView()
withNewLine = false,
withTypingEffectSpeedInMs = 10)
}
Zircon.eventBus.subscribe<PlayerGainedLevel> {
screen.openModal(LevelUpDialog(screen, game.player))
}

val gameComponent = GameComponents.newGameComponentBuilder<Tile, GameBlock>()
.withGameArea(game.world)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.hexworks.cavesofzircon.view.dialog

import org.hexworks.zircon.api.Components
import org.hexworks.zircon.api.component.ComponentAlignment
import org.hexworks.zircon.api.component.Container
import org.hexworks.zircon.api.component.Fragment
import org.hexworks.zircon.api.component.modal.Modal
import org.hexworks.zircon.api.extensions.onComponentEvent
import org.hexworks.zircon.api.uievent.ComponentEventType
import org.hexworks.zircon.api.uievent.Processed
import org.hexworks.zircon.internal.component.modal.EmptyModalResult

class CloseButtonFragment(modal: Modal<EmptyModalResult>, parent: Container) : Fragment {

override val root = Components.button().withText("Close")
.withAlignmentWithin(parent, ComponentAlignment.BOTTOM_RIGHT)
.build().apply {
onComponentEvent(ComponentEventType.ACTIVATED) {
modal.close(EmptyModalResult)
Processed
}
}
}
28 changes: 28 additions & 0 deletions src/main/kotlin/org/hexworks/cavesofzircon/view/dialog/Dialog.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.hexworks.cavesofzircon.view.dialog

import org.hexworks.cavesofzircon.GameConfig
import org.hexworks.zircon.api.builder.component.ModalBuilder
import org.hexworks.zircon.api.component.Container
import org.hexworks.zircon.api.component.modal.Modal
import org.hexworks.zircon.api.component.modal.ModalFragment
import org.hexworks.zircon.api.screen.Screen
import org.hexworks.zircon.internal.component.modal.EmptyModalResult

abstract class Dialog(private val screen: Screen,
withClose: Boolean = true) : ModalFragment<EmptyModalResult> {

abstract val container: Container // 1

final override val root: Modal<EmptyModalResult> by lazy { // 2
ModalBuilder.newBuilder<EmptyModalResult>()
.withComponent(container)
.withParentSize(screen.size)
.withCenteredDialog(true) // 3
.build().also {
if (withClose) { // 4
container.addFragment(CloseButtonFragment(it, container))
}
container.applyColorTheme(GameConfig.THEME)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.hexworks.cavesofzircon.view.dialog

import org.hexworks.cavesofzircon.attributes.CombatStats
import org.hexworks.cavesofzircon.attributes.Vision
import org.hexworks.cavesofzircon.attributes.types.Player
import org.hexworks.cavesofzircon.extensions.GameEntity
import org.hexworks.cavesofzircon.extensions.tryToFindAttribute
import org.hexworks.cavesofzircon.functions.logGameEvent
import org.hexworks.zircon.api.Components
import org.hexworks.zircon.api.extensions.onComponentEvent
import org.hexworks.zircon.api.graphics.BoxType
import org.hexworks.zircon.api.screen.Screen
import org.hexworks.zircon.api.uievent.ComponentEventType
import org.hexworks.zircon.api.uievent.Processed
import org.hexworks.zircon.internal.component.modal.EmptyModalResult

class LevelUpDialog(screen: Screen, player: GameEntity<Player>) : Dialog(screen, false) {

override val container = Components.vbox()
.withTitle("Ding!")
.withSize(30, 15)
.withBoxType(BoxType.TOP_BOTTOM_DOUBLE)
.wrapWithBox()
.build().apply {
val stats = player.tryToFindAttribute(CombatStats::class)
val vision = player.tryToFindAttribute(Vision::class)

addComponent(Components.textBox()
.withContentWidth(27)
.addHeader("Congratulations, you leveled up!")
.addParagraph("Pick an improvement from the options below:"))

addComponent(Components.button()
.withText("Max HP")
.build().apply {
onComponentEvent(ComponentEventType.ACTIVATED) {
stats.maxHpProperty.value += 10
logGameEvent("You look healthier.")
root.close(EmptyModalResult)
Processed
}
})


addComponent(Components.button()
.withText("Attack")
.build().apply {
onComponentEvent(ComponentEventType.ACTIVATED) {
stats.attackValueProperty.value += 2
logGameEvent("You look stronger.")
root.close(EmptyModalResult)
Processed
}
})

addComponent(Components.button()
.withText("Defense")
.build().apply {
onComponentEvent(ComponentEventType.ACTIVATED) {
stats.defenseValueProperty.value += 2
logGameEvent("You look tougher.")
root.close(EmptyModalResult)
Processed
}
})

addComponent(Components.button()
.withText("Vision")
.build().apply {
onComponentEvent(ComponentEventType.ACTIVATED) {
vision.radius++
logGameEvent("You look more perceptive.")
root.close(EmptyModalResult)
Processed
}
})
}
}

0 comments on commit 8ad9f45

Please sign in to comment.