Added support for volatile status effects including confusion and flinching. Also, all elements are now supported.

This commit is contained in:
James Daly 2019-06-03 22:46:09 -04:00
parent 34cd67c36f
commit ce81ab2079
12 changed files with 355 additions and 155 deletions

View File

@ -18,8 +18,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("Aqua Jet")) val movepool1 = IndexedSeq(Move("Confuse Ray"))
val movepool2 = IndexedSeq(Move("Thunder Wave")) val movepool2 = IndexedSeq(Move("Headbutt"))
val p1 = TrainerID("Jaeda", Gender.Female, 0) val p1 = TrainerID("Jaeda", Gender.Female, 0)
val p2 = TrainerID("Wild Monster", Gender.Male, 0) val p2 = TrainerID("Wild Monster", Gender.Male, 0)
val party1 = new Party(p1, new MonsterPtr(new Monster(new StorageMon("Allied Mon", 500, Gene.randomGene(null, form1), form1, Statistic.emptyEvs, movepool1))), IndexedSeq()) val party1 = new Party(p1, new MonsterPtr(new Monster(new StorageMon("Allied Mon", 500, Gene.randomGene(null, form1), form1, Statistic.emptyEvs, movepool1))), IndexedSeq())

View File

@ -23,7 +23,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
val queue = rng.shuffle(actions).sorted // Shuffle to randomize in the event of a tie val queue = rng.shuffle(actions).sorted // Shuffle to randomize in the event of a tie
queue.foreach(useMove) queue.foreach(useMove)
val eotQueue = Seq(player.lead, enemy.lead).sortBy(_(Speed)) val eotQueue = Seq(player.lead, enemy.lead).sortBy(_(Speed))
eotQueue.foreach(mon => mon.status.map(_.onResidual(mon))) eotQueue.foreach(mon => mon.statuses.map(_.onResidual(mon)))
if (!player.lead.isAlive) { if (!player.lead.isAlive) {
if (player.canFight) { if (player.canFight) {
@ -51,7 +51,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
val move = action.move val move = action.move
val target = action.target val target = action.target
if (user.isAlive) { if (user.isAlive) {
if (user.status.forall(_.onBeforeMove(user, move, target))) { if (user.statuses.forall(_.onBeforeMove(user, move, target))) {
move.useMove(user, target) move.useMove(user, target)
} }
} }

View File

@ -16,8 +16,7 @@ case class Element(val name : String, val effect : Map[String, Double]) {
} }
object Element { object Element {
private val elements = YamlHelper.extractSeq[Element](Element.getClass.getResourceAsStream("data/elements.yaml")) private val elements = YamlHelper.extractMap[Element](Element.getClass.getResourceAsStream("data/elements.yaml"))
private val fromName = elements.map(e => (e.name, e)).toMap
def apply(name : String) = fromName(name) def apply(name : String) = elements(name)
} }

View File

@ -14,6 +14,7 @@ class Monster(val base : StorageMon) {
var hp = stats(Hp) var hp = stats(Hp)
var status : Option[Status] = None var status : Option[Status] = None
var volatile = Seq[Status]()
def isAlive = hp > 0 def isAlive = hp > 0
@ -36,11 +37,34 @@ class Monster(val base : StorageMon) {
hp = Math.min(stats(Hp), hp + healing) hp = Math.min(stats(Hp), hp + healing)
} }
def statuses = volatile ++ status
def cureStatus() { def cureStatus() {
status.map(_.onEnd(this)) status.map(_.onEnd(this))
status = None status = None
} }
def +=(s : Status)(implicit rng: Random) = {
if (s.effectType == EffectType.Volatile && !volatile.exists(_.name == s.name)) {
volatile +:= s
s.onStart(this)
} else if (s.effectType == EffectType.NonVolatile && status == None) {
status = Some(s)
s.onStart(this)
} else {
println("But it failed!")
}
}
def -=(s : Status) = {
if (s.effectType == EffectType.Volatile) {
s.onEnd(this)
volatile = volatile.filterNot(_.name == s.name)
} else {
cureStatus()
}
}
def applyBoost(s : Stat, boost : Int) { def applyBoost(s : Stat, boost : Int) {
val modified = boosts.getOrElse(s, 0) + boost val modified = boosts.getOrElse(s, 0) + boost
boosts = boosts.updated(s, Math.min(MaxBoost, Math.max(-MaxBoost, modified))) boosts = boosts.updated(s, Math.min(MaxBoost, Math.max(-MaxBoost, modified)))

View File

@ -60,6 +60,7 @@ abstract class Move extends MoveTurn {
val crit: Int val crit: Int
val status: StatusTemplate val status: StatusTemplate
val drain: Fraction val drain: Fraction
val effect: Secondary
val selfEffect: Secondary val selfEffect: Secondary
val secondary: Secondary val secondary: Secondary
// boosts // boosts
@ -97,6 +98,9 @@ abstract class Move extends MoveTurn {
} }
applyBoosts(target, boosts) applyBoosts(target, boosts)
applyStatus(target, status) applyStatus(target, status)
if (effect != null) {
applyEffect(target, effect)
}
if (selfEffect != null) { if (selfEffect != null) {
applyEffect(user, selfEffect) applyEffect(user, selfEffect)
} }
@ -104,7 +108,6 @@ abstract class Move extends MoveTurn {
applyEffect(target, secondary) applyEffect(target, secondary)
} }
// TODO : Support moves
// TODO : Multiparty // TODO : Multiparty
} else { } else {
println("Missed!") println("Missed!")
@ -155,18 +158,12 @@ abstract class Move extends MoveTurn {
def applyEffect(target: Monster, effect: Secondary)(implicit rng: Random) { def applyEffect(target: Monster, effect: Secondary)(implicit rng: Random) {
applyBoosts(target, effect.boosts) applyBoosts(target, effect.boosts)
applyStatus(target, effect.status) applyStatus(target, effect.status)
applyStatus(target, effect.volatile)
} }
def applyStatus(target: Monster, status: StatusTemplate)(implicit rng: Random) { def applyStatus(target: Monster, status: StatusTemplate)(implicit rng: Random) {
if (target.isAlive && status != null) { if (target.isAlive && status != null) {
if (target.status == None) { target += status.build
// apply status
val s = status.build
target.status = Some(s)
s.onStart(target)
} else {
println("But it failed!")
}
} }
} }
@ -204,6 +201,7 @@ case class MoveToken(
val status: String, val status: String,
val self: SecondaryToken, val self: SecondaryToken,
val secondary: SecondaryToken, val secondary: SecondaryToken,
val volatileStatus: String,
val drain: Fraction, val drain: Fraction,
val recoil: Fraction, val recoil: Fraction,
@JsonScalaEnumeration(classOf[TargetType]) val target: Target = Target.Normal, @JsonScalaEnumeration(classOf[TargetType]) val target: Target = Target.Normal,
@ -212,6 +210,7 @@ case class MoveToken(
def instantiate() = { def instantiate() = {
val token = this val token = this
val effectToken = SecondaryToken(100, Map(), null, token.volatileStatus, null)
new Move { new Move {
val name = token.name val name = token.name
val desc = token.shortDesc val desc = token.shortDesc
@ -228,6 +227,7 @@ case class MoveToken(
val crit = token.critRatio val crit = token.critRatio
val status = if (token.status != null) Status(token.status) else null 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.frac val drain = if (token.drain != null) token.drain else if (token.recoil != null) -token.recoil else 0.frac
val effect = effectToken.instantiate()
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

@ -4,7 +4,7 @@ abstract class Secondary {
val chance : Int val chance : Int
val boosts : Map[Stat, Int] val boosts : Map[Stat, Int]
val status : StatusTemplate val status : StatusTemplate
// val volatileStatus val volatile: StatusTemplate
//val onHit : //val onHit :
// val self // val self
} }
@ -13,7 +13,7 @@ case class SecondaryToken(
val chance : Int, val chance : Int,
val boosts : Map[String, Int], val boosts : Map[String, Int],
val status : String, val status : String,
//val volatileStatus val volatileStatus: String,
val onHit : String val onHit : String
) { ) {
def instantiate() : Secondary = { def instantiate() : Secondary = {
@ -22,6 +22,7 @@ case class SecondaryToken(
val chance = self.chance val chance = self.chance
val boosts = if (self.boosts != null) self.boosts.map{case (s, i) => (Statistic(s), i)} else Map() val boosts = if (self.boosts != null) self.boosts.map{case (s, i) => (Statistic(s), i)} else Map()
val status = if (self.status != null) Status(self.status) else null val status = if (self.status != null) Status(self.status) else null
val volatile = if (self.volatileStatus != null) Status(self.volatileStatus) else null
} }
} }
} }

View File

@ -8,11 +8,24 @@ import scala.util.Random
import scala.io.Source import scala.io.Source
import fmon.util._ import fmon.util._
import EffectType.Volatile
object EffectType extends Enumeration {
val NonVolatile, Volatile, Field, Weather = Value
def parse(s : String, default : EffectType) = s match {
case "status" => NonVolatile
case "volatile" => Volatile
case "weather" => Weather
case _ => default
}
}
class Status(template: StatusTemplate) { class Status(template: StatusTemplate) {
def name = template.name def name = template.name
// val id // val id
// val effectType def effectType = template.effectType
def onStart(mon: Monster)(implicit rng: Random) = template.onStart(this, mon, rng) def onStart(mon: Monster)(implicit rng: Random) = template.onStart(this, mon, rng)
def onEnd(mon: Monster) = template.onEnd(this, mon) def onEnd(mon: Monster) = template.onEnd(this, mon)
def onModifyStat(mon: Monster, stat: Stat) = template.onModifyStat(this, mon, stat) def onModifyStat(mon: Monster, stat: Stat) = template.onModifyStat(this, mon, stat)
@ -33,7 +46,7 @@ class Status(template: StatusTemplate) {
abstract class StatusTemplate { abstract class StatusTemplate {
val name: String val name: String
// val id // val id
// val effectType val effectType: EffectType
val onStart: (Status, Monster, Random) => Unit val onStart: (Status, Monster, Random) => Unit
val onEnd: (Status, Monster) => Unit val onEnd: (Status, Monster) => Unit
val onModifyStat: (Status, Monster, Stat) => Fraction val onModifyStat: (Status, Monster, Stat) => Fraction
@ -52,6 +65,7 @@ abstract class StatusTemplate {
case class StatusToken( case class StatusToken(
val name: String, val name: String,
val effectType: String,
val onStart: String, val onStart: String,
val onEnd: String, val onEnd: String,
val onBeforeMove: String, val onBeforeMove: String,
@ -62,6 +76,7 @@ case class StatusToken(
val self = this val self = this
new StatusTemplate { new StatusTemplate {
val name = self.name val name = self.name
val effectType = EffectType.parse(self.effectType, Volatile)
val onStart = Status.compileOnStart(self.onStart) val onStart = Status.compileOnStart(self.onStart)
val onEnd = Status.compileOnEnd(self.onEnd) val onEnd = Status.compileOnEnd(self.onEnd)
val onBeforeMove = Status.compileOnBeforeMove(self.onBeforeMove) val onBeforeMove = Status.compileOnBeforeMove(self.onBeforeMove)
@ -74,7 +89,7 @@ case class StatusToken(
object Status { object Status {
private var statuses = Map[String, StatusTemplate]() private var statuses = Map[String, StatusTemplate]()
val tokens = YamlHelper.extractSeq[StatusToken](Source.fromInputStream(Move.getClass.getResourceAsStream("data/statuses.yaml"))).map(t => (t.name, t)).toMap val tokens = YamlHelper.extractMap[StatusToken](Source.fromInputStream(Status.getClass.getResourceAsStream("data/statuses.yaml")))
def apply(name: String) = { def apply(name: String) = {
if (!statuses.contains(name)) { if (!statuses.contains(name)) {
@ -135,6 +150,7 @@ object Status {
import scala.util.Random import scala.util.Random
import fmon.stat._ import fmon.stat._
import fmon.stat.Statistic._ import fmon.stat.Statistic._
import fmon.util._
def onBeforeMove(self: Status, mon: Monster, move: MoveTurn, target: Monster, rng: Random): Boolean = { def onBeforeMove(self: Status, mon: Monster, move: MoveTurn, target: Monster, rng: Random): Boolean = {
$code $code
} }

View File

@ -1,131 +1,174 @@
[ Bug:
{ effect:
name: Normal, Dark: 2.0
effect: { Fairy: 0.5
Rock: 0.5, Fighting: 0.5
Steel: 0.5, Fire: 0.5
Ghost: 0 Flying: 0.5
} Ghost: 0.5
}, Grass: 2.0
{
name: Fire,
effect: {
Fire: 0.5,
Water: 0.5,
Rock: 0.5,
Dragon: 0.5,
Bug: 2.0,
Grass: 2.0,
Ice: 2.0,
Steel: 2
}
},
{
name: Grass,
effect: {
Bug: 0.5,
Dragon: 0.5,
Fire: 0.5,
Flying: 0.5,
Water: 2.0,
Ground: 2.0,
Rock: 2.0,
Grass: 0.5,
Poison: 0.5 Poison: 0.5
} Psychic: 2.0
}, Steel: 0.5
{ name: Bug
name: Water, Dark:
effect: { effect:
Fire: 2.0, Dark: 0.5
Ground: 2.0, Fairy: 0.5
Rock: 2.0, Fighting: 0.5
Water: 0.5, Ghost: 2.0
Grass: 0.5, Psychic: 2.0
name: Dark
Dragon:
effect:
Dragon: 2.0
Fairy: 0.0
Steel: 0.5
name: Dragon
Electric:
effect:
Dragon: 0.5 Dragon: 0.5
} Electric: 0.5
}, Flying: 2.0
{ Grass: 0.5
name: Electric, Ground: 0.0
effect: { Water: 2.0
Flying: 2.0, name: Electric
Water: 2.0, Fairy:
Dragon: 0.5, effect:
Electric: 0.5, Dark: 2.0
Grass: 0.5, Dragon: 2.0
Ground: 0 Fighting: 2.0
} Fire: 0.5
}, Poison: 0.5
{
name: Ice,
effect: {
Dragon: 2.0,
Flying: 2.0,
Grass: 2.0,
Ground: 2.0,
Fire: 0.5,
Ice: 0.5,
Water: 0.5,
Steel: 0.5 Steel: 0.5
} name: Fairy
}, Fighting:
{ effect:
name: Psychic, Bug: 0.5
effect: { Dark: 2.0
Fighting: 2.0, Fairy: 0.5
Poison: 2.0, Flying: 0.5
Psychic: 0.5,
Steel: 0.5,
Dark: 0
}
},
{
name: Dark,
effect: {
Ghost: 2.0,
Psychic: 2.0,
Dark: 0.5,
Fairy: 0.5,
Fight: 0.5
}
},
{
name: Fairy,
effect: {
Dark: 2.0,
Dragon: 2.0,
Fighting: 2.0,
Fire: 0.5,
Poison: 0.5,
Steel: 0.5
}
},
{
name: Fighting,
effect: {
Dark: 2.0,
Ice: 2.0,
Normal: 2.0,
Rock: 2.0,
Steel: 2.0,
Bug: 0.5,
Fairy: 0.5,
Flying: 0.5,
Poison: 0.5,
Psychic: 0.5,
Ghost: 0.0 Ghost: 0.0
} Ice: 2.0
}, Normal: 2.0
{ Poison: 0.5
name: Poison, Psychic: 0.5
effect: { Rock: 2.0
Fairy: 2.0, Steel: 2.0
Grass: 2.0, name: Fighting
Poison: 0.5, Fire:
Ground: 0.5, effect:
Rock: 0.5, Bug: 2.0
Ghost: 0.5, Dragon: 0.5
Fire: 0.5
Grass: 2.0
Ice: 2.0
Rock: 0.5
Steel: 2.0
Water: 0.5
name: Fire
Flying:
effect:
Bug: 2.0
Electric: 0.5
Fighting: 2.0
Grass: 2.0
Rock: 0.5
Steel: 0.5
name: Flying
Ghost:
effect:
Dark: 0.5
Ghost: 2.0
Normal: 0.0
Psychic: 2.0
name: Ghost
Grass:
effect:
Bug: 0.5
Dragon: 0.5
Fire: 0.5
Flying: 0.5
Grass: 0.5
Ground: 2.0
Poison: 0.5
Rock: 2.0
Steel: 0.5
Water: 2.0
name: Grass
Ground:
effect:
Bug: 0.5
Electric: 2.0
Fire: 2.0
Flying: 0.0
Grass: 0.5
Poison: 2.0
Rock: 2.0
Steel: 2.0
name: Ground
Ice:
effect:
Dragon: 2.0
Fire: 0.5
Flying: 2.0
Grass: 2.0
Ground: 2.0
Ice: 0.5
Steel: 0.5
Water: 0.5
name: Ice
Normal:
effect:
Ghost: 0.0
Rock: 0.5
Steel: 0.5
name: Normal
Poison:
effect:
Fairy: 2.0
Ghost: 0.5
Grass: 2.0
Ground: 0.5
Poison: 0.5
Rock: 0.5
Steel: 0.0 Steel: 0.0
} name: Poison
} Psychic:
] effect:
Dark: 0.0
Fighting: 2.0
Poison: 2.0
Psychic: 0.5
Steel: 0.5
name: Psychic
Rock:
effect:
Bug: 2.0
Fighting: 0.5
Fire: 2.0
Flying: 2.0
Ground: 0.5
Ice: 2.0
Steel: 0.5
name: Rock
Steel:
effect:
Electric: 0.5
Fairy: 2.0
Fire: 0.5
Ice: 2.0
Rock: 2.0
Steel: 0.5
Water: 0.5
name: Steel
Water:
effect:
Dragon: 0.5
Fire: 2.0
Grass: 0.5
Ground: 2.0
Rock: 2.0
Water: 0.5
name: Water

View File

@ -113,6 +113,28 @@
zMovePower: 190 zMovePower: 190
contestType: "Tough" contestType: "Tough"
- name: Confuse Ray
accuracy: 100
basePower: 0
category: Status
contestType: Clever
desc: Causes the target to become confused.
flags:
mirror: 1
protect: 1
reflectable: 1
id: confuseray
num: 109
pp: 10
priority: 0
secondary: null
shortDesc: Confuses the target.
target: Normal
type: Ghost
volatileStatus: confusion
zMoveBoost:
MAtk: 1
- name: Electro Ball - name: Electro Ball
num: 486 num: 486
accuracy: 100 accuracy: 100
@ -165,6 +187,28 @@
zMovePower: 120 zMovePower: 120
contestType: "Beautiful" contestType: "Beautiful"
- name: Headbutt
accuracy: 100
basePower: 70
category: Physical
contestType: Tough
desc: Has a 30% chance to flinch the target.
flags:
contact: 1
mirror: 1
protect: 1
id: headbutt
num: 29
pp: 15
priority: 0
secondary:
chance: 30
volatileStatus: flinch
shortDesc: 30% chance to flinch the target.
target: Normal
type: Normal
zMovePower: 140
- name: Ice Beam - name: Ice Beam
accuracy: 100 accuracy: 100
basePower: 90 basePower: 90

View File

@ -1,4 +1,5 @@
- name: 'brn' brn:
name: 'brn'
id: 'brn' id: 'brn'
num: 0 num: 0
effectType: 'Status' effectType: 'Status'
@ -25,7 +26,53 @@
mon.takeDamage(mon(Hp) / 16); mon.takeDamage(mon(Hp) / 16);
println(s"${mon} was hurt by its burn!") println(s"${mon} was hurt by its burn!")
- name: 'par' confusion:
id: confusion
name: confusion
num: 0
onBeforeMove: |-
self.intData("time") -= 1
if (self.intData("time") <= 0) {
mon -= self
true
} else {
println(s"${mon} is confused!")
if (rng.chance(1, 3)) {
// confusion damage
val maxDmg = (2 * mon.level / 5 + 2) * 40 * mon(PAtk) / (mon(PDef) * 50) + 2
val minDmg = (17 \\ 20) * maxDmg
val dmg = rng.nextInt(minDmg, maxDmg)
println(s"${mon} hurt itself in its confusion.")
mon.takeDamage(dmg)
false
} else {
true
}
}
/*
this.activeTarget = mon;
let damage = this.getDamage(mon, mon, 40);
if (typeof damage !== 'number') throw new Error('Confusion damage not dealt');
mon.takeDamage(damage, mon, mon, /** @type {ActiveMove} */ ({
'id': 'confused',
'effectType': 'Move',
'type': '???'
}));
*/
onBeforeMovePriority: 3
onEnd: println(s"${mon} snapped out of its confusion.")
onStart: |-
println(s"${mon} was confused!")
/*
if (sourceEffect && sourceEffect.id === 'lockedmove') {
this.add('-start', target, 'confusion', '[fatigue]');
} else {
this.add('-start', target, 'confusion');
}*/
self.intData("time") = rng.nextInt(2, 6);
par:
name: 'par'
id: 'par' id: 'par'
num: 0 num: 0
effectType: 'Status' effectType: 'Status'
@ -55,7 +102,8 @@
true true
} }
- name: 'slp' slp:
name: 'slp'
id: 'slp' id: 'slp'
num: 0 num: 0
effectType: 'Status' effectType: 'Status'
@ -91,7 +139,21 @@
!move.flags("sleepUsable") !move.flags("sleepUsable")
} }
- name: 'frz' flinch:
duration: 1
id: flinch
name: flinch
num: 0
onBeforeMove: |-
println(s"${mon} flinched!")
false
onBeforeMovePriority: 8
onResidualOrder: 13
onResidual: |
mon -= self
frz:
name: 'frz'
effectType: Status effectType: Status
id: frz id: frz
num: 0 num: 0
@ -127,7 +189,8 @@
mon.cureStatus() mon.cureStatus()
} }
- name: 'psn' psn:
name: 'psn'
id: 'psn' id: 'psn'
num: 0 num: 0
effectType: 'Status' effectType: 'Status'
@ -147,7 +210,8 @@
mon.takeDamage(mon(Hp) / 8); mon.takeDamage(mon(Hp) / 8);
println(s"${mon} was damaged by poison!") println(s"${mon} was damaged by poison!")
- name: 'tox' tox:
name: 'tox'
id: 'tox' id: 'tox'
num: 0 num: 0
effectType: 'Status' effectType: 'Status'

View File

@ -11,6 +11,7 @@ import fmon.util.Dice
package object stat { package object stat {
type Stat = Statistic.Value type Stat = Statistic.Value
type EffectType = EffectType.Value
type MoveType = MoveType.Value type MoveType = MoveType.Value
type Gender = Gender.Value type Gender = Gender.Value
type Nature = Nature.Val type Nature = Nature.Val

View File

@ -42,4 +42,12 @@ object YamlHelper {
def extractSeq[T](source : InputStream)(implicit m : Manifest[T]) : IndexedSeq[T] = { def extractSeq[T](source : InputStream)(implicit m : Manifest[T]) : IndexedSeq[T] = {
mapper.readValue[IndexedSeq[T]](source) mapper.readValue[IndexedSeq[T]](source)
} }
def extractMap[T](source : Source)(implicit mf : Manifest[T]) : Map[String, T] = {
extract[Map[String, T]](source)
}
def extractMap[T](source : InputStream)(implicit m: Manifest[T]) : Map[String, T] = {
mapper.readValue[Map[String, T]](source)
}
} }