Connected the battle engine to the UI. Beginning to make messages appear in the UI

This commit is contained in:
James Daly 2019-06-13 22:22:57 -04:00
parent 2805bbf1c7
commit 0b52e91a3d
11 changed files with 109 additions and 61 deletions

View File

@ -40,7 +40,7 @@ class Game extends Application {
val url = getClass.getResource("battle/battle.fxml")
val loader = new FXMLLoader(url)
val root: Parent = loader.load()
val controller = loader.getController[BattleUI]()
implicit val controller = loader.getController[BattleUI]()
val scene: Scene = new Scene(root)
implicit val rng = new scala.util.Random()

View File

@ -2,11 +2,12 @@ package fmon.battle
import scala.util.Random
import fmon.battle.msg._
import fmon.stat._
import fmon.stat.Statistic._
import fmon.util.Fraction
class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random) {
class BattleEngine(val player: Party, val enemy: Party)(implicit val reader: SignalConsumer, val rng: Random) extends SignalConsumer {
def play() = {
println(player.lead.level)
@ -15,8 +16,6 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
}
}
def playTurn(): Unit = {
val playerAction = player.pollAction(enemy)
playTurn(playerAction)
@ -36,7 +35,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
val replace = player.pollReplacement(enemy)
player.switchIn(replace)
} else {
println(s"${player.trainer} has lost!")
this ! Message(s"${player.trainer} has lost!")
}
}
if (!enemy.lead.isAlive) {
@ -47,9 +46,6 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
println(s"${enemy.trainer} has lost!")
}
}
println(s"${player.lead}(${player.lead.hp}/${player.lead(Hp)})")
println(s"${enemy.lead}(${enemy.lead.hp}/${enemy.lead(Hp)})")
}
def useMove(action: Action) = {
@ -62,4 +58,8 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
}
}
}
def !(msg: Signal) = {
reader ! msg
}
}

View File

@ -10,10 +10,11 @@ import scalafx.scene.Parent
import scalafx.scene.control._
import scalafx.scene.layout._
import fmon.battle.msg._
import fmon.stat._
import fmon.stat.Statistic._
class BattleUI {
class BattleUI extends SignalConsumer {
@FXML var usName: jfxsc.Label = _
@FXML var usLv: jfxsc.Label = _
@FXML var usHealth: jfxsc.Label = _
@ -23,32 +24,50 @@ class BattleUI {
@FXML var themHealth: jfxsc.Label = _
@FXML var themHealthBar: jfxsc.ProgressBar = _
@FXML var buttonPane: javafx.scene.layout.VBox = _
@FXML var messages: jfxsc.TextArea = _
private var engine: BattleEngine = _
def setEngine(engine: BattleEngine): Unit = {
this.engine = engine
updateUI()
}
def updateUI(): Unit = {
val player = engine.player.lead
usName.text = player.name
usLv.text = s"Lv. ${player.level}"
usHealth.text = s"${player.hp}/${player.maxhp}"
usHealthBar.progress = player.hp / player.maxhp
usHealthBar.progress = player.hp.toDouble / player.maxhp
val enemy = engine.enemy.lead
themName.text = enemy.name
themLv.text = s"Lv. ${enemy.level}"
themHealth.text = s"${enemy.hp}/${enemy.maxhp}"
themHealthBar.progress = enemy.hp / enemy.maxhp
themHealthBar.progress = enemy.hp.toDouble / enemy.maxhp
val buttons = player.base.moves.map(move => loadMoveButton(move))
buttonPane.children = buttons
}
def onMove(move: Move): Unit = {
val action = Action(engine.player.lead, move, engine.enemy.lead)
engine.playTurn(action)
updateUI()
}
def !(msg: Signal): Unit = {
msg match {
case Message(text) => messages.text = s"${messages.text()}${text}\n"
}
}
private def loadMoveButton(move: Move): Button = {
val fxmlLoader = new FXMLLoader(getClass.getResource("moveButton.fxml"))
fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory())
val button = fxmlLoader.load[jfxsc.Button]()
val controller = fxmlLoader.getController[MoveButton]()
controller.setup(move)
controller.setup(move, onMove)
button
}
}

View File

@ -13,15 +13,16 @@ class MoveButton {
@FXML var moveName: jfxsc.Label = _
@FXML var moveUses: jfxsc.Label = _
private var move: Move = _
private var callback: Move => Unit = _
def setup(mv: Move): Unit = {
def setup(mv: Move, callback: Move => Unit): Unit = {
move = mv
moveName.text = mv.name
moveUses.text = s"${move.pp}/${move.pp}"
this.callback = callback
}
def moveActivate(): Unit = {
println("We should take a turn here")
println(s"${moveBtn.width}")
callback(move)
}
}

View File

@ -7,6 +7,7 @@
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.SeparatorMenuItem?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
@ -132,11 +133,12 @@
</Label>
</children>
</GridPane>
<ScrollPane hbarPolicy="NEVER" layoutX="616.0" layoutY="344.0" prefHeight="200.0" prefWidth="200.0">
<ScrollPane hbarPolicy="NEVER" layoutX="32.0" layoutY="29.0" prefHeight="200.0" prefWidth="200.0">
<content>
<VBox fx:id="buttonPane" />
</content>
</ScrollPane>
<TextArea fx:id="messages" layoutX="440.0" layoutY="449.0" prefHeight="112.0" prefWidth="399.0" />
</children>
</AnchorPane>
</children>

View File

@ -0,0 +1,11 @@
package fmon.battle.msg
trait SignalConsumer {
def !(msg: Signal): Unit
}
trait Signal
case class Message(val text: String) extends Signal {
}

View File

@ -5,6 +5,7 @@ import scala.tools.reflect.ToolBox
import scala.io.Source
import scala.util.Random
import fmon.battle.msg.SignalConsumer
import fmon.util._
abstract class Ability extends Effect {
@ -13,9 +14,10 @@ abstract class Ability extends Effect {
def effectType = EffectType.AbilityEffect
def onAfterDamage(source: Monster, move: MoveTurn, mon: Monster, dmg: Int)(implicit rng: Random) = onAfterDamageImp(this, source, move, mon, dmg, rng)
def onAfterDamage(source: Monster, move: MoveTurn, mon: Monster, dmg: Int)(implicit reader: SignalConsumer, rng: Random) =
onAfterDamageImp(this, source, move, mon, dmg, reader, rng)
protected val onAfterDamageImp: (Ability, Monster, MoveTurn, Monster, Int, Random) => Unit
protected val onAfterDamageImp: (Ability, Monster, MoveTurn, Monster, Int, SignalConsumer, Random) => Unit
override def toString = name
}
@ -50,6 +52,7 @@ object Ability {
private val header = """
|import scala.util.Random
|import fmon.battle.msg._
|import fmon.stat._
|import fmon.stat.MoveType._
|import fmon.stat.Statistic._
@ -57,20 +60,21 @@ object Ability {
"""
private val helper = """
implicit val gen = rng
implicit val signalConsumer = reader
def trigger {
print(s"[${mon}'s ${self}]")
reader ! Message(s"[${mon}'s ${self}]")
}
def effect = EffectSource(mon, self)
"""
def compileOnAfterDamage(code: String): (Ability, Monster, MoveTurn, Monster, Int, Random) => Unit = {
def compileOnAfterDamage(code: String): (Ability, Monster, MoveTurn, Monster, Int, SignalConsumer, Random) => Unit = {
if (code == null) {
(_, _, _, _, _, _) => ()
(_, _, _, _, _, _, _) => ()
} else {
val tree = tb.parse(
s"""
|$header
|def onAfterDamage(self: Ability, source: Monster, move: MoveTurn, mon: Monster, damage: Int, rng: Random): Unit = {
|def onAfterDamage(self: Ability, source: Monster, move: MoveTurn, mon: Monster, damage: Int, reader: SignalConsumer, rng: Random): Unit = {
| $helper
| $code
|}
@ -79,7 +83,7 @@ object Ability {
val f = tb.compile(tree)
val wrapper = f()
implicit val c = code
wrapper.asInstanceOf[(Ability, Monster, MoveTurn, Monster, Int, Random) => Unit]
wrapper.asInstanceOf[(Ability, Monster, MoveTurn, Monster, Int, SignalConsumer, Random) => Unit]
}
}
}

View File

@ -2,6 +2,7 @@ package fmon.stat
import scala.util._
import fmon.battle.msg._
import fmon.util._
import Monster._
@ -45,7 +46,7 @@ class Monster(val base : StorageMon) {
status = None
}
def addStatus(s : Status, source: EffectSource)(implicit rng: Random) = {
def addStatus(s : Status, source: EffectSource)(implicit reader: SignalConsumer, rng: Random) = {
if (s.effectType == EffectType.Volatile && !volatile.exists(_.name == s.name)) {
volatile +:= s
s.onStart(this, source)

View File

@ -9,6 +9,7 @@ import scala.io.Source
import scala.util.Random
import Statistic._
import fmon.battle.msg.{Message, SignalConsumer}
import fmon.util._
object MoveType extends Enumeration {
@ -28,7 +29,7 @@ trait MoveTurn extends Effect {
def prior: Int
def target: Target
def category: MoveType
def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit
def useMove(user: Monster, target: Monster)(implicit reader: SignalConsumer, rng: Random): Unit
}
class SwitchOut(val party: Party, val replacement: Monster) extends MoveTurn {
@ -38,10 +39,10 @@ class SwitchOut(val party: Party, val replacement: Monster) extends MoveTurn {
def target = Target.Self
def category = MoveType.Other
def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit = {
println(s"${party.trainer} withdrew $user!")
def useMove(user: Monster, target: Monster)(implicit reader: SignalConsumer, rng: Random): Unit = {
reader ! Message(s"${party.trainer} withdrew $user!")
party.switchIn(replacement)
println(s"${party.trainer} sent out $replacement")
reader ! Message(s"${party.trainer} sent out $replacement")
}
override def toString: String = s"Swap -> $replacement"
@ -72,34 +73,34 @@ abstract class Move extends MoveTurn {
// zPower, zMoveEffect, zMoveBoost
override def useMove(user: Monster, target: Monster)(implicit rng: Random) = {
println(s"$user used $name.")
override def useMove(user: Monster, target: Monster)(implicit reader: SignalConsumer, rng: Random) = {
reader ! Message(s"$user used $name.")
if (attackRoll(user, target)) {
if (pow > 0 || powCallback != null) {
val dmg = damageRoll(user, target)
val actualDmg = target.takeDamage(dmg)
if (dmg == actualDmg) {
println(s"$target takes $actualDmg damage!")
reader ! Message(s"$target takes $actualDmg damage!")
} else {
println(s"$target takes $actualDmg (+${dmg - actualDmg} overkill) damage!")
reader ! Message(s"$target takes $actualDmg (+${dmg - actualDmg} overkill) damage!")
}
if (drain > 0.frac) {
val healing = Math.max(drain * actualDmg, 1)
user.recoverDamage(healing)
println(s"$user recovered $healing damage.")
reader ! Message(s"$user recovered $healing damage.")
} else if (drain < 0.frac) {
val recoil = Math.max(-drain * actualDmg, 1)
user.takeDamage(recoil)
println(s"$user took $recoil damage from recoil!")
reader ! Message(s"$user took $recoil damage from recoil!")
}
target.base.ability.onAfterDamage(user, this, target, actualDmg)
if (!user.isAlive) {
println(s"$user fainted!")
reader ! Message(s"$user fainted!")
}
if (!target.isAlive) {
println(s"$target fainted!")
reader ! Message(s"$target fainted!")
}
}
applyBoosts(target, boosts, user)
@ -116,11 +117,11 @@ abstract class Move extends MoveTurn {
// TODO : Multiparty
} else {
println("Missed!")
reader ! Message("Missed!")
}
}
def attackRoll(user: Monster, target: Monster)(implicit rng: Random) = {
def attackRoll(user: Monster, target: Monster)(implicit reader: SignalConsumer, rng: Random) = {
if (accuracy == 0) {
true
} else {
@ -131,7 +132,7 @@ abstract class Move extends MoveTurn {
}
}
def damageRoll(user: Monster, target: Monster)(implicit rng: Random) = {
def damageRoll(user: Monster, target: Monster)(implicit reader: SignalConsumer, rng: Random) = {
val atkStat = if (category == MoveType.Physical) PAtk else MAtk
val defStat = if (category == MoveType.Physical) PDef else MDef
// TODO : Fixed damage
@ -140,34 +141,34 @@ abstract class Move extends MoveTurn {
val effectiveness = target.effectiveness(element)
val multiplier = effectiveness * critMultiplier(user, target)
if (effectiveness > 1.0) {
println("It's super effective!")
reader ! Message("It's super effective!")
} else if (effectiveness < 1.0) {
println("It's not very effective.")
reader ! Message("It's not very effective.")
}
val maxDmg = (baseDmg * multiplier).toInt
val minDmg = (17 \\ 20) * maxDmg
rng.nextInt(minDmg, maxDmg)
}
def critMultiplier(user: Monster, target: Monster)(implicit rng: Random) = {
def critMultiplier(user: Monster, target: Monster)(implicit reader: SignalConsumer, rng: Random) = {
// Percentage chance is different from Pokemon
val stage = crit
val chance = (1 << stage) \\ 16 // Doubles per stage
if (rng.chance(chance)) {
println("A critical hit!")
reader ! Message("A critical hit!")
1.5
} else {
1.0
}
}
def applyEffect(target: Monster, source: Monster, effect: Secondary)(implicit rng: Random) {
def applyEffect(target: Monster, source: Monster, effect: Secondary)(implicit reader: SignalConsumer, rng: Random) {
applyBoosts(target, effect.boosts, source)
applyStatus(target, source, effect.status)
applyStatus(target, source, effect.volatile)
}
def applyStatus(target: Monster, user: Monster, status: StatusTemplate)(implicit rng: Random) {
def applyStatus(target: Monster, user: Monster, status: StatusTemplate)(implicit reader: SignalConsumer, rng: Random) {
if (target.isAlive && status != null) {
target.addStatus(status.build, EffectSource(user, this))
}

View File

@ -7,6 +7,7 @@ import scala.util.Random
import scala.io.Source
import fmon.battle.msg._
import fmon.util._
import EffectType.Volatile
@ -47,11 +48,11 @@ class Status(template: StatusTemplate) extends Effect {
def name = template.name
// val id
def effectType = template.effectType
def onStart(mon: Monster, source: EffectSource)(implicit rng: Random) = template.onStart(this, mon, source, rng)
def onStart(mon: Monster, source: EffectSource)(implicit reader: SignalConsumer, rng: Random) = template.onStart(this, mon, source, reader, rng)
def onEnd(mon: Monster) = template.onEnd(this, mon)
def onModifyStat(mon: Monster, stat: Stat) = template.onModifyStat(this, mon, stat)
// val onBeforeMovePriority : Int
def onBeforeMove(mon: Monster, move: MoveTurn, target: Monster)(implicit rng: Random) = template.onBeforeMove(this, mon, move, target, rng)
def onBeforeMove(mon: Monster, move: MoveTurn, target: Monster)(implicit reader: SignalConsumer, rng: Random) = template.onBeforeMove(this, mon, move, target, reader, rng)
// val onModifyMove
// val onHit
def onResidualOrder = template.onResidualOrder
@ -68,11 +69,11 @@ abstract class StatusTemplate {
val name: String
// val id
val effectType: EffectType
val onStart: (Status, Monster, EffectSource, Random) => Unit
val onStart: (Status, Monster, EffectSource, SignalConsumer, Random) => Unit
val onEnd: (Status, Monster) => Unit
val onModifyStat: (Status, Monster, Stat) => Fraction
// val onBeforeMovePriority : Int
val onBeforeMove: (Status, Monster, MoveTurn, Monster, Random) => Boolean
val onBeforeMove: (Status, Monster, MoveTurn, Monster, SignalConsumer, Random) => Boolean
// val onModifyMove
// val onHit
val onResidualOrder: Int
@ -122,27 +123,34 @@ object Status {
private val tb = runtimeMirror(getClass.getClassLoader).mkToolBox()
private val header = """
import scala.util.Random
import fmon.battle.msg._
import fmon.stat._
import fmon.stat.MoveType._
import fmon.stat.Statistic._
import fmon.util._
"""
private val helpers = """
def msg(text: String): Unit = {
reader ! Message(text)
}
"""
def compileOnStart(code: String): (Status, Monster, EffectSource, Random ) => Unit = {
def compileOnStart(code: String): (Status, Monster, EffectSource, SignalConsumer, Random ) => Unit = {
if (code == null) {
(_, _, _, _) => ()
(_, _, _, _, _) => ()
} else {
val tree = tb.parse(
s"""
|$header
|def onStart(self:Status, mon: Monster, source: EffectSource, rng: Random) = {
|def onStart(self:Status, mon: Monster, source: EffectSource, reader: SignalConsumer, rng: Random) = {
| $helpers
| $code
|}
|onStart _
""".stripMargin)
val f = tb.compile(tree)
val wrapper = f()
wrapper.asInstanceOf[(Status, Monster, EffectSource, Random) => Unit]
wrapper.asInstanceOf[(Status, Monster, EffectSource, SignalConsumer, Random) => Unit]
}
}
@ -164,14 +172,15 @@ object Status {
}
}
def compileOnBeforeMove(code: String): (Status, Monster, MoveTurn, Monster, Random) => Boolean = {
def compileOnBeforeMove(code: String): (Status, Monster, MoveTurn, Monster, SignalConsumer, Random) => Boolean = {
if (code == null) {
(_, _, _, _, _) => true
(_, _, _, _, _, _) => true
} else {
val tree = tb.parse(
s"""
$header
def onBeforeMove(self: Status, mon: Monster, move: MoveTurn, target: Monster, rng: Random): Boolean = {
def onBeforeMove(self: Status, mon: Monster, move: MoveTurn, target: Monster, reader: SignalConsumer, rng: Random): Boolean = {
$helpers
$code
}
onBeforeMove _
@ -179,7 +188,7 @@ object Status {
val f = tb.compile(tree)
val wrapper = f()
wrapper.asInstanceOf[(Status, Monster, MoveTurn, Monster, Random) => Boolean]
wrapper.asInstanceOf[(Status, Monster, MoveTurn, Monster, SignalConsumer, Random) => Boolean]
}
}

View File

@ -13,7 +13,7 @@ brn:
if (!source.isMove) {
print(s"[${source.mon}'s ${source.effect}]")
}
println(s"${mon} was burned!")
msg(s"${mon} was burned!")
/*
if (sourceEffect && sourceEffect.id === 'flameorb') {
target.status = Status('brn', '[from] 'item': Flame Orb');
@ -45,7 +45,7 @@ confusion:
val maxDmg = (2 * mon.level / 5 + 2) * 40 * mon(PAtk) / (mon(PDef) * 50) + 2
val minDmg = (17 \\ 20) * maxDmg
val dmg = rng.nextInt(minDmg, maxDmg)
println(s"${mon} hurt itself in its confusion.")
msg(s"${mon} hurt itself in its confusion.")
mon.takeDamage(dmg)
false
} else {
@ -65,7 +65,7 @@ confusion:
onBeforeMovePriority: 3
onEnd: println(s"${mon} snapped out of its confusion.")
onStart: |-
println(s"${mon} was confused!")
msg(s"${mon} was confused!")
/*
if (sourceEffect && sourceEffect.id === 'lockedmove') {
this.add('-start', target, 'confusion', '[fatigue]');
@ -102,7 +102,7 @@ par:
onBeforeMovePriority: 1
onBeforeMove: |
if (rng.chance(1, 4)) {
println(s"${mon} is fully paralyzed!")
msg(s"${mon} is fully paralyzed!")
false
} else {
true