Added support for abilities including a bunch of abilities that have effects the the monster is hit. Better monster generation support has been added.

This commit is contained in:
James Daly 2019-06-05 21:31:25 -04:00
parent ce81ab2079
commit ad6780362b
12 changed files with 351 additions and 73 deletions

View File

@ -18,13 +18,13 @@ object Game {
implicit val rng = new scala.util.Random()
val form1 = Form("Diabolo")
val form2 = Form("Chanilla")
val movepool1 = IndexedSeq(Move("Confuse Ray"))
val movepool2 = IndexedSeq(Move("Headbutt"))
val movepool1 = IndexedSeq(Move("electroball"), Move("thunderwave"))
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())
val party2 = new Party(p2, new MonsterPtr(new Monster(new StorageMon("Wild Mon", 500, Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2))), IndexedSeq(
new Monster(new StorageMon("Sideboard Mon", 500, Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2))
val party1 = new Party(p1, new MonsterPtr(Monster.generate("Allied Mon", p1, 500, form1, movepool1)), IndexedSeq())
val party2 = new Party(p2, new MonsterPtr(Monster.generate("Wild Mon", p2, 500, form2, movepool2)), IndexedSeq(
Monster.generate("Sideboard Mon", p2, 500, form2, movepool2)
))
println(form1.xpCurve)
val engine = new BattleEngine(party1, party2)

View File

@ -0,0 +1,76 @@
package fmon.stat
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
import scala.io.Source
import scala.util.Random
import fmon.util._
abstract class Ability {
val name: String
val desc: String
def onAfterDamage(source: Monster, move: MoveTurn, mon: Monster, dmg: Int)(implicit rng: Random) = onAfterDamageImp(source, move, mon, dmg, rng)
protected val onAfterDamageImp: (Monster, MoveTurn, Monster, Int, Random) => Unit
override def toString = name
}
class AbilityToken(
val name: String,
val shortDesc: String,
val onAfterDamage: String
) {
def instantiate(): Ability = {
val self = this
new Ability {
val name = self.name
val desc = self.shortDesc
val onAfterDamageImp = Ability.compileOnAfterDamage(self.onAfterDamage)
}
}
}
object Ability {
private var abilities = Map[String, Ability]()
val tokens = YamlHelper.extractMap[AbilityToken](Source.fromInputStream(Ability.getClass.getResourceAsStream("data/abilities.yaml")))
private val tb = runtimeMirror(getClass.getClassLoader).mkToolBox()
def apply(name: String) = {
if (!abilities.contains(name)) {
abilities = abilities.updated(name, tokens(name).instantiate())
}
abilities(name)
}
private val header = """
|import scala.util.Random
|import fmon.stat._
|import fmon.stat.MoveType._
|import fmon.stat.Statistic._
|import fmon.util._
"""
def compileOnAfterDamage(code: String): (Monster, MoveTurn, Monster, Int, Random) => Unit = {
if (code == null) {
(_, _, _, _, _) => ()
} else {
val tree = tb.parse(
s"""
|$header
|def onAfterDamage(source: Monster, move: MoveTurn, mon: Monster, damage: Int, rng: Random): Unit = {
| implicit val gen = rng
| $code
|}
|onAfterDamage _
""".stripMargin)
val f = tb.compile(tree)
val wrapper = f()
implicit val c = code
wrapper.asInstanceOf[(Monster, MoveTurn, Monster, Int, Random) => Unit]
}
}
}

View File

@ -17,7 +17,7 @@ abstract class Form {
val baseStats : Map[Stat, Int]
// val appearance // animation
// moves
// abilities
val abilities : IndexedSeq[Ability]
val xpCurve : XpCurve
val catchRate : Int
// val color
@ -30,6 +30,7 @@ abstract class Form {
case class FormToken(
val name : String,
val desc : String,
val abilities : IndexedSeq[String],
val elements : IndexedSeq[String],
val baseStats : Map[String, Int],
@JsonScalaEnumeration(classOf[XpCurveType]) val xpCurve : XpCurve,
@ -43,6 +44,7 @@ case class FormToken(
val desc = self.desc
val elements = self.elements.map(Element(_))
val baseStats = self.baseStats.map{ case (s, i) => (Statistic(s), i)}
val abilities = self.abilities.map(Ability(_))
val xpCurve = self.xpCurve
val catchRate = self.catchRate
}

View File

@ -68,6 +68,11 @@ class Monster(val base : StorageMon) {
def applyBoost(s : Stat, boost : Int) {
val modified = boosts.getOrElse(s, 0) + boost
boosts = boosts.updated(s, Math.min(MaxBoost, Math.max(-MaxBoost, modified)))
if (boost > 0) {
println(s"$this's $s rose!")
} else if (boost < 0) {
println(s"$this's $s fell!")
}
}
def effectiveness(element : Element) : Double = {
@ -88,7 +93,11 @@ class Monster(val base : StorageMon) {
object Monster {
final val MaxBoost = 6
def build(trainer : TrainerID, form : Form)(implicit rng : Random) = {
def generate(nickname: String, ot: TrainerID, xp: Int, form: Form, moves: IndexedSeq[Move])(implicit rng: Random) = {
new Monster(StorageMon.generate(nickname, ot, xp, form, moves))
}
}
class MonsterPtr(var mon : Monster) {
override def toString : String = mon.toString()
}

View File

@ -1,6 +0,0 @@
package fmon.stat
class MonsterPtr(var mon : Monster) {
override def toString : String = mon.toString()
}

View File

@ -12,7 +12,7 @@ import Statistic._
import fmon.util._
object MoveType extends Enumeration {
val Physical, Special, Status = Value
val Physical, Special, Support, Other = Value
}
class MoveTypeType extends TypeReference[MoveType.type]
@ -26,6 +26,7 @@ trait MoveTurn {
def flags: Set[String]
def prior: Int
def target: Target
def category: MoveType
def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit
}
@ -34,6 +35,7 @@ class SwitchOut(val party: Party, val replacement: Monster) extends MoveTurn {
def flags = Set()
def prior = +6
def target = Target.Self
def category = MoveType.Other
def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit = {
println(s"${party.trainer} withdrew $user!")
@ -47,7 +49,7 @@ class SwitchOut(val party: Party, val replacement: Monster) extends MoveTurn {
abstract class Move extends MoveTurn {
val name: String
val desc: String
val mvType: MoveType
val category: MoveType
val pow: Int
val powCallback: (Monster, Monster) => Int
val prior: Int
@ -89,6 +91,9 @@ abstract class Move extends MoveTurn {
user.takeDamage(recoil)
println(s"$user took $recoil damage from recoil!")
}
target.base.ability.onAfterDamage(user, this, target, actualDmg)
if (!user.isAlive) {
println(s"$user fainted!")
}
@ -126,8 +131,8 @@ abstract class Move extends MoveTurn {
}
def damageRoll(user: Monster, target: Monster)(implicit rng: Random) = {
val atkStat = if (mvType == MoveType.Physical) PAtk else MAtk
val defStat = if (mvType == MoveType.Physical) PDef else MDef
val atkStat = if (category == MoveType.Physical) PAtk else MAtk
val defStat = if (category == MoveType.Physical) PDef else MDef
// TODO : Fixed damage
val actualPow = if (powCallback != null) powCallback(user, target) else pow
val baseDmg = (2 * user.level / 5 + 2) * actualPow * user(atkStat) / (target(defStat) * 50) + 2
@ -172,11 +177,7 @@ abstract class Move extends MoveTurn {
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!")
}
}
}
}
@ -214,7 +215,7 @@ case class MoveToken(
new Move {
val name = token.name
val desc = token.shortDesc
val mvType = category
val category = token.category
val pow = token.basePower.getOrElse(0)
val powCallback = Move.compilePowCallback(token.basePowerCallback)
val prior = token.priority
@ -236,7 +237,7 @@ case class MoveToken(
object Move {
private var moves = Map[String, Move]()
val tokens = YamlHelper.extractSeq[MoveToken](Source.fromInputStream(Move.getClass.getResourceAsStream("data/moves.yaml"))).map(t => (t.name, t)).toMap
val tokens = YamlHelper.extractMap[MoveToken](Source.fromInputStream(Move.getClass.getResourceAsStream("data/moves.yaml")))
def apply(name: String): Move = {
if (!moves.contains(name)) {
@ -250,8 +251,8 @@ object Move {
val tb = runtimeMirror(getClass.getClassLoader).mkToolBox()
val tree = tb.parse(
s"""
|import mon.stat.Monster
|import mon.stat.Statistic._
|import fmon.stat.Monster
|import fmon.stat.Statistic._
|def callback(user : Monster, target : Monster): Int = {
| $code
|}

View File

@ -99,6 +99,13 @@ object Status {
}
private val tb = runtimeMirror(getClass.getClassLoader).mkToolBox()
private val header = """
import scala.util.Random
import fmon.stat._
import fmon.stat.MoveType._
import fmon.stat.Statistic._
import fmon.util._
"""
def compileOnStart(code: String): (Status, Monster /*, source, source effect */, Random ) => Unit = {
if (code == null) {
@ -106,10 +113,7 @@ object Status {
} else {
val tree = tb.parse(
s"""
|import scala.util.Random
|import fmon.stat._
|import fmon.stat.Statistic._
|import fmon.util._
|$header
|def onStart(self:Status, mon: Monster, rng: Random) = {
| $code
|}
@ -127,13 +131,11 @@ object Status {
} else {
val tree = tb.parse(
s"""
|import fmon.stat._
|import fmon.stat.Statistic._
|import fmon.util._
|$header
|def onStart(self:Status, mon: Monster) = {
| $code
|}
|onStart _
onStart _
""".stripMargin)
val f = tb.compile(tree)
val wrapper = f()
@ -147,15 +149,13 @@ object Status {
} else {
val tree = tb.parse(
s"""
import scala.util.Random
import fmon.stat._
import fmon.stat.Statistic._
import fmon.util._
$header
def onBeforeMove(self: Status, mon: Monster, move: MoveTurn, target: Monster, rng: Random): Boolean = {
$code
}
onBeforeMove _
""")
val f = tb.compile(tree)
val wrapper = f()
wrapper.asInstanceOf[(Status, Monster, MoveTurn, Monster, Random) => Boolean]
@ -168,9 +168,7 @@ object Status {
} else {
val tree = tb.parse(
s"""
import fmon.stat._
import fmon.stat.Statistic._
import fmon.util._
$header
def onModifyStat(self: Status, mon: Monster, stat: Stat): Fraction = {
$code
}
@ -188,9 +186,7 @@ object Status {
} else {
val tree = tb.parse(
s"""
|import fmon.stat._
|import fmon.stat.Statistic._
|import fmon.util._
|$header
|def onResidual(self: Status, mon: Monster) = {
| $code
|}

View File

@ -1,5 +1,16 @@
package fmon.stat
class StorageMon(val nickname : String, val xp: Int, val gene : Gene, val form : Form, val evs : Map[Stat, Int], val moves : IndexedSeq[Move]) {
import scala.util.Random
import fmon.util._
class StorageMon(val nickname : String, val xp: Int, val gene : Gene, val form : Form, val evs : Map[Stat, Int], val moves : IndexedSeq[Move], val ability: Ability) {
def level = form.xpCurve(xp)
}
object StorageMon {
def generate(nickname: String, ot: TrainerID, xp: Int, form: Form, moves: IndexedSeq[Move])(implicit rng: Random) = {
val gene = Gene.randomGene(ot, form)
new StorageMon(nickname, xp, gene, form, Statistic.emptyEvs, moves, rng.pick(form.abilities))
}
}

View File

@ -0,0 +1,164 @@
noability:
id: noability
isNonstandard: Past
name: No Ability
num: 0
rating: 0.1
shortDesc: Does nothing.
aftermath:
desc: If this Pokemon is knocked out with a contact move, that move's user loses
1/4 of its maximum HP, rounded down. If any active Pokemon has the Damp Ability,
this effect is prevented.
id: aftermath
name: Aftermath
num: 106
onAfterDamage: |-
if ((source != null) && (source != mon) && (move != null) && move.flags("contact") && !mon.isAlive) {
println(s"$source is damaged in the aftermath!")
source.takeDamage(source(Hp) / 4);
}
onAfterDamageOrder: 1
rating: 2.5
shortDesc: If this Pokemon is KOed with a contact move, that move's user loses 1/4
its max HP.
flamebody:
id: flamebody
name: Flame Body
num: 49
onAfterDamage: |-
if (move != null && move.flags("contact")) {
if (rng.chance(3, 10)) {
source += Status("brn");
}
}
rating: 2
shortDesc: 30% chance a Pokemon making contact with this Pokemon will be burned.
gooey:
id: gooey
name: Gooey
num: 183
onAfterDamage: |-
if (move != null && move.flags("contact")) {
//this.add('-ability', target, 'Gooey');
source.applyBoost(Speed, -1)
//this.boost({'spe': -1}, source, target, null, true);
}
rating: 2.5
shortDesc: Pokemon making contact with this Pokemon have their Speed lowered by
1 stage.
innardsout:
desc: If this Pokemon is knocked out with a move, that move's user loses HP equal
to the amount of damage inflicted on this Pokemon.
id: innardsout
name: Innards Out
num: 215
onAfterDamage: |-
if (source != null && source != mon && move != null /* && move.effectType === 'Move'*/ && !mon.isAlive) {
println(s"$source is damaged in the aftermath!")
source.takeDamage(damage)//, source, target);
}
onAfterDamageOrder: 1
rating: 2.5
shortDesc: If this Pokemon is KOed with a move, that move's user loses an equal
amount of HP.
ironbarbs:
desc: Pokemon making contact with this Pokemon lose 1/8 of their maximum HP, rounded
down.
id: ironbarbs
name: Iron Barbs
num: 160
onAfterDamage: |-
if (source != null && source != mon && move != null && move.flags("contact")) {
println(s"Pointed barbs dug into $source!")
source.takeDamage(source(Hp) / 8)//, source, target)
}
onAfterDamageOrder: 1
rating: 3
shortDesc: Pokemon making contact with this Pokemon lose 1/8 of their max HP.
poisonpoint:
id: poisonpoint
name: Poison Point
num: 38
onAfterDamage: |-
if (move != null && move.flags("contact")) {
if (rng.chance(3, 10)) {
source += Status("psn")
}
}
rating: 2
shortDesc: 30% chance a Pokemon making contact with this Pokemon will be poisoned.
roughskin:
desc: Pokemon making contact with this Pokemon lose 1/8 of their maximum HP, rounded
down.
id: roughskin
name: Rough Skin
num: 24
onAfterDamage: |-
if (source != null && source != mon && move != null && move.flags("contact")) {
println(s"$source is damaged by rough skin!")
source.takeDamage(source(Hp) / 8)//, source, target)
}
onAfterDamageOrder: 1
rating: 3
shortDesc: Pokemon making contact with this Pokemon lose 1/8 of their max HP.
stamina:
id: stamina
name: Stamina
num: 192
onAfterDamage: |-
if (move != null /*&& effect.effectType == 'Move' && effect.id != 'confused'*/) {
mon.applyBoost(PDef, +1)
}
rating: 3
shortDesc: This Pokemon's Defense is raised by 1 stage after it is damaged by a move.
static:
id: static
name: Static
num: 9
onAfterDamage: |-
if (move != null && move.flags("contact")) {
if (rng.chance(3, 10)) {
source += Status("par");
}
}
rating: 2
shortDesc: 30% chance a Pokemon making contact with this Pokemon will be paralyzed.
tanglinghair:
id: tanglinghair
name: Tangling Hair
num: 221
onAfterDamage: |-
if (move != null && move.flags("contact")) {
this.add('-ability', target, 'Tangling Hair');
source.applyBoost(Speed, -1)
//this.boost({'spe': -1}, source, target, null, true);
}
rating: 2.5
shortDesc: Pokemon making contact with this Pokemon have their Speed lowered by
1 stage.
weakarmor:
desc: If a physical attack hits this Pokemon, its Defense is lowered by 1 stage
and its Speed is raised by 2 stages.
id: weakarmor
name: Weak Armor
num: 133
onAfterDamage: |-
if (move.category == Physical) {
//this.boost({'def': -1, 'spe': 2}, target, target);
mon.applyBoost(PDef, -1)
mon.applyBoost(Speed, +2)
}
rating: 1
shortDesc: If a physical attack hits this Pokemon, Defense is lowered by 1, Speed
is raised by 2.

View File

@ -2,6 +2,8 @@
desc: A vanilla chameleon.
elements:
- Normal
abilities:
- noability
baseStats:
hp: 60
patk: 70
@ -16,6 +18,9 @@
elements:
- Electric
- Fire
abilities:
- flamebody
- static
baseStats:
hp: 40
patk: 110

View File

@ -1,4 +1,5 @@
- name: "Absorb"
absorb:
name: "Absorb"
num: 71
accuracy: 100
basePower: 20
@ -19,7 +20,8 @@
zMovePower: 100
contestType: "Clever"
- name: Aqua Jet
aquajet:
name: Aqua Jet
num: 453
accuracy: 100
basePower: 40
@ -41,11 +43,12 @@
zMovePower: 100
contestType: Cool
- name: "Bulk Up"
bulkup:
name: "Bulk Up"
num: 339
accuracy: 0
basePower: 0
category: "Status"
category: Support
desc: "Raises the user's Attack and Defense by 1 stage."
shortDesc: "Raises the user's Attack and Defense by 1."
id: "bulkup"
@ -64,11 +67,12 @@
patk: 1
contestType: "Cool"
- name: Charm
charm:
name: Charm
num: 204
accuracy: 100
basePower: 0
category: Status
category: Support
desc: Lowers the target's Attack by 2 stages.
shortDesc: Lowers the target's Attack by 2.
id: charm
@ -88,7 +92,8 @@
pdef: 1
contestType: Cute
- name: "Close Combat"
closecombat:
name: "Close Combat"
num: 370
accuracy: 100
basePower: 120
@ -113,10 +118,11 @@
zMovePower: 190
contestType: "Tough"
- name: Confuse Ray
confuseray:
name: Confuse Ray
accuracy: 100
basePower: 0
category: Status
category: Support
contestType: Clever
desc: Causes the target to become confused.
flags:
@ -135,7 +141,8 @@
zMoveBoost:
MAtk: 1
- name: Electro Ball
electroball:
name: Electro Ball
num: 486
accuracy: 100
basePower: 0
@ -169,7 +176,8 @@
zMovePower: 160
contestType: "Cool"
- name: "Frost Breath"
frostbreath:
name: "Frost Breath"
num: 524
accuracy: 90
basePower: 60
@ -187,7 +195,8 @@
zMovePower: 120
contestType: "Beautiful"
- name: Headbutt
headbutt:
name: Headbutt
accuracy: 100
basePower: 70
category: Physical
@ -209,7 +218,8 @@
type: Normal
zMovePower: 140
- name: Ice Beam
icebeam:
name: Ice Beam
accuracy: 100
basePower: 90
category: Special
@ -231,11 +241,12 @@
type: Ice
zMovePower: 175
- name: "Poison Gas"
poisongas:
name: "Poison Gas"
num: 139
accuracy: 90
basePower: 0
category: "Status"
category: Support
desc: "Poisons the target."
shortDesc: "Poisons the foe(s)."
id: "poisongas"
@ -253,7 +264,8 @@
def: 1
contestType: "Clever"
- name: "Poison Sting"
poisonsting:
name: "Poison Sting"
num: 40
accuracy: 100
basePower: 15
@ -274,7 +286,8 @@
zMovePower: 100
contestType: "Clever"
- name: Scald
scald:
name: Scald
accuracy: 100
basePower: 80
category: Special
@ -298,7 +311,8 @@
type: Water
zMovePower: 160
- name: "Snarl"
snarl:
name: "Snarl"
num: 555
accuracy: 95
basePower: 55
@ -322,10 +336,11 @@
zMovePower: 100
contestType: "Tough"
- name: Spore
spore:
name: Spore
accuracy: 100
basePower: 0
category: Status
category: Support
contestType: Beautiful
flags:
mirror: 1
@ -344,7 +359,8 @@
type: Grass
zMoveEffect: clearnegativeboost
- name: Tackle
tackle:
name: Tackle
num: 33
accuracy: 100
basePower: 40
@ -363,10 +379,11 @@
zMovePower: 100
contestType: Tough
- name: Thunder Wave
thunderwave:
name: Thunder Wave
accuracy: 90
basePower: 0
category: Status
category: Support
contestType: Cool
desc: Paralyzes the target. This move does not ignore type immunity.
flags:
@ -387,7 +404,8 @@
zMoveBoost:
MDef: 1
- name: "Volt Tackle"
volttackle:
name: "Volt Tackle"
num: 344
accuracy: 100
basePower: 120
@ -408,10 +426,11 @@
zMovePower: 190
contestType: "Cool"
- name: Will-O-Wisp
willowisp:
name: Will-O-Wisp
accuracy: 85
basePower: 0
category: Status
category: Support
contestType: Beautiful
desc: Burns the target.
flags:

View File

@ -20,5 +20,6 @@ package object stat {
implicit val formats = DefaultFormats + new EnumNameSerializer(MoveType) + new EnumNameSerializer(Gender)
implicit def rngDice(rng : Random) = new Dice(rng)
implicit def ptrToMonster(ptr : MonsterPtr) : Monster = ptr.mon
implicit def ptrToMonster(ptr : MonsterPtr) : Monster = ptr.mon
implicit def templateToStatus(status: StatusTemplate): Status = status.build
}