345 lines
12 KiB
Scala
345 lines
12 KiB
Scala
package fmon.stat
|
|
|
|
import scala.reflect.runtime.universe._
|
|
import scala.tools.reflect.ToolBox
|
|
|
|
import com.fasterxml.jackson.module.scala.JsonScalaEnumeration
|
|
|
|
import scala.io.Source
|
|
import scala.util.Random
|
|
|
|
import Statistic._
|
|
import fmon.battle.msg._
|
|
import fmon._
|
|
import fmon.battle.msg.{Message, SignalConsumer}
|
|
import fmon.util._
|
|
|
|
object MoveType extends Enumeration {
|
|
val Physical, Special, Support, Other = Value
|
|
}
|
|
class MoveTypeType extends TypeReference[MoveType.type]
|
|
|
|
object Target extends Enumeration {
|
|
val Normal, Self, Any, AllAdjacent, AllAdjacentFoes = Value
|
|
}
|
|
class TargetType extends TypeReference[Target.type]
|
|
|
|
trait MoveTurn extends Effect {
|
|
def name: String
|
|
def effectType = EffectType.MoveEffect
|
|
def flags: Set[String]
|
|
def prior: Int
|
|
def target: Target
|
|
def category: MoveType
|
|
def useMove(user: Monster, target: Monster)(implicit reader: SignalConsumer, rng: Random): Unit
|
|
}
|
|
|
|
class SwitchOut(val party: Party, val replacement: Monster) extends MoveTurn {
|
|
def name = "Swap"
|
|
def id = "swap"
|
|
def flags = Set()
|
|
def prior = +6
|
|
def target = Target.Self
|
|
def category = MoveType.Other
|
|
|
|
def useMove(user: Monster, target: Monster)(implicit reader: SignalConsumer, rng: Random): Unit = {
|
|
reader ! Message(s"${party.trainer} withdrew $user!")
|
|
party.switchIn(replacement)
|
|
reader ! Message(s"${party.trainer} sent out $replacement")
|
|
}
|
|
|
|
override def toString: String = s"Swap -> $replacement"
|
|
}
|
|
|
|
abstract class Move extends MoveTurn {
|
|
val name: String
|
|
val id: String
|
|
val desc: String
|
|
val category: MoveType
|
|
val pow: Int
|
|
val powCallback: (Monster, Move, Monster, SignalConsumer, Random) => Int
|
|
val damageCallback: (Monster, Move, Monster, SignalConsumer, Random) => Int
|
|
val onTryMove: (Monster, Move, Monster, SignalConsumer, Random) => Boolean
|
|
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: StatusTemplate
|
|
val drain: Fraction
|
|
val effect: Secondary
|
|
val selfEffect: Secondary
|
|
val secondary: Secondary
|
|
// boosts
|
|
// onHit
|
|
// onTryHit
|
|
|
|
// zPower, zMoveEffect, zMoveBoost
|
|
|
|
override def useMove(user: Monster, target: Monster)(implicit reader: SignalConsumer, rng: Random) = {
|
|
reader ! Message(s"$user used $name.")
|
|
if (onTryMove(user, this, target, reader, rng)) {
|
|
if (attackRoll(user, target)) {
|
|
if (pow > 0 || powCallback != null || damageCallback != null) {
|
|
val dmg = damageRoll(user, target)
|
|
val actualDmg = target.takeDamage(dmg)
|
|
if (dmg == actualDmg) {
|
|
reader ! Message(s"$target takes $actualDmg damage!")
|
|
reader ! DamageMsg(dmg, target, element)
|
|
} else {
|
|
reader ! Message(s"$target takes $actualDmg (+${dmg - actualDmg} overkill) damage!")
|
|
reader ! DamageMsg(dmg, target, element)
|
|
}
|
|
if (drain > 0.frac) {
|
|
val healing = Math.max(drain * actualDmg, 1)
|
|
user.recoverDamage(healing)
|
|
reader ! Message(s"$user recovered $healing damage.")
|
|
} else if (drain < 0.frac) {
|
|
val recoil = Math.max(-drain * actualDmg, 1)
|
|
user.takeDamage(recoil)
|
|
reader ! Message(s"$user took $recoil damage from recoil!")
|
|
reader ! DamageMsg(actualDmg, target, Element("None"))
|
|
}
|
|
|
|
target.base.ability.onAfterDamage(user, this, target, actualDmg)
|
|
|
|
if (!user.isAlive) {
|
|
reader ! Message(s"$user fainted!")
|
|
}
|
|
if (!target.isAlive) {
|
|
reader ! Message(s"$target fainted!")
|
|
}
|
|
}
|
|
applyBoosts(target, boosts, user)
|
|
applyStatus(target, user, status)
|
|
if (effect != null) {
|
|
applyEffect(target, user, effect)
|
|
}
|
|
if (selfEffect != null) {
|
|
applyEffect(user, user, selfEffect)
|
|
}
|
|
if (secondary != null && rng.chance(secondary.chance.%%)) {
|
|
applyEffect(target, user, secondary)
|
|
}
|
|
|
|
// TODO : Multiparty
|
|
} else {
|
|
reader ! Message("Missed!")
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
def attackRoll(user: Monster, target: Monster)(implicit reader: SignalConsumer, 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 reader: SignalConsumer, rng: Random) = {
|
|
if (damageCallback != null) {
|
|
damageCallback(user, this, target, reader, rng)
|
|
} else {
|
|
val crit = critMultiplier(user, target)
|
|
println(crit)
|
|
val atkStat = if (category == MoveType.Physical) PAtk else MAtk
|
|
val defStat = if (category == MoveType.Physical) PDef else MDef
|
|
val atk = if (crit > 1.0) Math.max(user(atkStat), user.stats(atkStat)) else user(atkStat)
|
|
val defense = if (crit > 1.0) Math.min(target(defStat), target.stats(defStat)) else user(defStat)
|
|
println(target(defStat), target.stats(defStat), defense)
|
|
val actualPow = if (powCallback != null) powCallback(user, this, target, reader, rng) else pow
|
|
val baseDmg = (2 * user.level / 5 + 2) * actualPow * atk / (defense * 50) + 2
|
|
val effectiveness = target.effectiveness(element)
|
|
val multiplier = effectiveness * crit * stabMultiplier(user)
|
|
if (effectiveness > 1.0) {
|
|
reader ! Message("It's super effective!")
|
|
} else if (effectiveness < 1.0) {
|
|
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 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)) {
|
|
reader ! Message("A critical hit!")
|
|
Config.crit
|
|
} else {
|
|
1.0
|
|
}
|
|
}
|
|
|
|
def stabMultiplier(user: Monster) = {
|
|
if (user.elements.contains(element)) {
|
|
Config.stab
|
|
} else {
|
|
1.0
|
|
}
|
|
}
|
|
|
|
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 reader: SignalConsumer, rng: Random) {
|
|
if (target.isAlive && status != null) {
|
|
target.addStatus(status.build, EffectSource(user, this))
|
|
}
|
|
}
|
|
|
|
def applyBoosts(target: Monster, boosts: Map[Stat, Int], source: Monster)(implicit read: SignalConsumer, rng: Random) {
|
|
if (target.isAlive) {
|
|
boosts.foreach {
|
|
case (s, b) => {
|
|
target.applyBoost(s, b, EffectSource(source, this))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override def toString = {
|
|
name
|
|
}
|
|
}
|
|
|
|
case class MoveToken(
|
|
val name: String,
|
|
val id: String,
|
|
val shortDesc: String,
|
|
@JsonScalaEnumeration(classOf[MoveTypeType]) val category: MoveType,
|
|
val basePower: Option[Int],
|
|
val basePowerCallback: String,
|
|
val onTryMove: String,
|
|
val damage: String,
|
|
val priority: Int,
|
|
val accuracy: Option[Int],
|
|
val pp: Int,
|
|
val `type`: String,
|
|
val flags: Map[String, Int],
|
|
val status: String,
|
|
val self: SecondaryToken,
|
|
val secondary: SecondaryToken,
|
|
val volatileStatus: String,
|
|
val drain: Fraction,
|
|
val recoil: Fraction,
|
|
@JsonScalaEnumeration(classOf[TargetType]) val target: Target = Target.Normal,
|
|
val boosts: Map[String, Int] = Map(),
|
|
val critRatio: Int = 0) {
|
|
|
|
def instantiate() = {
|
|
val token = this
|
|
val effectToken = SecondaryToken(100, Map(), null, token.volatileStatus, null)
|
|
new Move {
|
|
val name = token.name
|
|
val id = token.id
|
|
val desc = token.shortDesc
|
|
val category = token.category
|
|
val pow = token.basePower.getOrElse(0)
|
|
val powCallback = Move.compilePowCallback(token.basePowerCallback)
|
|
val damageCallback = Move.compileDamageCallback(token.damage)
|
|
val onTryMove = Move.compileOnTryMove(token.onTryMove)
|
|
val prior = token.priority
|
|
val pp = token.pp
|
|
val element = Element(token.`type`)
|
|
val accuracy = token.accuracy.getOrElse(100)
|
|
val flags = token.flags.keySet
|
|
val target = token.target
|
|
val boosts = if (token.boosts != null) token.boosts.map { case (s, i) => (Statistic(s), i) } else Map()
|
|
val crit = token.critRatio
|
|
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.frac
|
|
val effect = effectToken.instantiate()
|
|
val selfEffect = if (token.self != null) token.self.instantiate() else null
|
|
val secondary = if (token.secondary != null) token.secondary.instantiate() else null
|
|
}
|
|
}
|
|
}
|
|
|
|
object Move {
|
|
private var moves = Map[String, Move]()
|
|
val tokens = YamlHelper.extractMap[MoveToken](Source.fromInputStream(Move.getClass.getResourceAsStream("data/moves.yaml")))
|
|
|
|
def apply(name: String): Move = {
|
|
if (!moves.contains(name)) {
|
|
moves = moves.updated(name, tokens(name).instantiate())
|
|
}
|
|
moves(name)
|
|
}
|
|
|
|
private val helpers = """
|
|
implicit val dice = rng
|
|
implicit val consumer = reader
|
|
def msg(text: String): Unit = {
|
|
reader ! Message(text)
|
|
}
|
|
"""
|
|
|
|
def compilePowCallback(code: String): (Monster, Move, Monster, SignalConsumer, Random) => Int = {
|
|
if (code != null) {
|
|
val tb = runtimeMirror(getClass.getClassLoader).mkToolBox()
|
|
val tree = tb.parse(
|
|
s"""
|
|
|import scala.util.Random
|
|
|import fmon._
|
|
|import fmon.battle.msg._
|
|
|import fmon.stat._
|
|
|import fmon.stat.Statistic._
|
|
|def callback(user : Monster, move: Move, target : Monster, reader: SignalConsumer, rng: Random): Int = {
|
|
| $code
|
|
|}
|
|
|callback _
|
|
""".stripMargin)
|
|
val f = tb.compile(tree)
|
|
val wrapper = f()
|
|
|
|
wrapper.asInstanceOf[(Monster, Move, Monster, SignalConsumer, Random) => Int]
|
|
} else {
|
|
null
|
|
}
|
|
|
|
}
|
|
|
|
def compileDamageCallback(code: String): (Monster, Move, Monster, SignalConsumer, Random) => Int = {
|
|
compilePowCallback(code)
|
|
}
|
|
|
|
def compileOnTryMove(code: String): (Monster, Move, Monster, SignalConsumer, Random) => Boolean = {
|
|
if (code == null) {
|
|
(_, _, _, _, _) => true
|
|
} else {
|
|
val tb = runtimeMirror(getClass.getClassLoader).mkToolBox()
|
|
val tree = tb.parse(
|
|
s"""
|
|
|import scala.util.Random
|
|
|import fmon._
|
|
|import fmon.battle.msg._
|
|
|import fmon.stat._
|
|
|import fmon.stat.Statistic._
|
|
|def callback(user : Monster, move: Move, target : Monster, reader: SignalConsumer, rng: Random): Boolean = {
|
|
| $helpers
|
|
| $code
|
|
|}
|
|
|callback _
|
|
""".stripMargin)
|
|
val f = tb.compile(tree)
|
|
val wrapper = f()
|
|
|
|
wrapper.asInstanceOf[(Monster, Move, Monster, SignalConsumer, Random) => Boolean]
|
|
}
|
|
}
|
|
} |