Moved move activation from the game engine to the move itself and added the ability to switch out
This commit is contained in:
parent
8ff9bdd938
commit
32c6913187
@ -23,8 +23,8 @@ object Game {
|
|||||||
val movepool2 = IndexedSeq(Move("Absorb"))
|
val movepool2 = IndexedSeq(Move("Absorb"))
|
||||||
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 Monster(new StorageMon("Allied Mon", Gene.randomGene(null, form1), form1, Statistic.emptyEvs, movepool1)), IndexedSeq())
|
val party1 = new Party(p1, new MonsterPtr(new Monster(new StorageMon("Allied Mon", Gene.randomGene(null, form1), form1, Statistic.emptyEvs, movepool1))), IndexedSeq())
|
||||||
val party2 = new Party(p2, new Monster(new StorageMon("Wild Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2)), IndexedSeq(
|
val party2 = new Party(p2, new MonsterPtr(new Monster(new StorageMon("Wild Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2))), IndexedSeq(
|
||||||
new Monster(new StorageMon("Sideboard Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2))
|
new Monster(new StorageMon("Sideboard Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2))
|
||||||
))
|
))
|
||||||
val engine = new BattleEngine(party1, party2)
|
val engine = new BattleEngine(party1, party2)
|
||||||
|
@ -3,7 +3,7 @@ package mon.battle
|
|||||||
import mon.stat._
|
import mon.stat._
|
||||||
import mon.stat.Statistic.Speed
|
import mon.stat.Statistic.Speed
|
||||||
|
|
||||||
case class Action(user : Monster, move : Move, target : Monster) extends Comparable[Action] {
|
case class Action(user : MonsterPtr, move : MoveTurn, target : MonsterPtr) extends Comparable[Action] {
|
||||||
override def compareTo(other : Action) = {
|
override def compareTo(other : Action) = {
|
||||||
if (move.prior == other.move.prior) {
|
if (move.prior == other.move.prior) {
|
||||||
other.user(Speed) compareTo user(Speed)
|
other.user(Speed) compareTo user(Speed)
|
||||||
|
@ -15,11 +15,11 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
|
|||||||
}
|
}
|
||||||
|
|
||||||
def playTurn() = {
|
def playTurn() = {
|
||||||
val playerMove = player.pollAction
|
val playerAction = player.pollAction(enemy)
|
||||||
val playerTarget = player.pollTarget(playerMove, player.lead, enemy)
|
val enemyAction = enemy.pollAction(player)
|
||||||
val enemyMove = enemy.pollAction
|
println(enemyAction)
|
||||||
val enemyTarget = enemy.pollTarget(enemyMove, enemy.lead, player)
|
|
||||||
val actions = Seq(Action(player.lead, playerMove, playerTarget), Action(enemy.lead, enemyMove, enemyTarget))
|
val actions = Seq(playerAction, enemyAction)
|
||||||
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))
|
||||||
@ -28,7 +28,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
|
|||||||
if (!player.lead.isAlive) {
|
if (!player.lead.isAlive) {
|
||||||
if (player.canFight) {
|
if (player.canFight) {
|
||||||
val replace = player.pollReplacement(enemy)
|
val replace = player.pollReplacement(enemy)
|
||||||
player.swapIn(replace)
|
player.switchIn(replace)
|
||||||
} else {
|
} else {
|
||||||
println(s"${player.trainer} has lost!")
|
println(s"${player.trainer} has lost!")
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
|
|||||||
if (!enemy.lead.isAlive) {
|
if (!enemy.lead.isAlive) {
|
||||||
if (enemy.canFight) {
|
if (enemy.canFight) {
|
||||||
val replace = enemy.pollReplacement(player)
|
val replace = enemy.pollReplacement(player)
|
||||||
enemy.swapIn(replace)
|
enemy.switchIn(replace)
|
||||||
} else {
|
} else {
|
||||||
println(s"${enemy.trainer} has lost!")
|
println(s"${enemy.trainer} has lost!")
|
||||||
}
|
}
|
||||||
@ -51,117 +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) {
|
||||||
println(s"$user used $move.")
|
move.useMove(user, target)
|
||||||
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)
|
|
||||||
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!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def attackRoll(user: Monster, move: Move, target: Monster) = {
|
|
||||||
if (move.accuracy == 0) {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
val acc = user.boosts.getOrElse(Accuracy, 0) - target.boosts.getOrElse(Evasion, 0)
|
|
||||||
val mod = if (acc > 0) Fraction(3 + acc, 3) else Fraction(3, 3 - acc)
|
|
||||||
val chance = move.accuracy.%% * mod
|
|
||||||
rng.chance(chance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 effectiveness = target.effectiveness(move.element)
|
|
||||||
val multiplier = effectiveness * critMultiplier(user, move, target)
|
|
||||||
if (effectiveness > 1.0) {
|
|
||||||
println("It's super effective!")
|
|
||||||
} else if (effectiveness < 1.0) {
|
|
||||||
println("It's not very effective.")
|
|
||||||
}
|
|
||||||
val maxDmg = (baseDmg * multiplier).toInt
|
|
||||||
val minDmg = (17 \ 20) * 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]) {
|
|
||||||
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!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
6
FakeMon/src/mon/stat/MonsterPtr.scala
Normal file
6
FakeMon/src/mon/stat/MonsterPtr.scala
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package mon.stat
|
||||||
|
|
||||||
|
class MonsterPtr(var mon : Monster) {
|
||||||
|
|
||||||
|
override def toString : String = mon.toString()
|
||||||
|
}
|
@ -6,7 +6,9 @@ import scala.tools.reflect.ToolBox
|
|||||||
import com.fasterxml.jackson.module.scala.JsonScalaEnumeration
|
import com.fasterxml.jackson.module.scala.JsonScalaEnumeration
|
||||||
|
|
||||||
import scala.io.Source
|
import scala.io.Source
|
||||||
|
import scala.util.Random
|
||||||
|
|
||||||
|
import Statistic._
|
||||||
import mon.util._
|
import mon.util._
|
||||||
|
|
||||||
object MoveType extends Enumeration {
|
object MoveType extends Enumeration {
|
||||||
@ -19,7 +21,28 @@ object Target extends Enumeration {
|
|||||||
}
|
}
|
||||||
class TargetType extends TypeReference[Target.type]
|
class TargetType extends TypeReference[Target.type]
|
||||||
|
|
||||||
abstract class Move {
|
trait MoveTurn {
|
||||||
|
def name: String
|
||||||
|
def prior: Int
|
||||||
|
def target: Target
|
||||||
|
def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwitchOut(val party: Party, val replacement: Monster) extends MoveTurn {
|
||||||
|
def name = "Swap"
|
||||||
|
def prior = +6
|
||||||
|
def target = Target.Self
|
||||||
|
|
||||||
|
def useMove(user: Monster, target: Monster)(implicit rng: Random): Unit = {
|
||||||
|
println(s"${party.trainer} withdrew $user!")
|
||||||
|
party.switchIn(replacement)
|
||||||
|
println(s"${party.trainer} sent out $replacement")
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = s"Swap -> $replacement"
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Move extends MoveTurn {
|
||||||
val name: String
|
val name: String
|
||||||
val desc: String
|
val desc: String
|
||||||
val mvType: MoveType
|
val mvType: MoveType
|
||||||
@ -43,6 +66,120 @@ abstract class Move {
|
|||||||
|
|
||||||
// zPower, zMoveEffect, zMoveBoost
|
// zPower, zMoveEffect, zMoveBoost
|
||||||
|
|
||||||
|
override def useMove(user: Monster, target: Monster)(implicit rng: Random) = {
|
||||||
|
println(s"$user used $name.")
|
||||||
|
if (attackRoll(user, target)) {
|
||||||
|
if (pow > 0 || powCallback != null) {
|
||||||
|
val dmg = damageRoll(user, 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 (drain > 0.frac) {
|
||||||
|
val healing = Math.max(drain * actualDmg, 1)
|
||||||
|
user.recoverDamage(healing)
|
||||||
|
println(s"$user recovered $healing damage.")
|
||||||
|
} else if (drain < 0.frac) {
|
||||||
|
val recoil = Math.max(-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, boosts)
|
||||||
|
applyStatus(target, status)
|
||||||
|
if (selfEffect != null) {
|
||||||
|
applyEffect(user, selfEffect)
|
||||||
|
}
|
||||||
|
if (secondary != null && rng.chance(secondary.chance.%%)) {
|
||||||
|
applyEffect(target, secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO : Support moves
|
||||||
|
// TODO : Multiparty
|
||||||
|
} else {
|
||||||
|
println("Missed!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def attackRoll(user: Monster, target: Monster)(implicit rng: Random) = {
|
||||||
|
if (accuracy == 0) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
val acc = user.boosts.getOrElse(Accuracy, 0) - target.boosts.getOrElse(Evasion, 0)
|
||||||
|
val mod = if (acc > 0) Fraction(3 + acc, 3) else Fraction(3, 3 - acc)
|
||||||
|
val chance = accuracy.%% * mod
|
||||||
|
rng.chance(chance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// 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
|
||||||
|
val effectiveness = target.effectiveness(element)
|
||||||
|
val multiplier = effectiveness * critMultiplier(user, target)
|
||||||
|
if (effectiveness > 1.0) {
|
||||||
|
println("It's super effective!")
|
||||||
|
} else if (effectiveness < 1.0) {
|
||||||
|
println("It's not very effective.")
|
||||||
|
}
|
||||||
|
val maxDmg = (baseDmg * multiplier).toInt
|
||||||
|
val minDmg = (17 \ 20) * maxDmg
|
||||||
|
rng.nextInt(minDmg, maxDmg)
|
||||||
|
}
|
||||||
|
|
||||||
|
def critMultiplier(user: Monster, target: Monster)(implicit rng: Random) = {
|
||||||
|
// Percentage chance is different from Pokemon
|
||||||
|
val stage = 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!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override def toString = {
|
override def toString = {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,24 @@ package mon.stat
|
|||||||
|
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
|
||||||
import mon.battle.rngDice
|
import mon.battle.Action
|
||||||
import mon.stat.Target._
|
import mon.stat.Target._
|
||||||
|
|
||||||
class Party(val trainer : TrainerID, var lead : Monster, var sideboard : IndexedSeq[Monster]) {
|
class Party(val trainer: TrainerID, val lead: MonsterPtr, var sideboard: IndexedSeq[Monster]) {
|
||||||
def pollAction(implicit rng : Random) : Move = rng.pick(lead.base.moves)
|
def pollAction(them: Party)(implicit rng: Random): Action = {
|
||||||
def pollTarget(move : Move, user : Monster, them: Party)(implicit rng : Random) : Monster = {
|
if (hasBackup && shouldSwitch(them)) {
|
||||||
|
val sub = pollReplacement(them: Party)
|
||||||
|
val move = new SwitchOut(this, sub)
|
||||||
|
Action(lead, move, lead)
|
||||||
|
} else {
|
||||||
|
val move = pollMove
|
||||||
|
val target = pollTarget(move, lead, them)
|
||||||
|
Action(lead, move, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def pollMove(implicit rng: Random): MoveTurn = rng.pick(lead.base.moves)
|
||||||
|
def pollTarget(move: MoveTurn, user: MonsterPtr, them: Party)(implicit rng: Random): MonsterPtr = {
|
||||||
if (move.target == Self) {
|
if (move.target == Self) {
|
||||||
user
|
user
|
||||||
} else {
|
} else {
|
||||||
@ -19,13 +31,21 @@ class Party(val trainer : TrainerID, var lead : Monster, var sideboard : Indexed
|
|||||||
sideboard.filter(_.isAlive).head
|
sideboard.filter(_.isAlive).head
|
||||||
}
|
}
|
||||||
|
|
||||||
def swapIn(mon : Monster) = {
|
def switchIn(mon: Monster) = {
|
||||||
val index = sideboard.indexOf(mon)
|
val index = sideboard.indexOf(mon)
|
||||||
sideboard = sideboard.updated(index, lead)
|
sideboard = sideboard.updated(index, lead.mon)
|
||||||
lead = mon
|
lead.mon = mon
|
||||||
|
}
|
||||||
|
|
||||||
|
def shouldSwitch(them: Party)(implicit rng: Random): Boolean = {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
def canFight: Boolean = {
|
def canFight: Boolean = {
|
||||||
lead.isAlive || sideboard.exists(_.isAlive)
|
lead.isAlive || hasBackup
|
||||||
|
}
|
||||||
|
|
||||||
|
def hasBackup: Boolean = {
|
||||||
|
sideboard.exists(_.isAlive)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,10 @@ package mon
|
|||||||
import org.json4s.DefaultFormats
|
import org.json4s.DefaultFormats
|
||||||
import org.json4s.ext.EnumNameSerializer
|
import org.json4s.ext.EnumNameSerializer
|
||||||
|
|
||||||
|
import scala.util.Random
|
||||||
|
|
||||||
|
import mon.util.Dice
|
||||||
|
|
||||||
package object stat {
|
package object stat {
|
||||||
type Stat = Statistic.Value
|
type Stat = Statistic.Value
|
||||||
type MoveType = MoveType.Value
|
type MoveType = MoveType.Value
|
||||||
@ -10,4 +14,6 @@ package object stat {
|
|||||||
type Target = Target.Value
|
type Target = Target.Value
|
||||||
|
|
||||||
implicit val formats = DefaultFormats + new EnumNameSerializer(MoveType) + new EnumNameSerializer(Gender)
|
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
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user