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 p1 = TrainerID("Jaeda", Gender.Female, 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 party2 = new Party(p2, new Monster(new StorageMon("Wild Mon", Gene.randomGene(null, form2), form2, Statistic.emptyEvs, movepool2)), 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 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))
|
||||
))
|
||||
val engine = new BattleEngine(party1, party2)
|
||||
|
@ -3,7 +3,7 @@ package mon.battle
|
||||
import mon.stat._
|
||||
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) = {
|
||||
if (move.prior == other.move.prior) {
|
||||
other.user(Speed) compareTo user(Speed)
|
||||
|
@ -15,11 +15,11 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
|
||||
}
|
||||
|
||||
def playTurn() = {
|
||||
val playerMove = player.pollAction
|
||||
val playerTarget = player.pollTarget(playerMove, player.lead, enemy)
|
||||
val enemyMove = enemy.pollAction
|
||||
val enemyTarget = enemy.pollTarget(enemyMove, enemy.lead, player)
|
||||
val actions = Seq(Action(player.lead, playerMove, playerTarget), Action(enemy.lead, enemyMove, enemyTarget))
|
||||
val playerAction = player.pollAction(enemy)
|
||||
val enemyAction = enemy.pollAction(player)
|
||||
println(enemyAction)
|
||||
|
||||
val actions = Seq(playerAction, enemyAction)
|
||||
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))
|
||||
@ -28,7 +28,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val rng: Random
|
||||
if (!player.lead.isAlive) {
|
||||
if (player.canFight) {
|
||||
val replace = player.pollReplacement(enemy)
|
||||
player.swapIn(replace)
|
||||
player.switchIn(replace)
|
||||
} else {
|
||||
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.canFight) {
|
||||
val replace = enemy.pollReplacement(player)
|
||||
enemy.swapIn(replace)
|
||||
enemy.switchIn(replace)
|
||||
} else {
|
||||
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 target = action.target
|
||||
if (user.isAlive) {
|
||||
println(s"$user used $move.")
|
||||
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!")
|
||||
}
|
||||
}
|
||||
}
|
||||
move.useMove(user, target)
|
||||
}
|
||||
}
|
||||
}
|
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 scala.io.Source
|
||||
import scala.util.Random
|
||||
|
||||
import Statistic._
|
||||
import mon.util._
|
||||
|
||||
object MoveType extends Enumeration {
|
||||
@ -19,7 +21,28 @@ object Target extends Enumeration {
|
||||
}
|
||||
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 desc: String
|
||||
val mvType: MoveType
|
||||
@ -43,6 +66,120 @@ abstract class Move {
|
||||
|
||||
// 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 = {
|
||||
name
|
||||
}
|
||||
|
@ -2,12 +2,24 @@ package mon.stat
|
||||
|
||||
import scala.util.Random
|
||||
|
||||
import mon.battle.rngDice
|
||||
import mon.battle.Action
|
||||
import mon.stat.Target._
|
||||
|
||||
class Party(val trainer : TrainerID, var lead : Monster, var sideboard : IndexedSeq[Monster]) {
|
||||
def pollAction(implicit rng : Random) : Move = rng.pick(lead.base.moves)
|
||||
def pollTarget(move : Move, user : Monster, them: Party)(implicit rng : Random) : Monster = {
|
||||
class Party(val trainer: TrainerID, val lead: MonsterPtr, var sideboard: IndexedSeq[Monster]) {
|
||||
def pollAction(them: Party)(implicit rng: Random): Action = {
|
||||
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) {
|
||||
user
|
||||
} else {
|
||||
@ -19,13 +31,21 @@ class Party(val trainer : TrainerID, var lead : Monster, var sideboard : Indexed
|
||||
sideboard.filter(_.isAlive).head
|
||||
}
|
||||
|
||||
def swapIn(mon : Monster) = {
|
||||
def switchIn(mon: Monster) = {
|
||||
val index = sideboard.indexOf(mon)
|
||||
sideboard = sideboard.updated(index, lead)
|
||||
lead = mon
|
||||
sideboard = sideboard.updated(index, lead.mon)
|
||||
lead.mon = mon
|
||||
}
|
||||
|
||||
def shouldSwitch(them: Party)(implicit rng: Random): Boolean = {
|
||||
true
|
||||
}
|
||||
|
||||
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.ext.EnumNameSerializer
|
||||
|
||||
import scala.util.Random
|
||||
|
||||
import mon.util.Dice
|
||||
|
||||
package object stat {
|
||||
type Stat = Statistic.Value
|
||||
type MoveType = MoveType.Value
|
||||
@ -10,4 +14,6 @@ package object stat {
|
||||
type Target = Target.Value
|
||||
|
||||
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