Added recoil, drain, and critical hits

This commit is contained in:
James Daly 2019-05-27 22:48:10 -04:00
parent 83e6bb7a3c
commit 9e376c4e52
7 changed files with 178 additions and 51 deletions

View File

@ -21,8 +21,8 @@ object Game {
implicit val rng = new scala.util.Random() implicit val rng = new scala.util.Random()
val form1 = Form("Diabolo") val form1 = Form("Diabolo")
val form2 = Form("Chanilla") val form2 = Form("Chanilla")
val movepool1 = IndexedSeq(Move("Poison Sting")) val movepool1 = IndexedSeq(Move("Frost Breath"))
val movepool2 = IndexedSeq(Move("Poison Sting")) 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 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 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) val engine = new BattleEngine(party1, party2)

View File

@ -6,7 +6,7 @@ import mon.stat._
import mon.stat.Statistic._ import mon.stat.Statistic._
import mon.util.Fraction 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() = { def playTurn() = {
val playerMove = player.pollAction val playerMove = player.pollAction
@ -22,29 +22,45 @@ class BattleEngine(val player : Party, val enemy : Party)(implicit val rng : Ran
println(s"${enemy.lead}(${enemy.lead.hp}/${enemy.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 user = action.user
val move = action.move val move = action.move
val target = action.target val target = action.target
if (user.isAlive) {
println(s"$user used $move.") println(s"$user used $move.")
if (attackRoll(user, move, target)) { if (attackRoll(user, move, target)) {
if (move.pow > 0 || move.powCallback != null) { if (move.pow > 0 || move.powCallback != null) {
val dmg = damageRoll(user, move, target) val dmg = damageRoll(user, move, target)
target.takeDamage(dmg) val actualDmg = target.takeDamage(dmg)
println(s"$target takes $dmg damage!") 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) applyBoosts(target, move.boosts)
applyStatus(target, move.status)
// TODO : Secondary effects // TODO : Secondary effects
if (move.selfEffect != null) { if (move.selfEffect != null) {
applyBoosts(user, move.selfEffect.boosts) applyEffect(user, move.selfEffect)
} }
if (move.secondary != null && rng.chance(move.secondary.chance.%%)) { if (move.secondary != null && rng.chance(move.secondary.chance.%%)) {
applyBoosts(target, move.secondary.boosts) applyEffect(target, move.secondary)
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 : Support moves
@ -53,8 +69,9 @@ class BattleEngine(val player : Party, val enemy : Party)(implicit val rng : Ran
println("Missed!") println("Missed!")
} }
} }
}
def attackRoll(user : Monster, move : Move, target : Monster) = { def attackRoll(user: Monster, move: Move, target: Monster) = {
if (move.accuracy == 0) { if (move.accuracy == 0) {
true true
} else { } else {
@ -65,16 +82,17 @@ class BattleEngine(val player : Party, val enemy : Party)(implicit val rng : Ran
} }
} }
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 atkStat = if (move.mvType == MoveType.Physical) PAtk else MAtk
val defStat = if (move.mvType == MoveType.Physical) PDef else MDef val defStat = if (move.mvType == MoveType.Physical) PDef else MDef
// TODO : Fixed damage // TODO : Fixed damage
val pow = if (move.powCallback != null) move.powCallback(user, target) else move.pow 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 baseDmg = (2 * user.level / 5 + 2) * pow * user(atkStat) / (target(defStat) * 50) + 2
val multiplier = target.effectiveness(move.element) val effectiveness = target.effectiveness(move.element)
if (multiplier > 1.0) { val multiplier = effectiveness * critMultiplier(user, move, target)
if (effectiveness > 1.0) {
println("It's super effective!") println("It's super effective!")
} else if (multiplier < 1.0) { } else if (effectiveness < 1.0) {
println("It's not very effective.") println("It's not very effective.")
} }
val maxDmg = (baseDmg * multiplier).toInt 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) rng.nextInt(minDmg, maxDmg)
} }
def applyBoosts(target : Monster, boosts : Map[Stat, Int]) { def critMultiplier(user : Monster, move : Move, target: Monster) = {
boosts.foreach{case (s, b) => { // 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) target.applyBoost(s, b)
if (b > 0) { if (b > 0) {
println(s"$target's $s rose!") println(s"$target's $s rose!")
} else if (b < 0) { } else if (b < 0) {
println(s"$target's $s fell!") println(s"$target's $s fell!")
} }
}} }
}
}
} }
} }

View File

@ -26,8 +26,10 @@ class Monster(val base : StorageMon) {
def elements = base.form.elements def elements = base.form.elements
def takeDamage(dmg : Int) { def takeDamage(dmg : Int) : Int = {
hp = Math.max(0, hp - dmg) val actualDmg = Math.min(hp, dmg)
hp -= actualDmg
actualDmg
} }
def recoverDamage(healing : Int) { def recoverDamage(healing : Int) {

View File

@ -7,7 +7,7 @@ import com.fasterxml.jackson.module.scala.JsonScalaEnumeration
import scala.io.Source import scala.io.Source
import mon.util.{TypeReference, YamlHelper} import mon.util._
object MoveType extends Enumeration { object MoveType extends Enumeration {
val Physical, Special, Status = Value val Physical, Special, Status = Value
@ -33,6 +33,8 @@ abstract class Move {
val target : Target val target : Target
val boosts : Map[Stat, Int] val boosts : Map[Stat, Int]
val crit : Int val crit : Int
val status : Status
val drain : Fraction
val selfEffect : Secondary val selfEffect : Secondary
val secondary: Secondary val secondary: Secondary
// boosts // boosts
@ -57,11 +59,14 @@ case class MoveToken(
val pp : Int, val pp : Int,
val `type` : String, val `type` : String,
val flags : Map[String, Int], val flags : Map[String, Int],
val status : String,
val self : SecondaryToken, val self : SecondaryToken,
val secondary : SecondaryToken, val secondary : SecondaryToken,
val drain : Fraction,
val recoil : Fraction,
@JsonScalaEnumeration(classOf[TargetType]) val target : Target = Target.Normal, @JsonScalaEnumeration(classOf[TargetType]) val target : Target = Target.Normal,
val boosts : Map[String, Int] = Map(), val boosts : Map[String, Int] = Map(),
val crit : Int = 0) { val critRatio : Int = 0) {
def instantiate() = { def instantiate() = {
val token = this val token = this
@ -78,7 +83,9 @@ case class MoveToken(
val flags = token.flags.keySet val flags = token.flags.keySet
val target = token.target val target = token.target
val boosts = if (token.boosts != null) token.boosts.map{case (s, i) => (Statistic(s), i)} else Map() 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 selfEffect = if (token.self != null) token.self.instantiate() else null
val secondary = if (token.secondary != null) token.secondary.instantiate() else null val secondary = if (token.secondary != null) token.secondary.instantiate() else null
} }

View File

@ -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 - name: Aqua Jet
num: 453 num: 453
accuracy: 100 accuracy: 100
@ -85,7 +106,7 @@
self: self:
boosts: boosts:
pdef: -1 pdef: -1
spd: -1 mdef: -1
secondary: null secondary: null
target: "Normal" target: "Normal"
type: "Fighting" type: "Fighting"
@ -126,6 +147,24 @@
zMovePower: 160 zMovePower: 160
contestType: "Cool" 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" - name: "Poison Gas"
num: 139 num: 139
accuracy: 90 accuracy: 90
@ -211,3 +250,24 @@
type: Normal type: Normal
zMovePower: 100 zMovePower: 100
contestType: Tough 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"

View File

@ -1,11 +1,18 @@
package mon.util 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 *(f : Fraction) = Fraction(num * f.num, denom * f.denom)
def *(x : Int) : Int = x * num / 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 { class IntFraction(val x : Int) extends AnyVal {
def %% = Fraction(x, 100) def %% = Fraction(x, 100)
def \ (denom : Int) = Fraction(x, denom) def \ (denom : Int) = Fraction(x, denom)
def frac = Fraction(x, 1)
} }

View File

@ -41,4 +41,6 @@ package object util {
implicit def filename2InputStream(filename : String) : BufferedInputStream = { implicit def filename2InputStream(filename : String) : BufferedInputStream = {
new BufferedInputStream(new FileInputStream(filename)) new BufferedInputStream(new FileInputStream(filename))
} }
implicit def int2Helper(x : Int) = new IntFraction(x)
} }