fakemon/FakeMon/src/fmon/stat/Move.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]
}
}
}