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] } } }