Renamed the root package to fmon and added paralysis, burn, and sleep status effects
This commit is contained in:
268
FakeMon/src/fmon/stat/Move.scala
Normal file
268
FakeMon/src/fmon/stat/Move.scala
Normal file
@@ -0,0 +1,268 @@
|
||||
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.util._
|
||||
|
||||
object MoveType extends Enumeration {
|
||||
val Physical, Special, Status = Value
|
||||
}
|
||||
class MoveTypeType extends TypeReference[MoveType.type]
|
||||
|
||||
object Target extends Enumeration {
|
||||
val Normal, Self, AllAdjacentFoes = Value
|
||||
}
|
||||
class TargetType extends TypeReference[Target.type]
|
||||
|
||||
trait MoveTurn {
|
||||
def name: String
|
||||
def flags: Set[String]
|
||||
def prior: Int
|
||||
def target: Target
|
||||
def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit
|
||||
}
|
||||
|
||||
class SwitchOut(val party: Party, val replacement: Monster) extends MoveTurn {
|
||||
def name = "Swap"
|
||||
def flags = Set()
|
||||
def prior = +6
|
||||
def target = Target.Self
|
||||
|
||||
def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit = {
|
||||
println(s"${party.trainer} withdrew $user!")
|
||||
party.switchIn(replacement)
|
||||
println(s"${party.trainer} sent out $replacement")
|
||||
}
|
||||
|
||||
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
|
||||
// boosts
|
||||
// onHit
|
||||
// onTryHit
|
||||
|
||||
// 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 && status != null) {
|
||||
if (target.status == None) {
|
||||
// apply status
|
||||
target.status = Some(status)
|
||||
status.onStart(target)
|
||||
} else {
|
||||
println("But it failed!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
case class MoveToken(
|
||||
val name: String,
|
||||
val shortDesc: String,
|
||||
@JsonScalaEnumeration(classOf[MoveTypeType]) val category: MoveType,
|
||||
val basePower: Option[Int],
|
||||
val basePowerCallback: 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 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
|
||||
new Move {
|
||||
val name = token.name
|
||||
val desc = token.shortDesc
|
||||
val mvType = category
|
||||
val pow = token.basePower.getOrElse(0)
|
||||
val powCallback = Move.compilePowCallback(token.basePowerCallback)
|
||||
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 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.extractSeq[MoveToken](Source.fromInputStream(Move.getClass.getResourceAsStream("data/moves.yaml"))).map(t => (t.name, t)).toMap
|
||||
|
||||
def apply(name: String): Move = {
|
||||
if (!moves.contains(name)) {
|
||||
moves = moves.updated(name, tokens(name).instantiate())
|
||||
}
|
||||
moves(name)
|
||||
}
|
||||
|
||||
def compilePowCallback(code: String): (Monster, Monster) => Int = {
|
||||
if (code != null) {
|
||||
val tb = runtimeMirror(getClass.getClassLoader).mkToolBox()
|
||||
val tree = tb.parse(
|
||||
s"""
|
||||
|import mon.stat.Monster
|
||||
|import mon.stat.Statistic._
|
||||
|def callback(user : Monster, target : Monster): Int = {
|
||||
| $code
|
||||
|}
|
||||
|callback _
|
||||
""".stripMargin)
|
||||
val f = tb.compile(tree)
|
||||
val wrapper = f()
|
||||
|
||||
wrapper.asInstanceOf[(Monster, Monster) => Int]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user