Renamed the root package to fmon and added paralysis, burn, and sleep status effects

This commit is contained in:
James Daly
2019-06-02 17:40:40 -04:00
parent d13d5b3bf6
commit 40553d36b9
31 changed files with 378 additions and 195 deletions

View File

@@ -0,0 +1,35 @@
package fmon
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
import fmon.battle.BattleEngine
import fmon.stat._
case class Prop(url : Seq[String])
object Game {
def main(args : Array[String]): Unit = {
println(Element("Water").effect)
println(Status("psn"))
implicit val rng = new scala.util.Random()
val form1 = Form("Diabolo")
val form2 = Form("Chanilla")
val movepool1 = IndexedSeq(Move("Ice Beam"))
val movepool2 = IndexedSeq(Move("Scald"))
val p1 = TrainerID("Jaeda", Gender.Female, 0)
val p2 = TrainerID("Wild Monster", Gender.Male, 0)
val party1 = new Party(p1, new MonsterPtr(new Monster(new StorageMon("Allied Mon", 500, Gene.randomGene(null, form1), form1, Statistic.emptyEvs, movepool1))), IndexedSeq())
val party2 = new Party(p2, new MonsterPtr(new Monster(new StorageMon("Wild Mon", 500, Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2))), IndexedSeq(
new Monster(new StorageMon("Sideboard Mon", 500, Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2))
))
println(form1.xpCurve)
val engine = new BattleEngine(party1, party2)
engine.play()
println(party1.lead.elements)
}
}

View File

@@ -0,0 +1,14 @@
package fmon.battle
import fmon.stat._
import fmon.stat.Statistic.Speed
case class Action(user : MonsterPtr, move : MoveTurn, target : MonsterPtr) extends Comparable[Action] {
override def compareTo(other : Action) = {
if (move.prior == other.move.prior) {
other.user(Speed) compareTo user(Speed)
} else {
other.move.prior compareTo move.prior
}
}
}

View File

@@ -0,0 +1,59 @@
package fmon.battle
import scala.util.Random
import fmon.stat._
import fmon.stat.Statistic._
import fmon.util.Fraction
class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random) {
def play() = {
println(player.lead.level)
while (player.canFight && enemy.canFight) {
playTurn()
}
}
def playTurn() = {
val playerAction = player.pollAction(enemy)
val enemyAction = enemy.pollAction(player)
val actions = Seq(playerAction, enemyAction)
val queue = rng.shuffle(actions).sorted // Shuffle to randomize in the event of a tie
queue.foreach(useMove)
val eotQueue = Seq(player.lead, enemy.lead).sortBy(_(Speed))
eotQueue.foreach(mon => mon.status.map(_.onResidual(mon)))
if (!player.lead.isAlive) {
if (player.canFight) {
val replace = player.pollReplacement(enemy)
player.switchIn(replace)
} else {
println(s"${player.trainer} has lost!")
}
}
if (!enemy.lead.isAlive) {
if (enemy.canFight) {
val replace = enemy.pollReplacement(player)
enemy.switchIn(replace)
} else {
println(s"${enemy.trainer} has lost!")
}
}
println(s"${player.lead}(${player.lead.hp}/${player.lead(Hp)})")
println(s"${enemy.lead}(${enemy.lead.hp}/${enemy.lead(Hp)})")
}
def useMove(action: Action) = {
val user = action.user
val move = action.move
val target = action.target
if (user.isAlive) {
if (user.status.forall(_.onBeforeMove(user, move, target, rng))) {
move.useMove(user, target)
}
}
}
}

View File

@@ -0,0 +1,12 @@
package fmon
import scala.language.implicitConversions
import scala.util.Random
import fmon.util._
package object battle {
implicit def rngDice(rng : Random) = new Dice(rng)
implicit def int2Helper(x : Int) = new IntFraction(x)
}

View File

@@ -0,0 +1,23 @@
package fmon.stat
import scala.io.Source
import fmon.util.YamlHelper
case class Element(val name : String, val effect : Map[String, Double]) {
def -->(other : Element) = {
effect.getOrElse(other.name, 1.0)
}
def <--(other : Element) = other --> this
override def toString = name
}
object Element {
private val elements = YamlHelper.extractSeq[Element](Element.getClass.getResourceAsStream("data/elements.yaml"))
private val fromName = elements.map(e => (e.name, e)).toMap
def apply(name : String) = fromName(name)
}

View File

@@ -0,0 +1,57 @@
package fmon.stat
import scala.io.Source
import com.fasterxml.jackson.module.scala.JsonScalaEnumeration
import Statistic._
import fmon.util.YamlHelper
abstract class Form {
val name : String
//val height
//val weight
val desc : String
val elements : IndexedSeq[Element]
val baseStats : Map[Stat, Int]
// val appearance // animation
// moves
// abilities
val xpCurve : XpCurve
val catchRate : Int
// val color
override def toString = {
name
}
}
case class FormToken(
val name : String,
val desc : String,
val elements : IndexedSeq[String],
val baseStats : Map[String, Int],
@JsonScalaEnumeration(classOf[XpCurveType]) val xpCurve : XpCurve,
val catchRate : Int = 255
) {
def instantiate() = {
val self = this
new Form {
val name = self.name
val desc = self.desc
val elements = self.elements.map(Element(_))
val baseStats = self.baseStats.map{ case (s, i) => (Statistic(s), i)}
val xpCurve = self.xpCurve
val catchRate = self.catchRate
}
}
}
object Form {
def apply(name : String) = byName(name)
val forms = YamlHelper.extractSeq[FormToken](Form.getClass.getResourceAsStream("data/forms.yaml")).map(_.instantiate())
val byName = forms.map(f => (f.name, f)).toMap
}

View File

@@ -0,0 +1,5 @@
package fmon.stat
object Gender extends Enumeration {
val Male, Female, Fluid, Neuter = Value
}

View File

@@ -0,0 +1,16 @@
package fmon.stat
import scala.util.Random
case class Gene(val gender : Gender, val nature : Nature, val ivs : Map[Stat, Int], ot : TrainerID) {
}
object Gene {
final val MaxIV = 31
def randomGene(ot : TrainerID, form : Form)(implicit rng : Random) = {
val gender = Gender.Neuter // TODO
val ivs = Statistic.values.map(s => (s, rng.nextInt(MaxIV + 1))).toMap
Gene(gender, Nature.randomNature, ivs, ot)
}
}

View File

@@ -0,0 +1,70 @@
package fmon.stat
import scala.util._
import fmon.util._
import Monster._
import Statistic._
class Monster(val base : StorageMon) {
def level = base.level
val stats = Statistic.buildMap(computeStat)
var boosts = Statistic.buildMap(_ => 0)
var hp = stats(Hp)
var status : Option[Status] = None
def isAlive = hp > 0
def apply(s : Stat) = {
val mod = boosts.getOrElse(s, 0)
val mult = if (mod > 0) Fraction(2 + mod, 2) else Fraction(2, 2 - mod)
status.foldLeft(1.frac)((m, sts) => m * sts.onModifyStat(this, s)) * mult * stats(s)
}
def elements = base.form.elements
def takeDamage(dmg : Int) : Int = {
val actualDmg = Math.min(hp, dmg)
hp -= actualDmg
actualDmg
}
def recoverDamage(healing : Int) {
hp = Math.min(stats(Hp), hp + healing)
}
def cureStatus() {
status.map(_.onEnd(this))
status = None
}
def applyBoost(s : Stat, boost : Int) {
val modified = boosts.getOrElse(s, 0) + boost
boosts = boosts.updated(s, Math.min(MaxBoost, Math.max(-MaxBoost, modified)))
}
def effectiveness(element : Element) : Double = {
elements.foldLeft(1.0)((m, e) => m * (element --> e))
}
private def computeStat(s : Stat) : Int = {
val num = 2 * base.form.baseStats(s) + base.gene.ivs(s) + base.evs(s) / 4
val frac = num * level / 100
val score = if(s == Hp) frac + 10 + level else frac + 5
(score * base.gene.nature(s)).toInt
}
def name = if (base.nickname != null) base.nickname else base.form.name
override def toString = name
}
object Monster {
final val MaxBoost = 6
def build(trainer : TrainerID, form : Form)(implicit rng : Random) = {
}
}

View File

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

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

View File

@@ -0,0 +1,40 @@
package fmon.stat
import scala.util.Random
import Statistic._
object Nature extends Enumeration {
case class Val protected(boosts : Map[Stat, Double]) extends super.Val {
def apply(s : Stat) = boosts.getOrElse(s, 1.0)
}
val Hardy = Val(Map())
val Lonely = Val(Map(PAtk -> 1.1, PDef -> 0.9))
val Brave = Val(Map(PAtk -> 1.1, Speed -> 0.9))
val Adamant = Val(Map(PAtk -> 1.1, MAtk -> 0.9))
val Naughty = Val(Map(PAtk -> 1.1, MDef -> 0.9))
val Bold = Val(Map(PDef -> 1.1, PAtk -> 0.9))
val Docile = Val(Map())
val Relaxed = Val(Map(PDef -> 1.1, Speed -> 0.9))
val Impish = Val(Map(PDef -> 1.1, MAtk -> 0.9))
val Lax = Val(Map(PDef -> 1.1, MDef -> 0.9))
val Timid = Val(Map(Speed -> 1.1, PAtk -> 0.9))
val Hasty = Val(Map(Speed -> 1.1, PDef -> 0.9))
val Serious = Val(Map())
val Jolly = Val(Map(Speed -> 1.1, MAtk -> 0.9))
val Naive = Val(Map(Speed -> 1.1, MDef -> 0.9))
val Modest = Val(Map(MAtk -> 1.1, PAtk -> 0.9))
val Mild = Val(Map(MAtk -> 1.1, PDef -> 0.9))
val Quiet = Val(Map(MAtk -> 1.1, Speed -> 0.9))
val Bashful = Val(Map())
val Rash = Val(Map(MAtk -> 1.1, MDef -> 0.9))
val Calm = Val(Map(MDef -> 1.1, PAtk -> 0.9))
val Gentle = Val(Map(MDef -> 1.1, PDef -> 0.9))
val Sassy = Val(Map(MDef -> 1.1, Speed -> 0.9))
val Careful = Val(Map(MDef -> 1.1, MAtk -> 0.9))
val Quirky = Val(Map())
def randomNature(implicit rng: Random) : Nature = rng.pick(natures)
val natures = values.toIndexedSeq.map(_.asInstanceOf[Val])
}

View File

@@ -0,0 +1,51 @@
package fmon.stat
import scala.util.Random
import fmon.battle.Action
import fmon.stat.Target._
class Party(val trainer: TrainerID, val lead: MonsterPtr, var sideboard: IndexedSeq[Monster]) {
def pollAction(them: Party)(implicit rng: Random): Action = {
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) {
user
} else {
them.lead
}
}
def pollReplacement(them: Party)(implicit rng: Random): Monster = {
sideboard.filter(_.isAlive).head
}
def switchIn(mon: Monster) = {
val index = sideboard.indexOf(mon)
sideboard = sideboard.updated(index, lead.mon)
lead.mon = mon
}
def shouldSwitch(them: Party)(implicit rng: Random): Boolean = {
false
}
def canFight: Boolean = {
lead.isAlive || hasBackup
}
def hasBackup: Boolean = {
sideboard.exists(_.isAlive)
}
}

View File

@@ -0,0 +1,31 @@
package fmon.stat
abstract class Secondary {
val chance : Int
val boosts : Map[Stat, Int]
val status : Status
// val volatileStatus
//val onHit :
// val self
}
case class SecondaryToken(
val chance : Int,
val boosts : Map[String, Int],
val status : String,
//val volatileStatus
val onHit : String
) {
def instantiate() : Secondary = {
val self = this
new Secondary {
val chance = self.chance
val boosts = if (self.boosts != null) self.boosts.map{case (s, i) => (Statistic(s), i)} else Map()
val status = if (self.status != null) Status(self.status) else null
}
}
}
object Secondary {
}

View File

@@ -0,0 +1,11 @@
package fmon.stat
class Species {
// name
// gender ratio
// forms
// baby forms (hatchable)
// evolution conditions (form change)
// egg groups
// egg steps
}

View File

@@ -0,0 +1,29 @@
package fmon.stat
object Statistic extends Enumeration {
val Hp = Value("Hp")
val PAtk = Value("P. Atk")
val PDef = Value("P. Def")
val MAtk = Value("M. Atk")
val MDef = Value("M. Def")
val Speed = Value("Speed")
val Accuracy = Value("Accuracy")
val Evasion = Value("Evasion")
def apply(s : String) = s match {
case "hp" => Hp
case "patk" => PAtk
case "pdef" => PDef
case "matk" => MAtk
case "mdef" => MDef
case "spd" => Speed
case "acc" => Accuracy
case "evd" => Evasion
}
def buildMap(f : Stat => Int) : Map[Stat, Int] = {
Map(Hp -> f(Hp), PAtk -> f(PAtk), PDef -> f(PDef), MAtk -> f(MAtk), MDef -> f(MDef), Speed -> f(Speed))
}
def emptyEvs = buildMap(_ => 0)
}

View File

@@ -0,0 +1,142 @@
package fmon.stat
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
import scala.util.Random
import scala.io.Source
import fmon.util._
abstract class Status {
val name: String
// val id
// val effectType
val onStart: Monster => Unit
val onEnd: Monster => Unit
val onModifyStat: (Monster, Stat) => Fraction
// val onBeforeMovePriority : Int
val onBeforeMove: (Monster, MoveTurn, Monster, Random) => Boolean
// val onModifyMove
// val onHit
val onResidualOrder: Int
val onResidual: Monster => Unit
// val onSwitchIn
override def toString = name
}
case class StatusToken(
val name: String,
val onStart: String,
val onEnd: String,
val onBeforeMove: String,
val onModifyStat: String,
val onResidualOrder: Int,
val onResidual: String) {
def instantiate() = {
val self = this
new Status {
val name = self.name
val onStart = Status.compileOnStart(self.onStart)
val onEnd = Status.compileOnStart(self.onEnd)
val onBeforeMove = Status.compileOnBeforeMove(self.onBeforeMove)
val onModifyStat = Status.compileOnModifyStat(self.onModifyStat)
val onResidualOrder = self.onResidualOrder
val onResidual = Status.compileOnResidual(self.onResidual)
}
}
}
object Status {
private var statuses = Map[String, Status]()
val tokens = YamlHelper.extractSeq[StatusToken](Source.fromInputStream(Move.getClass.getResourceAsStream("data/statuses.yaml"))).map(t => (t.name, t)).toMap
def apply(name: String) = {
if (!statuses.contains(name)) {
statuses = statuses.updated(name, tokens(name).instantiate())
}
statuses(name)
}
private val tb = runtimeMirror(getClass.getClassLoader).mkToolBox()
def compileOnStart(code: String): (Monster /*, source, source effect */ ) => Unit = {
if (code == null) {
_ => ()
} else {
val tree = tb.parse(
s"""
|import fmon.stat._
|import fmon.stat.Statistic._
|def onStart(mon : Monster) = {
| $code
|}
|onStart _
""".stripMargin)
val f = tb.compile(tree)
val wrapper = f()
wrapper.asInstanceOf[Monster => Unit]
}
}
def compileOnBeforeMove(code: String): (Monster, MoveTurn, Monster, Random) => Boolean = {
if (code == null) {
(_, _, _, _) => true
} else {
val tree = tb.parse(
s"""
import scala.util.Random
import fmon.stat._
import fmon.stat.Statistic._
def onBeforeMove(mon: Monster, move: MoveTurn, target: Monster, rng: Random): Boolean = {
$code
}
onBeforeMove _
""")
val f = tb.compile(tree)
val wrapper = f()
wrapper.asInstanceOf[(Monster, MoveTurn, Monster, Random) => Boolean]
}
}
def compileOnModifyStat(code: String): (Monster, Stat) => Fraction = {
if (code == null) {
(_, _) => 1.frac
} else {
val tree = tb.parse(
s"""
import fmon.stat._
import fmon.stat.Statistic._
import fmon.util._
def onModifyStat(mon: Monster, stat: Stat): Fraction = {
$code
}
onModifyStat _
""")
val f = tb.compile(tree)
val wrapper = f()
wrapper.asInstanceOf[(Monster, Stat) => Fraction]
}
}
def compileOnResidual(code: String): Monster => Unit = {
if (code == null) {
_ => ()
} else {
val tree = tb.parse(
s"""
|import fmon.stat._
|import fmon.stat.Statistic._
|def onResidual(mon : Monster) = {
| $code
|}
|onResidual _
""".stripMargin)
val f = tb.compile(tree)
val wrapper = f()
wrapper.asInstanceOf[Monster => Unit]
}
}
}

View File

@@ -0,0 +1,5 @@
package fmon.stat
class StorageMon(val nickname : String, val xp: Int, val gene : Gene, val form : Form, val evs : Map[Stat, Int], val moves : IndexedSeq[Move]) {
def level = form.xpCurve(xp)
}

View File

@@ -0,0 +1,6 @@
package fmon.stat
case class TrainerID(name : String, gender : Gender, id : Long) {
override def toString : String = name
}

View File

@@ -0,0 +1,15 @@
package fmon.stat
import fmon.util.TypeReference
object XpCurve extends Enumeration {
case class Val protected(val curve: Int => Int) extends super.Val {
def apply(xp : Int) = curve(xp)
}
val Fast = Val(xp => Math.cbrt(1.2 * xp).toInt)
val MediumFast = Val(xp => Math.cbrt(xp).toInt)
val MediumSlow = Val(xp => Math.cbrt(0.85 * xp).toInt)
val Slow = Val(xp => Math.cbrt(0.75 * xp).toInt)
}
class XpCurveType extends TypeReference[XpCurve.type]

View File

@@ -0,0 +1,131 @@
[
{
name: Normal,
effect: {
Rock: 0.5,
Steel: 0.5,
Ghost: 0
}
},
{
name: Fire,
effect: {
Fire: 0.5,
Water: 0.5,
Rock: 0.5,
Dragon: 0.5,
Bug: 2.0,
Grass: 2.0,
Ice: 2.0,
Steel: 2
}
},
{
name: Grass,
effect: {
Bug: 0.5,
Dragon: 0.5,
Fire: 0.5,
Flying: 0.5,
Water: 2.0,
Ground: 2.0,
Rock: 2.0,
Grass: 0.5,
Poison: 0.5
}
},
{
name: Water,
effect: {
Fire: 2.0,
Ground: 2.0,
Rock: 2.0,
Water: 0.5,
Grass: 0.5,
Dragon: 0.5
}
},
{
name: Electric,
effect: {
Flying: 2.0,
Water: 2.0,
Dragon: 0.5,
Electric: 0.5,
Grass: 0.5,
Ground: 0
}
},
{
name: Ice,
effect: {
Dragon: 2.0,
Flying: 2.0,
Grass: 2.0,
Ground: 2.0,
Fire: 0.5,
Ice: 0.5,
Water: 0.5,
Steel: 0.5
}
},
{
name: Psychic,
effect: {
Fighting: 2.0,
Poison: 2.0,
Psychic: 0.5,
Steel: 0.5,
Dark: 0
}
},
{
name: Dark,
effect: {
Ghost: 2.0,
Psychic: 2.0,
Dark: 0.5,
Fairy: 0.5,
Fight: 0.5
}
},
{
name: Fairy,
effect: {
Dark: 2.0,
Dragon: 2.0,
Fighting: 2.0,
Fire: 0.5,
Poison: 0.5,
Steel: 0.5
}
},
{
name: Fighting,
effect: {
Dark: 2.0,
Ice: 2.0,
Normal: 2.0,
Rock: 2.0,
Steel: 2.0,
Bug: 0.5,
Fairy: 0.5,
Flying: 0.5,
Poison: 0.5,
Psychic: 0.5,
Ghost: 0.0
}
},
{
name: Poison,
effect: {
Fairy: 2.0,
Grass: 2.0,
Poison: 0.5,
Ground: 0.5,
Rock: 0.5,
Ghost: 0.5,
Steel: 0.0
}
}
]

View File

@@ -0,0 +1,26 @@
- name: Chanilla
desc: A vanilla chameleon.
elements:
- Normal
baseStats:
hp: 60
patk: 70
pdef: 70
matk: 70
mdef: 70
spd: 75
xpCurve: MediumSlow
- name: Diabolo
desc: This is a ball of lightning from Hell itself.
elements:
- Electric
- Fire
baseStats:
hp: 40
patk: 110
pdef: 30
matk: 130
mdef: 40
spd: 130
xpCurve: Slow

View File

@@ -0,0 +1,366 @@
- name: "Absorb"
num: 71
accuracy: 100
basePower: 20
category: "Special"
desc: "The user recovers 1/2 the HP lost by the target, rounded half up. If Big Root is held by the user, the HP recovered is 1.3x normal, rounded half down."
shortDesc: "User recovers 50% of the damage dealt."
id: "absorb"
pp: 25
priority: 0
flags:
protect: 1
mirror: 1
heal: 1
drain: {num: 1, denom: 2}
secondary: null
target: "Normal"
type: "Grass"
zMovePower: 100
contestType: "Clever"
- name: Aqua Jet
num: 453
accuracy: 100
basePower: 40
category: Physical
desc: No additional effect.
shortDesc: Usually goes first.
id: aquajet
isViable: true
pp: 20
priority: 1
flags:
contact: 1
protect: 1
mirror: 1
secondary: null
target: Normal
type: Water
type: Water
zMovePower: 100
contestType: Cool
- name: "Bulk Up"
num: 339
accuracy: 0
basePower: 0
category: "Status"
desc: "Raises the user's Attack and Defense by 1 stage."
shortDesc: "Raises the user's Attack and Defense by 1."
id: "bulkup"
isViable: true
pp: 20
priority: 0
flags:
snatch: 1
boosts:
patk: 1
pdef: 1
secondary: null
target: "Self"
type: "Fighting"
zMoveBoost:
patk: 1
contestType: "Cool"
- name: Charm
num: 204
accuracy: 100
basePower: 0
category: Status
desc: Lowers the target's Attack by 2 stages.
shortDesc: Lowers the target's Attack by 2.
id: charm
pp: 20
priority: 0
flags:
protect: 1
reflectable: 1
mirror: 1
mystery: 1
boosts:
patk: -2
secondary: null
target: Normal
type: Fairy
zMoveBoost:
pdef: 1
contestType: Cute
- name: "Close Combat"
num: 370
accuracy: 100
basePower: 120
category: "Physical"
desc: "Lowers the user's Defense and Special Defense by 1 stage."
shortDesc: "Lowers the user's Defense and Sp. Def by 1."
id: "closecombat"
isViable: true
pp: 5
priority: 0
flags:
contact: 1
protect: 1
mirror: 1
self:
boosts:
pdef: -1
mdef: -1
secondary: null
target: "Normal"
type: "Fighting"
zMovePower: 190
contestType: "Tough"
- name: Electro Ball
num: 486
accuracy: 100
basePower: 0
basePowerCallback: |
val ratio = user(Speed) / target(Speed)
// this.debug([40, 60, 80, 120, 150][(Math.floor(ratio) > 4 ? 4 : Math.floor(ratio))] + ' bp');
if (ratio >= 4) {
150
} else if (ratio >= 3) {
120
} else if (ratio >= 2) {
80
} else if (ratio >= 1) {
60;
} else {
40;
}
category: Special
desc: "The power of this move depends on (user's current Speed / target's current Speed), rounded down. Power is equal to 150 if the result is 4 or more, 120 if 3, 80 if 2, 60 if 1, 40 if less than 1. If the target's current Speed is 0, this move's power is 40."
shortDesc: "More power the faster the user is than the target."
id: electroball
pp: 10
priority: 0
flags:
bullet: 1
protect: 1
mirror: 1
secondary: null
target: "Normal"
type: Electric
zMovePower: 160
contestType: "Cool"
- name: "Frost Breath"
num: 524
accuracy: 90
basePower: 60
category: "Special"
desc: "This move is always a critical hit unless the target is under the effect of Lucky Chant or has the Battle Armor or Shell Armor Abilities."
shortDesc: "Always results in a critical hit."
id: "frostbreath"
pp: 10
priority: 0
flags: {protect: 1, mirror: 1}
critRatio: 4
secondary: null
target: "Normal"
type: "Ice"
zMovePower: 120
contestType: "Beautiful"
- name: Ice Beam
accuracy: 100
basePower: 90
category: Special
contestType: Beautiful
desc: Has a 10% chance to freeze the target.
flags:
mirror: 1
protect: 1
id: icebeam
isViable: true
num: 58
pp: 10
priority: 0
secondary:
chance: 10
status: frz
shortDesc: 10% chance to freeze the target.
target: Normal
type: Ice
zMovePower: 175
- name: "Poison Gas"
num: 139
accuracy: 90
basePower: 0
category: "Status"
desc: "Poisons the target."
shortDesc: "Poisons the foe(s)."
id: "poisongas"
pp: 40
priority: 0
flags:
protect: 1
reflectable: 1
mirror: 1
status: 'psn'
secondary: null
target: "AllAdjacentFoes"
type: "Poison"
zMoveBoost:
def: 1
contestType: "Clever"
- name: "Poison Sting"
num: 40
accuracy: 100
basePower: 15
category: "Physical"
desc: "Has a 30% chance to poison the target."
shortDesc: "30% chance to poison the target."
id: "poisonsting"
pp: 35
priority: 0
flags:
protect: 1
mirror: 1
secondary:
chance: 30
status: 'psn'
target: "Normal"
type: "Poison"
zMovePower: 100
contestType: "Clever"
- name: Scald
accuracy: 100
basePower: 80
category: Special
contestType: Tough
desc: Has a 30% chance to burn the target. The target thaws out if it is frozen.
flags:
defrost: 1
mirror: 1
protect: 1
thaws: 1
id: scald
isViable: true
num: 503
pp: 15
priority: 0
secondary:
chance: 30
status: brn
shortDesc: 30% chance to burn the target. Thaws target.
target: Normal
type: Water
zMovePower: 160
- name: "Snarl"
num: 555
accuracy: 95
basePower: 55
category: "Special"
desc: "Has a 100% chance to lower the target's Special Attack by 1 stage."
shortDesc: "100% chance to lower the foe(s) Sp. Atk by 1."
id: "snarl"
pp: 15
priority: 0
flags:
protect: 1
mirror: 1
sound: 1
authentic: 1
secondary:
chance: 100
boosts:
matk: -1
target: "AllAdjacentFoes"
type: "Dark"
zMovePower: 100
contestType: "Tough"
- name: Tackle
num: 33
accuracy: 100
basePower: 40
category: Physical
shortDesc: No additional effect.
id: tackle
pp: 35
priority: 0
flags:
contact: 1
protect: 1
mirror: 1
secondary: null
target: Normal
type: Normal
zMovePower: 100
contestType: Tough
- name: Thunder Wave
accuracy: 90
basePower: 0
category: Status
contestType: Cool
desc: Paralyzes the target. This move does not ignore type immunity.
flags:
mirror: 1
protect: 1
reflectable: 1
id: thunderwave
ignoreImmunity: false
isViable: true
num: 86
pp: 20
priority: 0
secondary: null
shortDesc: Paralyzes the target.
status: par
target: Normal
type: Electric
zMoveBoost:
MDef: 1
- name: "Volt Tackle"
num: 344
accuracy: 100
basePower: 120
category: "Physical"
desc: "Has a 10% chance to paralyze the target. If the target lost HP, the user takes recoil damage equal to 33% the HP lost by the target, rounded half up, but not less than 1 HP."
shortDesc: "Has 33% recoil. 10% chance to paralyze target."
id: "volttackle"
isViable: true
pp: 15
priority: 0
flags: {contact: 1, protect: 1, mirror: 1}
recoil: {num: 33, denom: 100}
secondary:
chance: 100
status: 'par'
target: "Normal"
type: "Electric"
zMovePower: 190
contestType: "Cool"
- name: Will-O-Wisp
accuracy: 85
basePower: 0
category: Status
contestType: Beautiful
desc: Burns the target.
flags:
mirror: 1
protect: 1
reflectable: 1
id: willowisp
isViable: true
num: 261
pp: 15
priority: 0
secondary: null
shortDesc: Burns the target.
status: brn
target: Normal
type: Fire
zMoveBoost:
PAtk: 1

View File

@@ -0,0 +1,171 @@
- name: 'brn'
id: 'brn'
num: 0
effectType: 'Status'
onModifyStat: |
if (stat == PAtk) {
1 \\ 2
} else {
1.frac
}
onStart: |
println(s"${mon} was burned!")
/*
if (sourceEffect && sourceEffect.id === 'flameorb') {
target.status = Status('brn', '[from] 'item': Flame Orb');
} else if (sourceEffect && sourceEffect.effectType === 'Ability') {
target.status = Status('brn', '[from] 'ability': ' + sourceEffect.name, '[of] ' + source);
} else {
target.status = Status('brn');
}*/
onEnd: |
println(s"${mon} was healed of its burn.")
onResidualOrder: 9
onResidual: |
mon.takeDamage(mon(Hp) / 16);
println(s"${mon} was hurt by its burn!")
- name: 'par'
id: 'par'
num: 0
effectType: 'Status'
onStart: |
println(s"${mon} was paralyzed!")
/*
if (sourceEffect && sourceEffect.effectType === 'Ability') {
this.add('-status', target, 'par', '[from] ability: ' + sourceEffect.name, '[of] ' + source);
} else {
this.add('-status', target, 'par');
}
*/
onEnd: |
println(s"${mon} is no longer paralyzed!")
onModifyStat: |
if (stat == Speed /* && !mon.hasAbility('quickfeet') */) {
1 \\ 2
} else {
1.frac
}
onBeforeMovePriority: 1
onBeforeMove: |
if (rng.chance(1, 4)) {
println(s"${mon} is fully paralyzed!")
false
} else {
true
}
- name: 'slp'
id: 'slp'
num: 0
effectType: 'Status'
onStart: |
if (sourceEffect && sourceEffect.effectType === 'Ability') {
this.add('-status', target, 'slp', '[from] ability: ' + sourceEffect.name, '[of] ' + source);
} else if (sourceEffect && sourceEffect.effectType === 'Move') {
this.add('-status', target, 'slp', '[from] move: ' + sourceEffect.name);
} else {
this.add('-status', target, 'slp');
}
// 1-3 turns
this.effectData.startTime = this.random(2, 5);
this.effectData.time = this.effectData.startTime;
onEnd: |
println(s"${mon} woke up!")
onBeforeMovePriority: 10
onBeforeMove: |
/*
if (pokemon.hasAbility('earlybird')) {
pokemon.statusData.time--;
}
*/
pokemon.statusData.time--;
if (pokemon.statusData.time <= 0) {
pokemon.cureStatus();
return;
}
/*
this.add('cant', pokemon, 'slp');
if (move.sleepUsable) {
return;
}
*/
return false;
- name: 'frz'
effectType: Status
id: frz
num: 0
onBeforeMove: |-
if (move.flags("defrost") || rng.chance(1, 5)) {
mon.cureStatus()
true
} else {
println(s"${mon} is completely frozen!")
false
}
onBeforeMovePriority: 10
onHit: |-
if (move.flags("thaws") || move.element === 'Fire' && move.category !== MoveType.Status) {
target.cureStatus()
}
onStart: |-
println(s"${mon} was frozen solid!")
/*
if (sourceEffect && sourceEffect.effectType === 'Ability') {
target.status = Status('frz', '[from] 'ability': ' + sourceEffect.name, '[of] ' + source);
} else {
target.status = Status('frz');
}
if (target.template.species === 'Shaymin-Sky' && target.baseTemplate.baseSpecies === 'Shaymin') {
target.formeChange('Shaymin', this.effect, true);
}
*/
onEnd: |-
println(s"${mon} thawed out.")
onModifyMove: |
if (move.flags("defrost")) {
mon.cureStatus()
}
- name: 'psn'
id: 'psn'
num: 0
effectType: 'Status'
onStart: |
println(s"${mon} was poisoned!")
/*
if (sourceEffect && sourceEffect.effectType === 'Ability') {
this.add('-status', target, 'psn', '[from] ability: ' + sourceEffect.name, '[of] ' + source);
} else {
this.add('-status', target, 'psn');
}
*/
oneEnd: |
println(s"${mon} was cured of its poison.")
onResidualOrder: 9
onResidual: |
mon.takeDamage(mon(Hp) / 8);
println(s"${mon} was damaged by poison!")
- name: 'tox'
id: 'tox'
num: 0
effectType: 'Status'
onStart: |
this.effectData.stage = 0;
if (sourceEffect && sourceEffect.id === 'toxicorb') {
this.add('-status', target, 'tox', '[from] item: Toxic Orb');
} else if (sourceEffect && sourceEffect.effectType === 'Ability') {
this.add('-status', target, 'tox', '[from] ability: ' + sourceEffect.name, '[of] ' + source);
} else {
this.add('-status', target, 'tox');
}
onSwitchIn: |
this.effectData.stage = 0;
onResidualOrder: 9
onResidual: |
if (this.effectData.stage < 15) {
this.effectData.stage++;
}
this.damage(this.clampIntRange(pokemon.maxhp / 16, 1) * this.effectData.stage);

View File

@@ -0,0 +1,23 @@
package fmon
import scala.language.implicitConversions
import org.json4s.DefaultFormats
import org.json4s.ext.EnumNameSerializer
import scala.util.Random
import fmon.util.Dice
package object stat {
type Stat = Statistic.Value
type MoveType = MoveType.Value
type Gender = Gender.Value
type Nature = Nature.Val
type Target = Target.Value
type XpCurve = XpCurve.Val
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
}

View File

@@ -0,0 +1,12 @@
package fmon.util
import scala.util.Random
class Dice(val rng : Random) extends AnyVal {
def nextInt(min : Int, max : Int) = rng.nextInt(max - min + 1) + min
def pick[T](seq : IndexedSeq[T]) : T = seq(rng.nextInt(seq.size))
def chance(frac : Fraction) :Boolean = chance(frac.num, frac.denom)
def chance(num : Int, denom : Int) : Boolean = rng.nextInt(denom) < num
}

View File

@@ -0,0 +1,18 @@
package fmon.util
case class Fraction(val num : Int, val denom : Int) extends Ordered[Fraction] {
def *(f : Fraction) = Fraction(num * f.num, denom * f.denom)
def *(x : Int) : Int = x * num / denom
def unary_-() : Fraction = Fraction(-num, denom)
override def compare(f : Fraction) : Int = {
(num * f.denom) compare (f.num * denom)
}
}
class IntFraction(val x : Int) extends AnyVal {
def %% = Fraction(x, 100)
def \\ (denom : Int) = Fraction(x, denom)
def frac = Fraction(x, 1)
}

View File

@@ -0,0 +1,52 @@
package fmon.util
import org.json4s._
import org.json4s.jackson.JsonMethods._
import org.json4s.ext.EnumNameSerializer
import java.io.InputStream
import scala.io.Source
object JsonHelper {
// Maybe Look at Jackson-YAML & Jackson-Scala
implicit val formats = fmon.stat.formats
def extract[T](text : String)(implicit mf : Manifest[T]) : T = {
val json = parse(text)
json.extract[T]
}
def extract[T](source : Source)(implicit mf : Manifest[T]) : T = {
val text = source.getLines().mkString("\n")
extract(text)
}
def extractFromFile[T](filename : String)(implicit mf : Manifest[T]) : T = {
using(filename : InputStream)(reader => extract(Source.fromInputStream(filename)))
}
def toScala(jvalue : JValue): Any = jvalue match {
case JNothing => null
case JNull => null
case JString(s) => s
case JDouble(num) => num.toDouble
case JDecimal(num) => num.toInt
case JInt(num) => num.toInt
case JLong(num) => num.toLong
case JBool(value) => value
case JObject(obj) => obj.map{ case (s, o) => {
(s, toScala(o))
}}.toMap
case JArray(arr) => arr.map(toScala)
case JSet(data) => data.map(toScala)
}
def loadFromSource(source : Source) : List[Map[String, Any]] = {
val text = source.getLines().mkString("\n")
val array = parse(text).asInstanceOf[JArray]
array.arr.map(jo => toScala(jo).asInstanceOf[Map[String, Any]])
}
}

View File

@@ -0,0 +1,45 @@
package fmon.util
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import java.io.InputStream
import scala.io.Source
import fmon.util._
object YamlHelper {
val mapper = new ObjectMapper(new YAMLFactory()) with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
def extract[T](text : String)(implicit m : Manifest[T]) : T = {
mapper.readValue[T](text)
}
def extract[T](source : Source)(implicit mf : Manifest[T]) : T = {
val text = source.getLines().mkString("\n")
extract(text)
}
def extract[T](source : InputStream)(implicit m : Manifest[T]) : T = {
mapper.readValue[T](source)
}
def extractSeq[T](text : String)(implicit m : Manifest[T]) : IndexedSeq[T] = {
mapper.readValue[IndexedSeq[T]](text)
}
def extractSeq[T](source : Source)(implicit mf : Manifest[T]) : IndexedSeq[T] = {
val text = source.getLines().mkString("\n")
extractSeq(text)
}
def extractSeq[T](source : InputStream)(implicit m : Manifest[T]) : IndexedSeq[T] = {
mapper.readValue[IndexedSeq[T]](source)
}
}

View File

@@ -0,0 +1,46 @@
package fmon
import java.io._
import scala.language.implicitConversions
package object util {
type TypeReference[A] = com.fasterxml.jackson.core.`type`.TypeReference[A]
def using[T <: Closeable, A](resource : T)(block : T => A) : A = {
try {
block(resource)
} finally {
resource.close()
}
}
def makeParentDirs(filename : String) : Unit = {
val file = new File(filename)
file.getParentFile().mkdirs()
}
def getFilename(file : String) : String = {
file.split("\\.").head
}
implicit def fileName2Writer(filename : String) : PrintWriter = {
makeParentDirs(filename)
new PrintWriter(new BufferedWriter(new FileWriter(filename)))
}
implicit def fileName2Reader(filename : String) : BufferedReader = {
new BufferedReader(new FileReader(filename))
}
implicit def filename2OutputStream(filename : String) : PrintStream = {
makeParentDirs(filename)
new PrintStream(new BufferedOutputStream(new FileOutputStream(filename)))
}
implicit def filename2InputStream(filename : String) : BufferedInputStream = {
new BufferedInputStream(new FileInputStream(filename))
}
implicit def int2Helper(x : Int) = new IntFraction(x)
}