Added recoil, drain, and critical hits
This commit is contained in:
parent
83e6bb7a3c
commit
9e376c4e52
@ -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)
|
||||
|
@ -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!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user