Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Arcane Mage implementation #128

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Not yet implemented:

To calculate EPs for a single character definition, use the following command:

`./tbcsim --calc-ep-single <path_to_character_definition_file>`
`./tbcsim --calc-ep <path_to_character_definition_file>`

This uses the sim defaults of a step interval of 10ms and an iteration count of 10,000 - both can be adjusted to your preference. See the CLI usage below, or just run `./tbcsim`.

Expand Down
1 change: 1 addition & 0 deletions src/commonMain/kotlin/character/Mutex.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ enum class Mutex {
BUFF_EXPOSE_WEAKNESS,
BUFF_FEROCIOUS_INSPIRATION,
BUFF_FAERIE_FIRE,
BUFF_SPIRIT,

// Hunter
BUFF_HUNTER_ASPECT,
Expand Down
4 changes: 2 additions & 2 deletions src/commonMain/kotlin/character/Spec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ abstract class Spec {
Triple("strength", Stats(strength = 50), 50.0),
Triple("agility", Stats(agility = 50), 50.0),
Triple("meleeCritRating", Stats(meleeCritRating = 5.0 * Rating.critPerPct), 5.0 * Rating.critPerPct),
Triple("physicalHitRating", Stats(physicalHitRating = 2.0 * Rating.physicalHitPerPct), 2.0 * Rating.physicalHitPerPct),
Triple("physicalHitRating", Stats(physicalHitRating = -5.0 * Rating.physicalHitPerPct), -5.0 * Rating.physicalHitPerPct),
secretbis marked this conversation as resolved.
Show resolved Hide resolved
Triple("physicalHasteRating", Stats(physicalHasteRating = 5.0 * Rating.hastePerPct), 5.0 * Rating.hastePerPct),
Triple("expertiseRating", Stats(expertiseRating = 2.0 * Rating.expertisePerPct), 2.0 * Rating.expertisePerPct),
Triple("armorPen", Stats(armorPen = 100), 100.0),
Expand All @@ -33,7 +33,7 @@ abstract class Spec {
// AKA Enhancement Shaman
val casterHybridDeltas = listOf(
Triple("spellCritRating", Stats(spellCritRating = 5.0 * Rating.critPerPct), 5.0 * Rating.critPerPct),
Triple("spellHitRating", Stats(spellHitRating = 5.0 * Rating.spellHitPerPct), 5.0 * Rating.spellHitPerPct)
Triple("spellHitRating", Stats(spellHitRating = -5.0 * Rating.spellHitPerPct), -5.0 * Rating.spellHitPerPct)
)
val defaultCasterDeltas: List<SpecEpDelta> = listOf(
Triple("intellect", Stats(intellect = 50), 50.0),
Expand Down
1 change: 1 addition & 0 deletions src/commonMain/kotlin/character/classes/mage/Mage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Mage(talents: Map<String, Talent>, spec: Spec) : Class(talents, spec) {
IcyVeins.name -> IcyVeins()
ManaEmerald.name -> ManaEmerald()
MoltenArmor.name -> MoltenArmor()
MageArmor.name -> MageArmor()
PresenceOfMind.name -> PresenceOfMind()
Scorch.name -> Scorch()
SummonWaterElemental.name -> SummonWaterElemental()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Frostbolt : Ability() {
val spellPowerCoeff = Spell.spellPowerCoeff(baseCastTimeMs)
override fun cast(sp: SimParticipant) {
val elementalPrecision: ElementalPrecision? = sp.character.klass.talentInstance(ElementalPrecision.name)
val emHit = elementalPrecision?.bonusFireFrostHitPct() ?: 0.0
val emHit = 2 * (elementalPrecision?.bonusFireFrostHitPct() ?: 0.0)
secretbis marked this conversation as resolved.
Show resolved Hide resolved

val empFb: EmpoweredFrostbolt? = sp.character.klass.talentInstance(EmpoweredFrostbolt.name)
val bonusFbCrit = empFb?.frostboltAddlCritPct() ?: 0.0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package character.classes.mage

class MageArmor {

secretbis marked this conversation as resolved.
Show resolved Hide resolved
}
7 changes: 5 additions & 2 deletions src/commonMain/kotlin/character/classes/mage/specs/Arcane.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package character.classes.mage.specs

import character.Spec
import character.SpecEpDelta
import character.Stats

class Arcane : Spec() {
override val name: String = "Arcane"
override val epBaseStat: SpecEpDelta = spellPowerBase
override val epStatDeltas: List<SpecEpDelta> = defaultCasterDeltas
override val epStatDeltas: List<SpecEpDelta> = listOf(Triple("spirit", Stats(spirit = 50), 50.0)) +
defaultCasterDeltas


override fun redSocketEp(deltas: Map<String, Double>): Double {
// 12 spell dmg
Expand All @@ -15,7 +18,7 @@ class Arcane : Spec() {

override fun yellowSocketEp(deltas: Map<String, Double>): Double {
// 5 spell haste rating / 6 spell damage
return ((deltas["spellHasteRating"] ?: 0.0) * 5.0) + 6.0
return ((deltas["intellect"] ?: 0.0) * 10.0)
secretbis marked this conversation as resolved.
Show resolved Hide resolved
}

override fun blueSocketEp(deltas: Map<String, Double>): Double {
Expand Down
5 changes: 5 additions & 0 deletions src/commonMain/kotlin/data/abilities/generic/AdeptsElixir.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package data.abilities.generic

class AdeptsElixir : Ability() {

secretbis marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package data.abilities.generic

secretbis marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@ import character.Ability
object GenericAbilities {
fun byName(name: String): Ability? {
return when(name) {
AdeptsElixir.name -> AdeptsElixir()
BlackenedBasilisk.name -> BlackenedBasilisk()
CrunchySerpent.name -> CrunchySerpent()
DarkRune.name -> DarkRune()
DemonicRune.name -> DemonicRune()
DestructionPotion.name -> DestructionPotion()
ElixirOfDraenicWisdom.name -> ElixirOfDraenicWisdom()
ElixirOfMajorAgility.name -> ElixirOfMajorAgility()
ElixirOfMajorStrength.name -> ElixirOfMajorStrength()
FlaskOfBlindingLight.name -> FlaskOfBlindingLight()
FlaskOfPureDeath.name -> FlaskOfPureDeath()
FlaskOfRelentlessAssault.name -> FlaskOfRelentlessAssault()
HastePotion.name -> HastePotion()
Innervate.name -> Innervate()
InsaneStrengthPotion.name -> InsaneStrengthPotion()
RoastedClefthoof.name -> RoastedClefthoof()
ScrollOfSpiritV.name -> ScrollOfSpiritV()
SpicyHotTalbuk.name -> SpicyHotTalbuk()
SuperManaPotion.name -> SuperManaPotion()
UseActiveTrinket.name -> UseActiveTrinket()
Expand Down
2 changes: 2 additions & 0 deletions src/commonMain/kotlin/data/abilities/generic/Innervate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package data.abilities.generic

secretbis marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package data.abilities.generic

secretbis marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions src/commonMain/kotlin/data/abilities/raid/DivineSpirit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package data.abilities.raid

import character.Ability
import character.Buff
import character.Mutex
import character.Stats
import mechanics.Rating
import sim.SimParticipant
Expand All @@ -20,6 +21,7 @@ class DivineSpirit : Ability() {
override val name: String = Companion.name
override val icon: String = "spell_holy_prayerofspirit.jpg"
override val durationMs: Int = -1
override val mutex: List<Mutex> = listOf(Mutex.BUFF_SPIRIT)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As with the spirit scroll implementation, this would benefit from a mutexPriority override to accurately express the potency.


override fun modifyStats(sp: SimParticipant): Stats {
return Stats(spirit = 50)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package data.abilities.raid

import character.Ability
import character.Buff
import character.Mutex
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Leftover import

import character.Stats
import mechanics.Rating
import sim.SimParticipant
Expand All @@ -20,6 +21,7 @@ class ImprovedDivineSpirit : Ability() {
override val name: String = Companion.name
override val icon: String = "spell_holy_prayerofspirit.jpg"
override val durationMs: Int = -1
override val mutex: List<Mutex> = listOf(Mutex.BUFF_SPIRIT)
secretbis marked this conversation as resolved.
Show resolved Hide resolved

override fun modifyStats(sp: SimParticipant): Stats {
// assumes max rank
Expand Down
15 changes: 6 additions & 9 deletions src/jvmMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,8 @@ class TBCSim : CliktCommand() {
)

fun singleEpSim(config: Config, opts: SimOptions, epDelta: SpecEpDelta? = null) : Pair<SpecEpDelta?, Double> {
// Most presets are hit capped, so apply a universal -2% hit buff so the hit has something to sim against
val hitReduction = Stats(
physicalHitRating = -2.0 * Rating.physicalHitPerPct,
expertiseRating = -2.0 * Rating.expertisePerPct,
spellHitRating = -5.0 * Rating.spellHitPerPct,
)

val epStatMod = epDelta?.second ?: Stats()
val totalStatMod = Stats().add(epStatMod).add(hitReduction)
val totalStatMod = Stats().add(epStatMod)//.add(hitReduction)

val iterations = runBlocking { Sim(config, opts, totalStatMod) {}.sim() }
return Pair(epDelta, SimStats.dps(iterations).entries.sumByDouble { it.value?.mean ?: 0.0 })
Expand Down Expand Up @@ -265,7 +258,11 @@ class TBCSim : CliktCommand() {
val specFilter = specFilterStr?.split(",")
val categoryFilter = categoryFilterStr?.split(",")

if (calcEP) {
if (calcEP && configFile?.exists() == true) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about this, looks like I broke that option a long time ago and never fixed the docs... The EP rankings on the site mostly replaced the intent of that feature.

Would the most useful output for a single-character sim be just a table of results to the console? Or, would something else be better?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found creating the list of my personal EPs to be the most useful thing. Arcane mages have a few different points where haste, in particular, changes very dramatically in value. Understanding where I am relative to those points of inflection is what I wanted to get, and the current output with my local changes does that.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! Whenever you're ready, I'd love to see the implementation!

val config = ConfigMaker.fromYml(configFile!!.readText())
println("Starting EP run")
val deltas = computeEpDeltas(config, opts)
} else if (calcEP) {
val epTypeRef = object : TypeReference<EpOutput>(){}
val existing = mapper.readValue(File(epOutputPath).readText(), epTypeRef)
// EP calculation sim
Expand Down
63 changes: 44 additions & 19 deletions ui/src/presets/samples/mage_arcane_phase2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ gear:
mainHand:
name: The Nexus Key
enchant: Major Spellpower (Weapon)
tempEnchant: Superior Wizard Oil
tempEnchant: Brilliant Wizard Oil
rangedTotemLibram:
name: Eredar Wand of Obliteration
head:
Expand Down Expand Up @@ -112,14 +112,28 @@ gear:
rotation:
autoAttack: false
precombat:
- name: Flask of Blinding Light
- name: Elixir of Draenic Wisdom
- name: Adept's Elixir
- name: Crunchy Serpent
- name: Arcane Intellect
- name: Molten Armor
- name: Mage Armor
secretbis marked this conversation as resolved.
Show resolved Hide resolved
- name: Scroll of Spirit V

combat:
- name: Evocation
criteria:
- type: RESOURCE_PCT_LTE
pct: 30
resourceType: MANA
- type: FIGHT_TIME_REMAINING_GTE
seconds: 30
- name: Blood Fury
- name: Berserking
- name: Bloodlust
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bloodlust is also a configurable raid buff, and is not an ability Mages can cast. As such, it should be removed from the rotation, as it won't do anything here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't clear to me how to configure the Bloodlust buff. For example, how do you specify when it triggers?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The raid version of Bloodlust is cast at the start of the fight, and lines up with generally-immediate cooldown usage by presets.

criteria:
- type: FIGHT_DURATION_GTE
seconds: 15
- name: Drums of Battle
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drums are a configurable raid buff, and assumes it is cast by someone else - is the intent here to account for the drum cast time? In any case, this wouldn't have any effect in a rotation, since it's not a registered Ability for the mage class.

- name: Mana Emerald
criteria:
- type: RESOURCE_MISSING_GTE
Expand All @@ -131,31 +145,40 @@ rotation:
- type: RESOURCE_MISSING_GTE
amount: 3000
resourceType: MANA
- name: Evocation
- type: ABILITY_COOLDOWN_GTE
ability: Mana Emerald
seconds: 1
- name: Innervate
criteria:
- type: RESOURCE_PCT_LTE
pct: 20
pct: 35
resourceType: MANA
- type: FIGHT_TIME_REMAINING_GTE
seconds: 30
- name: Cold Snap
criteria:
- type: ABILITY_COOLDOWN_LTE
ability: Mana Emerald
seconds: 5
- type: ABILITY_COOLDOWN_GTE
ability: Icy Veins
ability: Mana Emerald
seconds: 1
- name: Icy Veins
criteria:
- type: FIGHT_TIME_ELAPSED_GTE
seconds: 5
- type: RESOURCE_MISSING_GTE
# Account for Serpent-Coil Braid bonus potential over the regular mana gem amount
amount: 3125
resourceType: MANA
- name: Arcane Power
criteria:
- type: FIGHT_TIME_ELAPSED_GTE
seconds: 5
- type: RESOURCE_MISSING_GTE
# Account for Serpent-Coil Braid bonus potential over the regular mana gem amount
amount: 3125
resourceType: MANA
- name: Presence of Mind
criteria:
- type: FIGHT_TIME_ELAPSED_GTE
seconds: 5
- name: Use Active Trinket
criteria:
- type: RESOURCE_MISSING_GTE
amount: 3125
resourceType: MANA
# Cast AB if we're using cooldowns, have high mana, or have low mana and low stacks
- name: Arcane Blast
criteria:
Expand All @@ -170,16 +193,18 @@ rotation:
- name: Arcane Blast
criteria:
- type: RESOURCE_PCT_GTE
pct: 20
pct: 25
resourceType: MANA
- name: Arcane Blast
criteria:
- type: BUFF_STACKS_LTE
buff: Arcane Blast
stacks: 2
- type: RESOURCE_PCT_LTE
pct: 20
resourceType: MANA
- name: Arcane Blast
criteria:
- type: BUFF_DURATION_LTE
buff: Arcane Blast
seconds: 1
- name: Frostbolt

raidBuffs:
Expand Down