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()
val form1 = Form("Diabolo")
val form2 = Form("Chanilla")
val movepool1 = IndexedSeq(Move("Aqua Jet"))
val movepool2 = IndexedSeq(Move("Thunder Wave"))
val movepool1 = IndexedSeq(Move("Confuse Ray"))
val movepool2 = IndexedSeq(Move("Headbutt"))
val p1 = TrainerID("Jaeda", Gender.Female, 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())

View File

@ -23,8 +23,8 @@ 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
queue.foreach(useMove)
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.canFight) {
val replace = player.pollReplacement(enemy)
@ -51,7 +51,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
val move = action.move
val target = action.target
if (user.isAlive) {
if (user.status.forall(_.onBeforeMove(user, move, target))) {
if (user.statuses.forall(_.onBeforeMove(user, move, target))) {
move.useMove(user, target)
}
}

View File

@ -16,8 +16,7 @@ case class Element(val name : String, val effect : Map[String, Double]) {
}
object Element {
private val elements = YamlHelper.extractSeq[Element](Element.getClass.getResourceAsStream("data/elements.yaml"))
private val fromName = elements.map(e => (e.name, e)).toMap
private val elements = YamlHelper.extractMap[Element](Element.getClass.getResourceAsStream("data/elements.yaml"))
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 status : Option[Status] = None
var volatile = Seq[Status]()
def isAlive = hp > 0
@ -36,11 +37,34 @@ class Monster(val base : StorageMon) {
hp = Math.min(stats(Hp), hp + healing)
}
def statuses = volatile ++ status
def cureStatus() {
status.map(_.onEnd(this))
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) {
val modified = boosts.getOrElse(s, 0) + boost
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 status: StatusTemplate
val drain: Fraction
val effect: Secondary
val selfEffect: Secondary
val secondary: Secondary
// boosts
@ -97,6 +98,9 @@ abstract class Move extends MoveTurn {
}
applyBoosts(target, boosts)
applyStatus(target, status)
if (effect != null) {
applyEffect(target, effect)
}
if (selfEffect != null) {
applyEffect(user, selfEffect)
}
@ -104,7 +108,6 @@ abstract class Move extends MoveTurn {
applyEffect(target, secondary)
}
// TODO : Support moves
// TODO : Multiparty
} else {
println("Missed!")
@ -155,18 +158,12 @@ abstract class Move extends MoveTurn {
def applyEffect(target: Monster, effect: Secondary)(implicit rng: Random) {
applyBoosts(target, effect.boosts)
applyStatus(target, effect.status)
applyStatus(target, effect.volatile)
}
def applyStatus(target: Monster, status: StatusTemplate)(implicit rng: Random) {
if (target.isAlive && status != null) {
if (target.status == None) {
// apply status
val s = status.build
target.status = Some(s)
s.onStart(target)
} else {
println("But it failed!")
}
target += status.build
}
}
@ -204,6 +201,7 @@ case class MoveToken(
val status: String,
val self: SecondaryToken,
val secondary: SecondaryToken,
val volatileStatus: String,
val drain: Fraction,
val recoil: Fraction,
@JsonScalaEnumeration(classOf[TargetType]) val target: Target = Target.Normal,
@ -212,6 +210,7 @@ case class MoveToken(
def instantiate() = {
val token = this
val effectToken = SecondaryToken(100, Map(), null, token.volatileStatus, null)
new Move {
val name = token.name
val desc = token.shortDesc
@ -228,6 +227,7 @@ case class MoveToken(
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.frac
val effect = effectToken.instantiate()
val selfEffect = if (token.self != null) token.self.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 boosts : Map[Stat, Int]
val status : StatusTemplate
// val volatileStatus
val volatile: StatusTemplate
//val onHit :
// val self
}
@ -13,7 +13,7 @@ case class SecondaryToken(
val chance : Int,
val boosts : Map[String, Int],
val status : String,
//val volatileStatus
val volatileStatus: String,
val onHit : String
) {
def instantiate() : Secondary = {
@ -22,6 +22,7 @@ case class SecondaryToken(
val chance = self.chance
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 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 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) {
def name = template.name
// val id
// val effectType
def effectType = template.effectType
def onStart(mon: Monster)(implicit rng: Random) = template.onStart(this, mon, rng)
def onEnd(mon: Monster) = template.onEnd(this, mon)
def onModifyStat(mon: Monster, stat: Stat) = template.onModifyStat(this, mon, stat)
@ -33,7 +46,7 @@ class Status(template: StatusTemplate) {
abstract class StatusTemplate {
val name: String
// val id
// val effectType
val effectType: EffectType
val onStart: (Status, Monster, Random) => Unit
val onEnd: (Status, Monster) => Unit
val onModifyStat: (Status, Monster, Stat) => Fraction
@ -52,6 +65,7 @@ abstract class StatusTemplate {
case class StatusToken(
val name: String,
val effectType: String,
val onStart: String,
val onEnd: String,
val onBeforeMove: String,
@ -62,6 +76,7 @@ case class StatusToken(
val self = this
new StatusTemplate {
val name = self.name
val effectType = EffectType.parse(self.effectType, Volatile)
val onStart = Status.compileOnStart(self.onStart)
val onEnd = Status.compileOnEnd(self.onEnd)
val onBeforeMove = Status.compileOnBeforeMove(self.onBeforeMove)
@ -74,7 +89,7 @@ case class StatusToken(
object Status {
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) = {
if (!statuses.contains(name)) {
@ -135,6 +150,7 @@ object Status {
import scala.util.Random
import fmon.stat._
import fmon.stat.Statistic._
import fmon.util._
def onBeforeMove(self: Status, mon: Monster, move: MoveTurn, target: Monster, rng: Random): Boolean = {
$code
}

View File

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

@ -112,6 +112,28 @@
type: "Fighting"
zMovePower: 190
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
num: 486
@ -165,6 +187,28 @@
zMovePower: 120
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
accuracy: 100
basePower: 90

View File

@ -1,4 +1,5 @@
- name: 'brn'
brn:
name: 'brn'
id: 'brn'
num: 0
effectType: 'Status'
@ -24,8 +25,54 @@
onResidual: |
mon.takeDamage(mon(Hp) / 16);
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'
num: 0
effectType: 'Status'
@ -55,7 +102,8 @@
true
}
- name: 'slp'
slp:
name: 'slp'
id: 'slp'
num: 0
effectType: 'Status'
@ -91,7 +139,21 @@
!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
id: frz
num: 0
@ -127,7 +189,8 @@
mon.cureStatus()
}
- name: 'psn'
psn:
name: 'psn'
id: 'psn'
num: 0
effectType: 'Status'
@ -147,7 +210,8 @@
mon.takeDamage(mon(Hp) / 8);
println(s"${mon} was damaged by poison!")
- name: 'tox'
tox:
name: 'tox'
id: 'tox'
num: 0
effectType: 'Status'

View File

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

View File

@ -42,4 +42,12 @@ object YamlHelper {
def extractSeq[T](source : InputStream)(implicit m : Manifest[T]) : IndexedSeq[T] = {
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)
}
}