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()
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)

View File

@ -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!")
}
}
}
}
}
}

View File

@ -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) {

View File

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

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
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"

View File

@ -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)
}

View File

@ -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)
}