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()
|
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)
|
||||||
|
@ -26,25 +26,41 @@ class BattleEngine(val player : Party, val enemy : Party)(implicit val rng : Ran
|
|||||||
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,6 +69,7 @@ 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) {
|
||||||
@ -71,10 +88,11 @@ class BattleEngine(val player : Party, val enemy : Party)(implicit val rng : Ran
|
|||||||
// 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 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]) {
|
def applyBoosts(target: Monster, boosts: Map[Stat, Int]) {
|
||||||
boosts.foreach{case (s, b) => {
|
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!")
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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) {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
}
|
}
|
@ -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)
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user