diff --git a/FakeMon/src/mon/Game.scala b/FakeMon/src/mon/Game.scala index 5fbb53a..8c25829 100644 --- a/FakeMon/src/mon/Game.scala +++ b/FakeMon/src/mon/Game.scala @@ -21,8 +21,8 @@ object Game { 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 movepool1 = IndexedSeq(Move("Frost Breath")) + val movepool2 = IndexedSeq(Move("Absorb")) 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) diff --git a/FakeMon/src/mon/battle/BattleEngine.scala b/FakeMon/src/mon/battle/BattleEngine.scala index 8628c14..2a1ed9b 100644 --- a/FakeMon/src/mon/battle/BattleEngine.scala +++ b/FakeMon/src/mon/battle/BattleEngine.scala @@ -6,8 +6,8 @@ import mon.stat._ import mon.stat.Statistic._ import mon.util.Fraction -class BattleEngine(val player : Party, val enemy : Party)(implicit val rng : Random) { - +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) @@ -21,40 +21,57 @@ class BattleEngine(val player : Party, val enemy : Party)(implicit val rng : Ran println(s"${player.lead}(${player.lead.hp}/${player.lead(Hp)})") println(s"${enemy.lead}(${enemy.lead.hp}/${enemy.lead(Hp)})") } - - def useMove(action : Action) = { + + 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)) + if (user.isAlive) { + println(s"$user used $move.") + if (attackRoll(user, move, target)) { + if (move.pow > 0 || move.powCallback != null) { + val dmg = damageRoll(user, move, 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 (move.drain > 0.frac) { + val healing = Math.max(move.drain * actualDmg, 1) + user.recoverDamage(healing) + println(s"$user recovered $healing damage.") + } else if (move.drain < 0.frac) { + val recoil = Math.max(-move.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, move.boosts) + applyStatus(target, move.status) + // TODO : Secondary effects + if (move.selfEffect != null) { + applyEffect(user, move.selfEffect) + } + if (move.secondary != null && rng.chance(move.secondary.chance.%%)) { + applyEffect(target, move.secondary) } - } - // TODO : Support moves - // TODO : Multiparty - } else { - println("Missed!") + // TODO : Support moves + // TODO : Multiparty + } else { + println("Missed!") + } } } - - def attackRoll(user : Monster, move : Move, target : Monster) = { + + def attackRoll(user: Monster, move: Move, target: Monster) = { if (move.accuracy == 0) { true } else { @@ -64,17 +81,18 @@ class BattleEngine(val player : Party, val enemy : Party)(implicit val rng : Ran rng.chance(chance) } } - - def damageRoll(user : Monster, move : Move, target : Monster) = { + + 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) { + val effectiveness = target.effectiveness(move.element) + val multiplier = effectiveness * critMultiplier(user, move, target) + if (effectiveness > 1.0) { println("It's super effective!") - } else if (multiplier < 1.0) { + } else if (effectiveness < 1.0) { println("It's not very effective.") } val maxDmg = (baseDmg * multiplier).toInt @@ -82,14 +100,45 @@ class BattleEngine(val player : Party, val enemy : Party)(implicit val rng : Ran 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!") + def critMultiplier(user : Monster, move : Move, target: Monster) = { + // Percentage chance is different from Pokemon + val stage = move.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) { + if (status != null && target.status == None) { + // apply status + target.status = Some(status) + status.onStart.map(_(target)) } - }} + } + } + + 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!") + } + } + } + } } } \ No newline at end of file diff --git a/FakeMon/src/mon/stat/Monster.scala b/FakeMon/src/mon/stat/Monster.scala index 204af1f..144a055 100644 --- a/FakeMon/src/mon/stat/Monster.scala +++ b/FakeMon/src/mon/stat/Monster.scala @@ -26,8 +26,10 @@ class Monster(val base : StorageMon) { def elements = base.form.elements - def takeDamage(dmg : Int) { - hp = Math.max(0, hp - dmg) + def takeDamage(dmg : Int) : Int = { + val actualDmg = Math.min(hp, dmg) + hp -= actualDmg + actualDmg } def recoverDamage(healing : Int) { diff --git a/FakeMon/src/mon/stat/Move.scala b/FakeMon/src/mon/stat/Move.scala index dfd3130..9c28bd6 100644 --- a/FakeMon/src/mon/stat/Move.scala +++ b/FakeMon/src/mon/stat/Move.scala @@ -7,7 +7,7 @@ import com.fasterxml.jackson.module.scala.JsonScalaEnumeration import scala.io.Source -import mon.util.{TypeReference, YamlHelper} +import mon.util._ object MoveType extends Enumeration { val Physical, Special, Status = Value @@ -33,6 +33,8 @@ abstract class Move { val target : Target val boosts : Map[Stat, Int] val crit : Int + val status : Status + val drain : Fraction val selfEffect : Secondary val secondary: Secondary // boosts @@ -57,11 +59,14 @@ case class MoveToken( 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 crit : Int = 0) { + val critRatio : Int = 0) { def instantiate() = { val token = this @@ -78,7 +83,9 @@ case class MoveToken( 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 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 \ 1 val selfEffect = if (token.self != null) token.self.instantiate() else null val secondary = if (token.secondary != null) token.secondary.instantiate() else null } diff --git a/FakeMon/src/mon/stat/data/moves.yaml b/FakeMon/src/mon/stat/data/moves.yaml index 44e72d0..7de25df 100644 --- a/FakeMon/src/mon/stat/data/moves.yaml +++ b/FakeMon/src/mon/stat/data/moves.yaml @@ -1,3 +1,24 @@ +- 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 @@ -85,7 +106,7 @@ self: boosts: pdef: -1 - spd: -1 + mdef: -1 secondary: null target: "Normal" type: "Fighting" @@ -126,6 +147,24 @@ 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: "Poison Gas" num: 139 accuracy: 90 @@ -211,3 +250,24 @@ type: Normal zMovePower: 100 contestType: Tough + +- 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: 10 + xstatus: 'par' + target: "Normal" + type: "Electric" + zMovePower: 190 + contestType: "Cool" diff --git a/FakeMon/src/mon/util/Fraction.scala b/FakeMon/src/mon/util/Fraction.scala index ffac8f7..315b8d3 100644 --- a/FakeMon/src/mon/util/Fraction.scala +++ b/FakeMon/src/mon/util/Fraction.scala @@ -1,11 +1,18 @@ package mon.util -case class Fraction(val num : Int, val denom : Int) { +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) } \ No newline at end of file diff --git a/FakeMon/src/mon/util/package.scala b/FakeMon/src/mon/util/package.scala index c239815..a0284a6 100644 --- a/FakeMon/src/mon/util/package.scala +++ b/FakeMon/src/mon/util/package.scala @@ -41,4 +41,6 @@ package object util { implicit def filename2InputStream(filename : String) : BufferedInputStream = { new BufferedInputStream(new FileInputStream(filename)) } + + implicit def int2Helper(x : Int) = new IntFraction(x) } \ No newline at end of file