Moved move activation from the game engine to the move itself and added the ability to switch out

This commit is contained in:
James Daly 2019-05-29 22:20:47 -04:00
parent 8ff9bdd938
commit 32c6913187
7 changed files with 237 additions and 178 deletions

View File

@ -23,8 +23,8 @@ object Game {
val movepool2 = IndexedSeq(Move("Absorb")) val movepool2 = IndexedSeq(Move("Absorb"))
val p1 = TrainerID("Jaeda", Gender.Female, 0) val p1 = TrainerID("Jaeda", Gender.Female, 0)
val p2 = TrainerID("Wild Monster", Gender.Male, 0) val p2 = TrainerID("Wild Monster", Gender.Male, 0)
val party1 = new Party(p1, new Monster(new StorageMon("Allied Mon", Gene.randomGene(null, form1), form1, Statistic.emptyEvs, movepool1)), IndexedSeq()) val party1 = new Party(p1, new MonsterPtr(new Monster(new StorageMon("Allied Mon", Gene.randomGene(null, form1), form1, Statistic.emptyEvs, movepool1))), IndexedSeq())
val party2 = new Party(p2, new Monster(new StorageMon("Wild Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2)), IndexedSeq( val party2 = new Party(p2, new MonsterPtr(new Monster(new StorageMon("Wild Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2))), IndexedSeq(
new Monster(new StorageMon("Sideboard Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2)) new Monster(new StorageMon("Sideboard Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2))
)) ))
val engine = new BattleEngine(party1, party2) val engine = new BattleEngine(party1, party2)

View File

@ -3,7 +3,7 @@ package mon.battle
import mon.stat._ import mon.stat._
import mon.stat.Statistic.Speed import mon.stat.Statistic.Speed
case class Action(user : Monster, move : Move, target : Monster) extends Comparable[Action] { case class Action(user : MonsterPtr, move : MoveTurn, target : MonsterPtr) extends Comparable[Action] {
override def compareTo(other : Action) = { override def compareTo(other : Action) = {
if (move.prior == other.move.prior) { if (move.prior == other.move.prior) {
other.user(Speed) compareTo user(Speed) other.user(Speed) compareTo user(Speed)

View File

@ -15,11 +15,11 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
} }
def playTurn() = { def playTurn() = {
val playerMove = player.pollAction val playerAction = player.pollAction(enemy)
val playerTarget = player.pollTarget(playerMove, player.lead, enemy) val enemyAction = enemy.pollAction(player)
val enemyMove = enemy.pollAction println(enemyAction)
val enemyTarget = enemy.pollTarget(enemyMove, enemy.lead, player)
val actions = Seq(Action(player.lead, playerMove, playerTarget), Action(enemy.lead, enemyMove, enemyTarget)) val actions = Seq(playerAction, enemyAction)
val queue = rng.shuffle(actions).sorted // Shuffle to randomize in the event of a tie val queue = rng.shuffle(actions).sorted // Shuffle to randomize in the event of a tie
queue.foreach(useMove) queue.foreach(useMove)
val eotQueue = Seq(player.lead, enemy.lead).sortBy(_(Speed)) val eotQueue = Seq(player.lead, enemy.lead).sortBy(_(Speed))
@ -28,7 +28,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
if (!player.lead.isAlive) { if (!player.lead.isAlive) {
if (player.canFight) { if (player.canFight) {
val replace = player.pollReplacement(enemy) val replace = player.pollReplacement(enemy)
player.swapIn(replace) player.switchIn(replace)
} else { } else {
println(s"${player.trainer} has lost!") println(s"${player.trainer} has lost!")
} }
@ -36,7 +36,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
if (!enemy.lead.isAlive) { if (!enemy.lead.isAlive) {
if (enemy.canFight) { if (enemy.canFight) {
val replace = enemy.pollReplacement(player) val replace = enemy.pollReplacement(player)
enemy.swapIn(replace) enemy.switchIn(replace)
} else { } else {
println(s"${enemy.trainer} has lost!") println(s"${enemy.trainer} has lost!")
} }
@ -51,117 +51,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
val move = action.move val move = action.move
val target = action.target val target = action.target
if (user.isAlive) { if (user.isAlive) {
println(s"$user used $move.") move.useMove(user, target)
if (attackRoll(user, move, target)) {
if (move.pow > 0 || move.powCallback != null) {
val dmg = damageRoll(user, move, target)
val actualDmg = target.takeDamage(dmg)
if (dmg == actualDmg) {
println(s"$target takes $actualDmg damage!")
} else {
println(s"$target takes $actualDmg (+${dmg - actualDmg} overkill) damage!")
}
if (move.drain > 0.frac) {
val healing = Math.max(move.drain * actualDmg, 1)
user.recoverDamage(healing)
println(s"$user recovered $healing damage.")
} else if (move.drain < 0.frac) {
val recoil = Math.max(-move.drain * actualDmg, 1)
user.takeDamage(recoil)
println(s"$user took $recoil damage from recoil!")
}
if (!user.isAlive) {
println(s"$user fainted!")
}
if (!target.isAlive) {
println(s"$target fainted!")
}
}
applyBoosts(target, move.boosts)
applyStatus(target, move.status)
if (move.selfEffect != null) {
applyEffect(user, move.selfEffect)
}
if (move.secondary != null && rng.chance(move.secondary.chance.%%)) {
applyEffect(target, move.secondary)
}
// TODO : Support moves
// TODO : Multiparty
} else {
println("Missed!")
}
}
}
def attackRoll(user: Monster, move: Move, target: Monster) = {
if (move.accuracy == 0) {
true
} else {
val acc = user.boosts.getOrElse(Accuracy, 0) - target.boosts.getOrElse(Evasion, 0)
val mod = if (acc > 0) Fraction(3 + acc, 3) else Fraction(3, 3 - acc)
val chance = move.accuracy.%% * mod
rng.chance(chance)
}
}
def damageRoll(user: Monster, move: Move, target: Monster) = {
val atkStat = if (move.mvType == MoveType.Physical) PAtk else MAtk
val defStat = if (move.mvType == MoveType.Physical) PDef else MDef
// TODO : Fixed damage
val pow = if (move.powCallback != null) move.powCallback(user, target) else move.pow
val baseDmg = (2 * user.level / 5 + 2) * pow * user(atkStat) / (target(defStat) * 50) + 2
val effectiveness = target.effectiveness(move.element)
val multiplier = effectiveness * critMultiplier(user, move, target)
if (effectiveness > 1.0) {
println("It's super effective!")
} else if (effectiveness < 1.0) {
println("It's not very effective.")
}
val maxDmg = (baseDmg * multiplier).toInt
val minDmg = (17 \ 20) * maxDmg
rng.nextInt(minDmg, maxDmg)
}
def critMultiplier(user : Monster, move : Move, target: Monster) = {
// Percentage chance is different from Pokemon
val stage = move.crit
val chance = (1 << stage) \ 16 // Doubles per stage
if (rng.chance(chance)) {
println("A critical hit!")
1.5
} else {
1.0
}
}
def applyEffect(target: Monster, effect: Secondary) {
applyBoosts(target, effect.boosts)
applyStatus(target, effect.status)
}
def applyStatus(target: Monster, status: Status) {
if (target.isAlive) {
if (status != null && target.status == None) {
// apply status
target.status = Some(status)
status.onStart.map(_(target))
}
}
}
def applyBoosts(target: Monster, boosts: Map[Stat, Int]) {
if (target.isAlive) {
boosts.foreach {
case (s, b) => {
target.applyBoost(s, b)
if (b > 0) {
println(s"$target's $s rose!")
} else if (b < 0) {
println(s"$target's $s fell!")
}
}
}
} }
} }
} }

View File

@ -0,0 +1,6 @@
package mon.stat
class MonsterPtr(var mon : Monster) {
override def toString : String = mon.toString()
}

View File

@ -6,7 +6,9 @@ import scala.tools.reflect.ToolBox
import com.fasterxml.jackson.module.scala.JsonScalaEnumeration import com.fasterxml.jackson.module.scala.JsonScalaEnumeration
import scala.io.Source import scala.io.Source
import scala.util.Random
import Statistic._
import mon.util._ import mon.util._
object MoveType extends Enumeration { object MoveType extends Enumeration {
@ -19,55 +21,190 @@ object Target extends Enumeration {
} }
class TargetType extends TypeReference[Target.type] class TargetType extends TypeReference[Target.type]
abstract class Move { trait MoveTurn {
val name : String def name: String
val desc : String def prior: Int
val mvType : MoveType def target: Target
val pow : Int def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit
val powCallback : (Monster, Monster) => Int }
val prior : Int
val accuracy : Int class SwitchOut(val party: Party, val replacement: Monster) extends MoveTurn {
val pp : Int def name = "Swap"
val element : Element def prior = +6
val flags : Set[String] def target = Target.Self
val target : Target
val boosts : Map[Stat, Int] def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit = {
val crit : Int println(s"${party.trainer} withdrew $user!")
val status : Status party.switchIn(replacement)
val drain : Fraction println(s"${party.trainer} sent out $replacement")
val selfEffect : Secondary }
override def toString: String = s"Swap -> $replacement"
}
abstract class Move extends MoveTurn {
val name: String
val desc: String
val mvType: MoveType
val pow: Int
val powCallback: (Monster, Monster) => Int
val prior: Int
val accuracy: Int
val pp: Int
val element: Element
val flags: Set[String]
val target: Target
val boosts: Map[Stat, Int]
val crit: Int
val status: Status
val drain: Fraction
val selfEffect: Secondary
val secondary: Secondary val secondary: Secondary
// boosts // boosts
// onHit // onHit
// onTryHit // onTryHit
// zPower, zMoveEffect, zMoveBoost // zPower, zMoveEffect, zMoveBoost
override def useMove(user: Monster, target: Monster)(implicit rng: Random) = {
println(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!")
} else {
println(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.")
} else if (drain < 0.frac) {
val recoil = Math.max(-drain * actualDmg, 1)
user.takeDamage(recoil)
println(s"$user took $recoil damage from recoil!")
}
if (!user.isAlive) {
println(s"$user fainted!")
}
if (!target.isAlive) {
println(s"$target fainted!")
}
}
applyBoosts(target, boosts)
applyStatus(target, status)
if (selfEffect != null) {
applyEffect(user, selfEffect)
}
if (secondary != null && rng.chance(secondary.chance.%%)) {
applyEffect(target, secondary)
}
// TODO : Support moves
// TODO : Multiparty
} else {
println("Missed!")
}
}
def attackRoll(user: Monster, target: Monster)(implicit rng: Random) = {
if (accuracy == 0) {
true
} else {
val acc = user.boosts.getOrElse(Accuracy, 0) - target.boosts.getOrElse(Evasion, 0)
val mod = if (acc > 0) Fraction(3 + acc, 3) else Fraction(3, 3 - acc)
val chance = accuracy.%% * mod
rng.chance(chance)
}
}
def damageRoll(user: Monster, target: Monster)(implicit rng: Random) = {
val atkStat = if (mvType == MoveType.Physical) PAtk else MAtk
val defStat = if (mvType == MoveType.Physical) PDef else MDef
// TODO : Fixed damage
val actualPow = if (powCallback != null) powCallback(user, target) else pow
val baseDmg = (2 * user.level / 5 + 2) * actualPow * user(atkStat) / (target(defStat) * 50) + 2
val effectiveness = target.effectiveness(element)
val multiplier = effectiveness * critMultiplier(user, target)
if (effectiveness > 1.0) {
println("It's super effective!")
} else if (effectiveness < 1.0) {
println("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) = {
// 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!")
1.5
} else {
1.0
}
}
def applyEffect(target: Monster, effect: Secondary) {
applyBoosts(target, effect.boosts)
applyStatus(target, effect.status)
}
def applyStatus(target: Monster, status: Status) {
if (target.isAlive) {
if (status != null && target.status == None) {
// apply status
target.status = Some(status)
status.onStart.map(_(target))
}
}
}
def applyBoosts(target: Monster, boosts: Map[Stat, Int]) {
if (target.isAlive) {
boosts.foreach {
case (s, b) => {
target.applyBoost(s, b)
if (b > 0) {
println(s"$target's $s rose!")
} else if (b < 0) {
println(s"$target's $s fell!")
}
}
}
}
}
override def toString = { override def toString = {
name name
} }
} }
case class MoveToken( case class MoveToken(
val name : String, val name: String,
val shortDesc : String, val shortDesc: String,
@JsonScalaEnumeration(classOf[MoveTypeType]) val category : MoveType, @JsonScalaEnumeration(classOf[MoveTypeType]) val category: MoveType,
val basePower : Option[Int], val basePower: Option[Int],
val basePowerCallback : String, val basePowerCallback: String,
val priority : Int, val priority: Int,
val accuracy : Option[Int], val accuracy: Option[Int],
val pp : Int, val pp: Int,
val `type` : String, val `type`: String,
val flags : Map[String, Int], val flags: Map[String, Int],
val status : String, val status: String,
val self : SecondaryToken, val self: SecondaryToken,
val secondary : SecondaryToken, val secondary: SecondaryToken,
val drain : Fraction, val drain: Fraction,
val recoil : Fraction, val recoil: Fraction,
@JsonScalaEnumeration(classOf[TargetType]) val target : Target = Target.Normal, @JsonScalaEnumeration(classOf[TargetType]) val target: Target = Target.Normal,
val boosts : Map[String, Int] = Map(), val boosts: Map[String, Int] = Map(),
val critRatio : Int = 0) { val critRatio: Int = 0) {
def instantiate() = { def instantiate() = {
val token = this val token = this
new Move { new Move {
@ -82,7 +219,7 @@ case class MoveToken(
val accuracy = token.accuracy.getOrElse(100) val accuracy = token.accuracy.getOrElse(100)
val flags = token.flags.keySet val flags = token.flags.keySet
val target = token.target val target = token.target
val boosts = if (token.boosts != null) token.boosts.map{case (s, i) => (Statistic(s), i)} else Map() val boosts = if (token.boosts != null) token.boosts.map { case (s, i) => (Statistic(s), i) } else Map()
val crit = token.critRatio val crit = token.critRatio
val status = if (token.status != null) Status(token.status) else null val status = if (token.status != null) Status(token.status) else null
val drain = if (token.drain != null) token.drain else if (token.recoil != null) -token.recoil else 0 \ 1 val drain = if (token.drain != null) token.drain else if (token.recoil != null) -token.recoil else 0 \ 1
@ -95,14 +232,14 @@ case class MoveToken(
object Move { object Move {
private var moves = Map[String, Move]() private var moves = Map[String, Move]()
val tokens = YamlHelper.extractSeq[MoveToken](Source.fromInputStream(Move.getClass.getResourceAsStream("data/moves.yaml"))).map(t => (t.name, t)).toMap val tokens = YamlHelper.extractSeq[MoveToken](Source.fromInputStream(Move.getClass.getResourceAsStream("data/moves.yaml"))).map(t => (t.name, t)).toMap
def apply(name : String) : Move = { def apply(name: String): Move = {
if (!moves.contains(name)) { if (!moves.contains(name)) {
moves = moves.updated(name, tokens(name).instantiate()) moves = moves.updated(name, tokens(name).instantiate())
} }
moves(name) moves(name)
} }
def compilePowCallback(code: String): (Monster, Monster) => Int = { def compilePowCallback(code: String): (Monster, Monster) => Int = {
if (code != null) { if (code != null) {
val tb = runtimeMirror(getClass.getClassLoader).mkToolBox() val tb = runtimeMirror(getClass.getClassLoader).mkToolBox()
@ -117,11 +254,11 @@ object Move {
""".stripMargin) """.stripMargin)
val f = tb.compile(tree) val f = tb.compile(tree)
val wrapper = f() val wrapper = f()
wrapper.asInstanceOf[(Monster, Monster) => Int] wrapper.asInstanceOf[(Monster, Monster) => Int]
} else { } else {
null null
} }
} }
} }

View File

@ -2,30 +2,50 @@ package mon.stat
import scala.util.Random import scala.util.Random
import mon.battle.rngDice import mon.battle.Action
import mon.stat.Target._ import mon.stat.Target._
class Party(val trainer : TrainerID, var lead : Monster, var sideboard : IndexedSeq[Monster]) { class Party(val trainer: TrainerID, val lead: MonsterPtr, var sideboard: IndexedSeq[Monster]) {
def pollAction(implicit rng : Random) : Move = rng.pick(lead.base.moves) def pollAction(them: Party)(implicit rng: Random): Action = {
def pollTarget(move : Move, user : Monster, them: Party)(implicit rng : Random) : Monster = { if (hasBackup && shouldSwitch(them)) {
val sub = pollReplacement(them: Party)
val move = new SwitchOut(this, sub)
Action(lead, move, lead)
} else {
val move = pollMove
val target = pollTarget(move, lead, them)
Action(lead, move, target)
}
}
def pollMove(implicit rng: Random): MoveTurn = rng.pick(lead.base.moves)
def pollTarget(move: MoveTurn, user: MonsterPtr, them: Party)(implicit rng: Random): MonsterPtr = {
if (move.target == Self) { if (move.target == Self) {
user user
} else { } else {
them.lead them.lead
} }
} }
def pollReplacement(them : Party)(implicit rng : Random) : Monster = { def pollReplacement(them: Party)(implicit rng: Random): Monster = {
sideboard.filter(_.isAlive).head sideboard.filter(_.isAlive).head
} }
def swapIn(mon : Monster) = { def switchIn(mon: Monster) = {
val index = sideboard.indexOf(mon) val index = sideboard.indexOf(mon)
sideboard = sideboard.updated(index, lead) sideboard = sideboard.updated(index, lead.mon)
lead = mon lead.mon = mon
} }
def canFight : Boolean = { def shouldSwitch(them: Party)(implicit rng: Random): Boolean = {
lead.isAlive || sideboard.exists(_.isAlive) true
}
def canFight: Boolean = {
lead.isAlive || hasBackup
}
def hasBackup: Boolean = {
sideboard.exists(_.isAlive)
} }
} }

View File

@ -3,6 +3,10 @@ package mon
import org.json4s.DefaultFormats import org.json4s.DefaultFormats
import org.json4s.ext.EnumNameSerializer import org.json4s.ext.EnumNameSerializer
import scala.util.Random
import mon.util.Dice
package object stat { package object stat {
type Stat = Statistic.Value type Stat = Statistic.Value
type MoveType = MoveType.Value type MoveType = MoveType.Value
@ -10,4 +14,6 @@ package object stat {
type Target = Target.Value type Target = Target.Value
implicit val formats = DefaultFormats + new EnumNameSerializer(MoveType) + new EnumNameSerializer(Gender) implicit val formats = DefaultFormats + new EnumNameSerializer(MoveType) + new EnumNameSerializer(Gender)
implicit def rngDice(rng : Random) = new Dice(rng)
implicit def ptrToMonster(ptr : MonsterPtr) : Monster = ptr.mon
} }