From 83e6bb7a3c0aa36e7404e9f9ea2bb9c7e3d99f90 Mon Sep 17 00:00:00 2001 From: James Daly Date: Thu, 23 May 2019 22:58:31 -0400 Subject: [PATCH] Adding FakeMon project --- .gitignore | 7 + FakeMon/src/mon/Game.scala | 32 ++++ FakeMon/src/mon/battle/Action.scala | 14 ++ FakeMon/src/mon/battle/BattleEngine.scala | 95 ++++++++++ FakeMon/src/mon/battle/package.scala | 12 ++ FakeMon/src/mon/stat/Element.scala | 23 +++ FakeMon/src/mon/stat/Form.scala | 62 +++++++ FakeMon/src/mon/stat/Gender.scala | 5 + FakeMon/src/mon/stat/Gene.scala | 16 ++ FakeMon/src/mon/stat/Monster.scala | 62 +++++++ FakeMon/src/mon/stat/Move.scala | 116 ++++++++++++ FakeMon/src/mon/stat/Party.scala | 17 ++ FakeMon/src/mon/stat/Secondary.scala | 31 ++++ FakeMon/src/mon/stat/Species.scala | 11 ++ FakeMon/src/mon/stat/Statistic.scala | 29 +++ FakeMon/src/mon/stat/Status.scala | 96 ++++++++++ FakeMon/src/mon/stat/StorageMon.scala | 5 + FakeMon/src/mon/stat/TrainerID.scala | 5 + FakeMon/src/mon/stat/data/elements.yaml | 131 +++++++++++++ FakeMon/src/mon/stat/data/forms.yaml | 24 +++ FakeMon/src/mon/stat/data/moves.yaml | 213 ++++++++++++++++++++++ FakeMon/src/mon/stat/data/statuses.yaml | 140 ++++++++++++++ FakeMon/src/mon/stat/package.scala | 13 ++ FakeMon/src/mon/util/Dice.scala | 11 ++ FakeMon/src/mon/util/Fraction.scala | 11 ++ FakeMon/src/mon/util/JsonHelper.scala | 52 ++++++ FakeMon/src/mon/util/YamlHelper.scala | 45 +++++ FakeMon/src/mon/util/package.scala | 44 +++++ 28 files changed, 1322 insertions(+) create mode 100644 .gitignore create mode 100644 FakeMon/src/mon/Game.scala create mode 100644 FakeMon/src/mon/battle/Action.scala create mode 100644 FakeMon/src/mon/battle/BattleEngine.scala create mode 100644 FakeMon/src/mon/battle/package.scala create mode 100644 FakeMon/src/mon/stat/Element.scala create mode 100644 FakeMon/src/mon/stat/Form.scala create mode 100644 FakeMon/src/mon/stat/Gender.scala create mode 100644 FakeMon/src/mon/stat/Gene.scala create mode 100644 FakeMon/src/mon/stat/Monster.scala create mode 100644 FakeMon/src/mon/stat/Move.scala create mode 100644 FakeMon/src/mon/stat/Party.scala create mode 100644 FakeMon/src/mon/stat/Secondary.scala create mode 100644 FakeMon/src/mon/stat/Species.scala create mode 100644 FakeMon/src/mon/stat/Statistic.scala create mode 100644 FakeMon/src/mon/stat/Status.scala create mode 100644 FakeMon/src/mon/stat/StorageMon.scala create mode 100644 FakeMon/src/mon/stat/TrainerID.scala create mode 100644 FakeMon/src/mon/stat/data/elements.yaml create mode 100644 FakeMon/src/mon/stat/data/forms.yaml create mode 100644 FakeMon/src/mon/stat/data/moves.yaml create mode 100644 FakeMon/src/mon/stat/data/statuses.yaml create mode 100644 FakeMon/src/mon/stat/package.scala create mode 100644 FakeMon/src/mon/util/Dice.scala create mode 100644 FakeMon/src/mon/util/Fraction.scala create mode 100644 FakeMon/src/mon/util/JsonHelper.scala create mode 100644 FakeMon/src/mon/util/YamlHelper.scala create mode 100644 FakeMon/src/mon/util/package.scala diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e137c18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*/bin/* +*.metadata +*.cache +*.cache-main +*.classpath +*.project +*/.settings/* diff --git a/FakeMon/src/mon/Game.scala b/FakeMon/src/mon/Game.scala new file mode 100644 index 0000000..5fbb53a --- /dev/null +++ b/FakeMon/src/mon/Game.scala @@ -0,0 +1,32 @@ +package mon + +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 mon.battle.BattleEngine +import mon.stat._ + +case class Prop(url : Seq[String]) + +object Game { + def main(args : Array[String]): Unit = { + println(Element("Water").effect) + println(Move.moves) + println(Form.forms) + println(Status("psn")) + implicit val rng = new scala.util.Random() + val form1 = Form("Diabolo") + val form2 = Form("Chanilla") + val movepool1 = IndexedSeq(Move("Poison Sting")) + val movepool2 = IndexedSeq(Move("Poison Sting")) + val party1 = new Party(null, new Monster(new StorageMon("Allied Mon", Gene.randomGene(null, form1), form1, Statistic.emptyEvs, movepool1)), IndexedSeq()) + val party2 = new Party(null, new Monster(new StorageMon("Wild Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2)), IndexedSeq()) + val engine = new BattleEngine(party1, party2) + engine.playTurn() + println(party1.lead.elements) + } +} \ No newline at end of file diff --git a/FakeMon/src/mon/battle/Action.scala b/FakeMon/src/mon/battle/Action.scala new file mode 100644 index 0000000..73fdf61 --- /dev/null +++ b/FakeMon/src/mon/battle/Action.scala @@ -0,0 +1,14 @@ +package mon.battle + +import mon.stat._ +import mon.stat.Statistic.Speed + +case class Action(user : Monster, move : Move, target : Monster) 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 + } + } +} \ No newline at end of file diff --git a/FakeMon/src/mon/battle/BattleEngine.scala b/FakeMon/src/mon/battle/BattleEngine.scala new file mode 100644 index 0000000..8628c14 --- /dev/null +++ b/FakeMon/src/mon/battle/BattleEngine.scala @@ -0,0 +1,95 @@ +package mon.battle + +import scala.util.Random + +import mon.stat._ +import mon.stat.Statistic._ +import mon.util.Fraction + +class BattleEngine(val player : Party, val enemy : Party)(implicit val rng : Random) { + + def playTurn() = { + val playerMove = player.pollAction + val playerTarget = player.pollTarget(playerMove, player.lead, player, enemy) + val enemyMove = enemy.pollAction + val enemyTarget = enemy.pollTarget(enemyMove, enemy.lead, enemy, player) + val actions = Seq(Action(player.lead, playerMove, playerTarget), Action(enemy.lead, enemyMove, enemyTarget)) + 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.map(_(mon)))) + 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 + println(s"$user used $move.") + if (attackRoll(user, move, target)) { + if (move.pow > 0 || move.powCallback != null) { + val dmg = damageRoll(user, move, target) + target.takeDamage(dmg) + println(s"$target takes $dmg damage!") + } + applyBoosts(target, move.boosts) + // TODO : Secondary effects + if (move.selfEffect != null) { + applyBoosts(user, move.selfEffect.boosts) + } + if (move.secondary != null && rng.chance(move.secondary.chance.%%)) { + applyBoosts(target, move.secondary.boosts) + if (move.secondary.status != null && target.status == None) { + // apply status + target.status = Some(move.secondary.status) + move.secondary.status.onStart.map(_(target)) + } + } + + // TODO : Support moves + // TODO : Multiparty + } else { + println("Missed!") + } + } + + def attackRoll(user : Monster, move : Move, target : Monster) = { + if (move.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 = move.accuracy.%% * mod + rng.chance(chance) + } + } + + def damageRoll(user : Monster, move : Move, target : Monster) = { + val atkStat = if (move.mvType == MoveType.Physical) PAtk else MAtk + val defStat = if (move.mvType == MoveType.Physical) PDef else MDef + // TODO : Fixed damage + val pow = if (move.powCallback != null) move.powCallback(user, target) else move.pow + val baseDmg = (2 * user.level / 5 + 2) * pow * user(atkStat) / (target(defStat) * 50) + 2 + val multiplier = target.effectiveness(move.element) + if (multiplier > 1.0) { + println("It's super effective!") + } else if (multiplier < 1.0) { + println("It's not very effective.") + } + val maxDmg = (baseDmg * multiplier).toInt + val minDmg = (17 \ 20) * maxDmg + rng.nextInt(minDmg, maxDmg) + } + + def applyBoosts(target : Monster, boosts : Map[Stat, Int]) { + 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!") + } + }} + } +} \ No newline at end of file diff --git a/FakeMon/src/mon/battle/package.scala b/FakeMon/src/mon/battle/package.scala new file mode 100644 index 0000000..6573ba2 --- /dev/null +++ b/FakeMon/src/mon/battle/package.scala @@ -0,0 +1,12 @@ +package mon + +import scala.languageFeature.implicitConversions +import scala.util.Random + +import mon.util._ + + +package object battle { + implicit def rngDice(rng : Random) = new Dice(rng) + implicit def int2Helper(x : Int) = new IntFraction(x) +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Element.scala b/FakeMon/src/mon/stat/Element.scala new file mode 100644 index 0000000..2907a26 --- /dev/null +++ b/FakeMon/src/mon/stat/Element.scala @@ -0,0 +1,23 @@ +package mon.stat + +import scala.io.Source + +import mon.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) +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Form.scala b/FakeMon/src/mon/stat/Form.scala new file mode 100644 index 0000000..4328d1c --- /dev/null +++ b/FakeMon/src/mon/stat/Form.scala @@ -0,0 +1,62 @@ +package mon.stat + +import scala.io.Source + +import Statistic._ + +import mon.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 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], + 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 catchRate = self.catchRate + } + } +} + +object Form { + def apply(name : String) = byName(name) + + def fromMap(dict : Map[String, Any]) = { + new Form { + val name = dict("name").toString + val desc = dict("desc").toString() + val elements = IndexedSeq() + val baseStats = Statistic.buildMap(_ => 10) + val catchRate = 255 + } + } + + val forms = YamlHelper.extractSeq[FormToken](Form.getClass.getResourceAsStream("data/forms.yaml")).map(_.instantiate()) + val byName = forms.map(f => (f.name, f)).toMap +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Gender.scala b/FakeMon/src/mon/stat/Gender.scala new file mode 100644 index 0000000..5c24e16 --- /dev/null +++ b/FakeMon/src/mon/stat/Gender.scala @@ -0,0 +1,5 @@ +package mon.stat + +object Gender extends Enumeration { + val Male, Female, Fluid, Neuter = Value +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Gene.scala b/FakeMon/src/mon/stat/Gene.scala new file mode 100644 index 0000000..da43cee --- /dev/null +++ b/FakeMon/src/mon/stat/Gene.scala @@ -0,0 +1,16 @@ +package mon.stat + +import scala.util.Random + +case class Gene(val gender : Gender, 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, ivs, ot) + } +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Monster.scala b/FakeMon/src/mon/stat/Monster.scala new file mode 100644 index 0000000..204af1f --- /dev/null +++ b/FakeMon/src/mon/stat/Monster.scala @@ -0,0 +1,62 @@ +package mon.stat + +import scala.util._ + +import mon.util.Fraction + +import Monster._ +import Statistic._ + +class Monster(val base : StorageMon) { + val level = 10 + 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) + mult * stats(s) + } + + def elements = base.form.elements + + + def takeDamage(dmg : Int) { + hp = Math.max(0, hp - dmg) + } + + def recoverDamage(healing : Int) { + hp = Math.min(stats(Hp), hp + healing) + } + + 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 + } + + override def toString = base.nickname +} + +object Monster { + final val MaxBoost = 6 + + def build(trainer : TrainerID, form : Form)(implicit rng : Random) = { + + } +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Move.scala b/FakeMon/src/mon/stat/Move.scala new file mode 100644 index 0000000..dfd3130 --- /dev/null +++ b/FakeMon/src/mon/stat/Move.scala @@ -0,0 +1,116 @@ +package mon.stat + +import scala.reflect.runtime.universe._ +import scala.tools.reflect.ToolBox + +import com.fasterxml.jackson.module.scala.JsonScalaEnumeration + +import scala.io.Source + +import mon.util.{TypeReference, YamlHelper} + +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] + +abstract class Move { + 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 selfEffect : Secondary + val secondary: Secondary + // boosts + // onHit + // onTryHit + + // zPower, zMoveEffect, zMoveBoost + + 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 self : SecondaryToken, + val secondary : SecondaryToken, + @JsonScalaEnumeration(classOf[TargetType]) val target : Target = Target.Normal, + val boosts : Map[String, Int] = Map(), + val crit : 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.crit + val selfEffect = if (token.self != null) token.self.instantiate() else null + val secondary = if (token.secondary != null) token.secondary.instantiate() else null + } + } +} + +object Move { + val tokens = YamlHelper.extractSeq[MoveToken](Source.fromInputStream(Move.getClass.getResourceAsStream("data/moves.yaml"))) + val moves = tokens.map(_.instantiate()) + val byName = moves.map(m => (m.name, m)).toMap + + def apply(s : String) = byName(s) + + 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 + } + + } +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Party.scala b/FakeMon/src/mon/stat/Party.scala new file mode 100644 index 0000000..694bd66 --- /dev/null +++ b/FakeMon/src/mon/stat/Party.scala @@ -0,0 +1,17 @@ +package mon.stat + +import scala.util.Random + +import mon.battle.rngDice +import mon.stat.Target._ + +class Party(val trainer : TrainerID, var lead : Monster, val sideboard : IndexedSeq[Monster]) { + def pollAction(implicit rng : Random) : Move = rng.pick(lead.base.moves) + def pollTarget(move : Move, user : Monster, us : Party, them: Party)(implicit rng : Random) : Monster = { + if (move.target == Self) { + user + } else { + them.lead + } + } +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Secondary.scala b/FakeMon/src/mon/stat/Secondary.scala new file mode 100644 index 0000000..1d92b0c --- /dev/null +++ b/FakeMon/src/mon/stat/Secondary.scala @@ -0,0 +1,31 @@ +package mon.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 { + +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Species.scala b/FakeMon/src/mon/stat/Species.scala new file mode 100644 index 0000000..96ed9bf --- /dev/null +++ b/FakeMon/src/mon/stat/Species.scala @@ -0,0 +1,11 @@ +package mon.stat + +class Species { + // name + // gender ratio + // forms + // baby forms (hatchable) + // evolution conditions (form change) + // egg groups + // egg steps +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Statistic.scala b/FakeMon/src/mon/stat/Statistic.scala new file mode 100644 index 0000000..4b57a01 --- /dev/null +++ b/FakeMon/src/mon/stat/Statistic.scala @@ -0,0 +1,29 @@ +package mon.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) +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Status.scala b/FakeMon/src/mon/stat/Status.scala new file mode 100644 index 0000000..dbea548 --- /dev/null +++ b/FakeMon/src/mon/stat/Status.scala @@ -0,0 +1,96 @@ +package mon.stat + +import scala.reflect.runtime.universe._ +import scala.tools.reflect.ToolBox + +import scala.io.Source + +import mon.util.YamlHelper + +abstract class Status { + val name : String + // val id + // val effectType + val onStart : Option[Monster => Unit] + // val onModifyStat + // val onBeforeMovePriority : Int + // val onBeforeMove + // val onModifyMove + // val onHit + val onResidualOrder : Int + val onResidual : Option[Monster => Unit] + // val onSwitchIn + + override def toString = name +} + +case class StatusToken( + val name : String, + val onStart : 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 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) : Option[(Monster /*, source, source effect */) => Unit] = { + if (code == null) { + None + } else { + val tree = tb.parse( + s""" + |import mon.stat.Monster + |import mon.stat.Statistic._ + |def onStart(mon : Monster) = { + | $code + |} + |onStart _ + """.stripMargin) + val f = tb.compile(tree) + val wrapper = f() + + Some(wrapper.asInstanceOf[Monster => Unit]) + } + } + + def compileOnResidual(code : String) : Option[Monster => Unit] = { + if (code == null) { + None + } else { + val tree = tb.parse( + s""" + |import mon.stat.Monster + |import mon.stat.Statistic._ + |def onResidual(mon : Monster) = { + | $code + |} + |onResidual _ + """.stripMargin) + val f = tb.compile(tree) + val wrapper = f() + + Some(wrapper.asInstanceOf[Monster => Unit]) + } + } +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/StorageMon.scala b/FakeMon/src/mon/stat/StorageMon.scala new file mode 100644 index 0000000..031258a --- /dev/null +++ b/FakeMon/src/mon/stat/StorageMon.scala @@ -0,0 +1,5 @@ +package mon.stat + +class StorageMon(val nickname : String, val gene : Gene, val form : Form, val evs : Map[Stat, Int], val moves : IndexedSeq[Move]) { + +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/TrainerID.scala b/FakeMon/src/mon/stat/TrainerID.scala new file mode 100644 index 0000000..28f9dc1 --- /dev/null +++ b/FakeMon/src/mon/stat/TrainerID.scala @@ -0,0 +1,5 @@ +package mon.stat + +case class TrainerID(name : String, gender : Gender, id : Long) { + +} \ No newline at end of file diff --git a/FakeMon/src/mon/stat/data/elements.yaml b/FakeMon/src/mon/stat/data/elements.yaml new file mode 100644 index 0000000..30ea6c2 --- /dev/null +++ b/FakeMon/src/mon/stat/data/elements.yaml @@ -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 + } +} +] \ No newline at end of file diff --git a/FakeMon/src/mon/stat/data/forms.yaml b/FakeMon/src/mon/stat/data/forms.yaml new file mode 100644 index 0000000..b3ac151 --- /dev/null +++ b/FakeMon/src/mon/stat/data/forms.yaml @@ -0,0 +1,24 @@ +- name: Chanilla + desc: A vanilla chameleon. + elements: + - Normal + baseStats: + hp: 60 + patk: 70 + pdef: 70 + matk: 70 + mdef: 70 + spd: 60 + +- 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 \ No newline at end of file diff --git a/FakeMon/src/mon/stat/data/moves.yaml b/FakeMon/src/mon/stat/data/moves.yaml new file mode 100644 index 0000000..44e72d0 --- /dev/null +++ b/FakeMon/src/mon/stat/data/moves.yaml @@ -0,0 +1,213 @@ +- 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 + spd: -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: "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: "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 diff --git a/FakeMon/src/mon/stat/data/statuses.yaml b/FakeMon/src/mon/stat/data/statuses.yaml new file mode 100644 index 0000000..77c1485 --- /dev/null +++ b/FakeMon/src/mon/stat/data/statuses.yaml @@ -0,0 +1,140 @@ +- name: 'brn' + id: 'brn' + num: 0 + effectType: 'Status' + onStart: | + if (sourceEffect && sourceEffect.id === 'flameorb') { + this.add('-status', target, 'brn', '[from] item: Flame Orb'); + } else if (sourceEffect && sourceEffect.effectType === 'Ability') { + this.add('-status', target, 'brn', '[from] ability: ' + sourceEffect.name, '[of] ' + source); + } else { + this.add('-status', target, 'brn'); + } + // Damage reduction is handled directly in the sim/battle.js damage function + onResidualOrder: 9 + onResidual: | + this.damage(pokemon.maxhp / 16); + +- name: 'par' + id: 'par' + num: 0 + effectType: 'Status' + onStart: | + if (sourceEffect && sourceEffect.effectType === 'Ability') { + this.add('-status', target, 'par', '[from] ability: ' + sourceEffect.name, '[of] ' + source); + } else { + this.add('-status', target, 'par'); + } + onModifySpe: | + if (!pokemon.hasAbility('quickfeet')) { + return this.chainModify(0.5); + } + onBeforeMovePriority: 1 + onBeforeMove: | + if (this.randomChance(1, 4)) { + this.add('cant', pokemon, 'par'); + return false; + } + +- 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; + 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' + id: 'frz' + num: 0 + effectType: 'Status' + onStart: | + if (sourceEffect && sourceEffect.effectType === 'Ability') { + this.add('-status', target, 'frz', '[from] ability: ' + sourceEffect.name, '[of] ' + source); + } else { + this.add('-status', target, 'frz'); + } + if (target.template.species === 'Shaymin-Sky' && target.baseTemplate.baseSpecies === 'Shaymin') { + target.formeChange('Shaymin', this.effect, true); + } + onBeforeMovePriority: 10 + onBeforeMove: | + if (move.flags['defrost']) return; + if (this.randomChance(1, 5)) { + pokemon.cureStatus(); + return; + } + this.add('cant', pokemon, 'frz'); + return false; + onModifyMove: | + if (move.flags['defrost']) { + this.add('-curestatus', pokemon, 'frz', '[from] move: ' + move); + pokemon.setStatus(''); + } + onHit: | + if (move.thawsTarget || move.type === 'Fire' && move.category !== 'Status') { + target.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'); + } + */ + 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); diff --git a/FakeMon/src/mon/stat/package.scala b/FakeMon/src/mon/stat/package.scala new file mode 100644 index 0000000..fed4240 --- /dev/null +++ b/FakeMon/src/mon/stat/package.scala @@ -0,0 +1,13 @@ +package mon + +import org.json4s.DefaultFormats +import org.json4s.ext.EnumNameSerializer + +package object stat { + type Stat = Statistic.Value + type MoveType = MoveType.Value + type Gender = Gender.Value + type Target = Target.Value + + implicit val formats = DefaultFormats + new EnumNameSerializer(MoveType) + new EnumNameSerializer(Gender) +} \ No newline at end of file diff --git a/FakeMon/src/mon/util/Dice.scala b/FakeMon/src/mon/util/Dice.scala new file mode 100644 index 0000000..58391ca --- /dev/null +++ b/FakeMon/src/mon/util/Dice.scala @@ -0,0 +1,11 @@ +package mon.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) = rng.nextInt(frac.denom) < frac.num +} \ No newline at end of file diff --git a/FakeMon/src/mon/util/Fraction.scala b/FakeMon/src/mon/util/Fraction.scala new file mode 100644 index 0000000..ffac8f7 --- /dev/null +++ b/FakeMon/src/mon/util/Fraction.scala @@ -0,0 +1,11 @@ +package mon.util + +case class Fraction(val num : Int, val denom : Int) { + def *(f : Fraction) = Fraction(num * f.num, denom * f.denom) + def *(x : Int) : Int = x * num / denom +} + +class IntFraction(val x : Int) extends AnyVal { + def %% = Fraction(x, 100) + def \ (denom : Int) = Fraction(x, denom) +} \ No newline at end of file diff --git a/FakeMon/src/mon/util/JsonHelper.scala b/FakeMon/src/mon/util/JsonHelper.scala new file mode 100644 index 0000000..1f823ea --- /dev/null +++ b/FakeMon/src/mon/util/JsonHelper.scala @@ -0,0 +1,52 @@ +package mon.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 = mon.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]]) + } +} \ No newline at end of file diff --git a/FakeMon/src/mon/util/YamlHelper.scala b/FakeMon/src/mon/util/YamlHelper.scala new file mode 100644 index 0000000..83d2187 --- /dev/null +++ b/FakeMon/src/mon/util/YamlHelper.scala @@ -0,0 +1,45 @@ +package mon.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 mon.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) + } +} \ No newline at end of file diff --git a/FakeMon/src/mon/util/package.scala b/FakeMon/src/mon/util/package.scala new file mode 100644 index 0000000..c239815 --- /dev/null +++ b/FakeMon/src/mon/util/package.scala @@ -0,0 +1,44 @@ +package mon + +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)) + } +} \ No newline at end of file