diff --git a/itemparser/main.go b/itemparser/main.go index 21735b8..fa7a7b5 100644 --- a/itemparser/main.go +++ b/itemparser/main.go @@ -41,7 +41,7 @@ func main() { // Another header row... continue } - if v[0] != "" && v[1] == "" && v[2] == "" { + if v[0] != "" && v[2] == "" { // Change slot switch v[0] { case "Helm", "Head": @@ -87,6 +87,10 @@ func main() { haste, _ := strconv.ParseFloat(v[8], 64) // spp, _ := strconv.ParseFloat(v[], 64) + numRed. _ := strconv.Atoi(v[12]) + numRed. _ := strconv.Atoi(v[13]) + numRed. _ := strconv.Atoi(v[14]) + numRed. _ := strconv.Atoi(v[15]) i := tbc.Item{ Name: v[1], SourceZone: v[2], @@ -100,6 +104,7 @@ func main() { tbc.StatHaste: haste, tbc.StatMP5: mp5, }, + GemSlots: , } fmt.Fprintf(os.Stdout, "%#v,\n", i) diff --git a/main.go b/main.go index 71f5256..334d088 100644 --- a/main.go +++ b/main.go @@ -86,7 +86,7 @@ func main() { ) gearStats := gear.Stats() - fmt.Printf("Gear Stats:\n%s", gearStats.Print()) + fmt.Printf("Gear Stats:\n%s", gearStats.Print(true)) opt := tbc.Options{ NumBloodlust: 1, @@ -155,14 +155,14 @@ func runTBCSim(equip tbc.Equipment, opt tbc.Options, seconds int, numSims int, c spellOrders = [][]string{customRotation} } - fmt.Printf("\nFinal Stats: %s\n", stats.Print()) + fmt.Printf("\nFinal Stats: %s\n", stats.Print(true)) statchan := make(chan string, 3) for spi, spells := range spellOrders { go func(spo []string) { simDmgs := []float64{} simOOMs := []int{} histogram := map[int]int{} - casts := map[string]int{} + casts := map[int32]int{} manaSpent := 0.0 manaLeft := 0.0 oomdps := 0.0 diff --git a/tbc/auras.go b/tbc/auras.go index 16b1438..d74990e 100644 --- a/tbc/auras.go +++ b/tbc/auras.go @@ -2,11 +2,10 @@ package tbc import ( "math" - "strings" ) type Aura struct { - ID string + ID int32 Expires int // ticks aura will apply OnCast AuraEffect @@ -19,12 +18,46 @@ type Aura struct { // AuraEffects will mutate a cast or simulation state. type AuraEffect func(sim *Simulation, c *Cast) +// List of all magic effects and spells and items and stuff that can go on CD or have an aura. +const ( + MagicIDUnknown int32 = iota + // Auras + MagicIDLOTalent + MagicIDJoW + MagicIDEleFocus + MagicIDEleMastery + MagicIDStormcaller + MagicIDSilverCrescent + MagicIDQuagsEye + MagicIDFungalFrenzy + MagicIDBloodlust + MagicIDSkycall + MagicIDEnergized + MagicIDNAC + MagicIDChaoticSkyfire + MagicIDInsightfulEarthstorm + MagicIDMysticSkyfire + MagicIDMysticFocus + MagicIDEmberSkyfire + + //Spells + MagicIDLB12 + MagicIDCL6 + + //Items + MagicIDISCTrink + MagicIDNACTrink + MagicIDPotion + MagicIDRune + MagicIDAllTrinket +) + func AuraJudgementOfWisdom() Aura { return Aura{ - ID: "jow", + ID: MagicIDJoW, Expires: math.MaxInt32, OnSpellHit: func(sim *Simulation, c *Cast) { - debug(" -Judgement Of Wisdom +74 mana- ") + sim.debug(" -Judgement Of Wisdom +74 mana- \n") sim.CurrentMana += 74 }, } @@ -32,19 +65,23 @@ func AuraJudgementOfWisdom() Aura { func AuraLightningOverload(lvl int) Aura { return Aura{ - ID: "lotalent", + ID: MagicIDLOTalent, Expires: math.MaxInt32, OnSpellHit: func(sim *Simulation, c *Cast) { - if !strings.HasPrefix(c.Spell.ID, "LB") && !strings.HasPrefix(c.Spell.ID, "CL") { + if c.Spell.ID == MagicIDLB12 || c.Spell.ID == MagicIDCL6 { return } + if c.isLO { + return // can't proc LO on LO + } if sim.rando.Float64() < 0.04*float64(lvl) { - debug("\tLightning Overload...") + sim.debug("Lightning Overload Proc\n") dmg := c.DidDmg if c.DidCrit { dmg /= 2 } clone := &Cast{ + isLO: true, Spell: c.Spell, Hit: c.Hit, Crit: c.Crit, @@ -65,7 +102,7 @@ func AuraLightningOverload(lvl int) Aura { func AuraElementalFocus(tick int) Aura { count := 2 return Aura{ - ID: "elefocus", + ID: MagicIDEleFocus, Expires: tick + (15 * TicksPerSecond), OnCast: func(sim *Simulation, c *Cast) { c.ManaCost *= .6 // reduced by 40% @@ -76,7 +113,7 @@ func AuraElementalFocus(tick int) Aura { } count-- if count == 0 { - sim.cleanAuraName("elefocus") + sim.cleanAuraName(MagicIDEleFocus) } }, } @@ -84,35 +121,35 @@ func AuraElementalFocus(tick int) Aura { func AuraEleMastery() Aura { return Aura{ - ID: "elemastery", + ID: MagicIDEleMastery, Expires: math.MaxInt32, OnCast: func(sim *Simulation, c *Cast) { - debug(" -ele mastery active- ") + sim.debug(" -ele mastery active- \n") c.Crit = 1.01 // 101% chance of crit c.ManaCost = 0 - sim.CDs["elemastery"] = 180 * TicksPerSecond + sim.CDs[MagicIDEleMastery] = 180 * TicksPerSecond }, OnCastComplete: func(sim *Simulation, c *Cast) { - sim.cleanAuraName("elemastery") + sim.cleanAuraName(MagicIDEleMastery) }, } } func AuraStormcaller(tick int) Aura { return Aura{ - ID: "stormcaller", + ID: MagicIDStormcaller, Expires: tick + (8 * TicksPerSecond), OnCast: func(sim *Simulation, c *Cast) { - debug(" -stormcaller- ") + sim.debug(" -stormcaller- \n") c.Spellpower += 50 }, } } func ActivateSilverCrescent(sim *Simulation) Aura { - debug(" -silver crescent active- ") + sim.debug(" -silver crescent active- \n") return Aura{ - ID: "silvercrescent", + ID: MagicIDSilverCrescent, Expires: sim.currentTick + 20*TicksPerSecond, OnCast: func(sim *Simulation, c *Cast) { c.Spellpower += 155 @@ -124,20 +161,20 @@ func ActivateQuagsEye(sim *Simulation) Aura { lastActivation := math.MinInt32 const hasteBonus = 320.0 return Aura{ - ID: "quageye", + ID: MagicIDQuagsEye, Expires: math.MaxInt32, OnCastComplete: func(sim *Simulation, c *Cast) { if lastActivation+(45*TicksPerSecond) < sim.currentTick && sim.rando.Float64() < 0.1 { - debug(" -quags eye- ") + sim.debug(" -quags eye- \n") sim.Buffs[StatHaste] += hasteBonus - sim.addAura(AuraHasteRemoval(sim.currentTick, 6.0, hasteBonus, "fungalfrenzy")) + sim.addAura(AuraHasteRemoval(sim.currentTick, 6.0, hasteBonus, MagicIDFungalFrenzy)) lastActivation = sim.currentTick } }, } } -func AuraHasteRemoval(tick int, seconds int, amount float64, id string) Aura { +func AuraHasteRemoval(tick int, seconds int, amount float64, id int32) Aura { return Aura{ ID: id, Expires: tick + (seconds * TicksPerSecond), @@ -148,31 +185,31 @@ func AuraHasteRemoval(tick int, seconds int, amount float64, id string) Aura { } func ActivateBloodlust(sim *Simulation) Aura { - debug(" -BL Activated- ") + sim.debug(" -BL Activated- \n") sim.Buffs[StatHaste] += 472.8 - sim.CDs["bloodlust"] = 40 * TicksPerSecond // assumes that multiple BLs are different shaman. - return AuraHasteRemoval(sim.currentTick, 40, 472.8, "bloodlust") + sim.CDs[MagicIDBloodlust] = 40 * TicksPerSecond // assumes that multiple BLs are different shaman. + return AuraHasteRemoval(sim.currentTick, 40, 472.8, MagicIDBloodlust) } func ActivateSkycall(sim *Simulation) Aura { const hasteBonus = 101 return Aura{ - ID: "skycall", + ID: MagicIDSkycall, Expires: math.MaxInt32, OnCastComplete: func(sim *Simulation, c *Cast) { if sim.rando.Float64() < 0.1 { // TODO: what is actual proc rate? - debug(" -skycall energized- ") + sim.debug(" -skycall energized- \n") sim.Buffs[StatHaste] += hasteBonus - sim.addAura(AuraHasteRemoval(sim.currentTick, 10, hasteBonus, "energized")) + sim.addAura(AuraHasteRemoval(sim.currentTick, 10, hasteBonus, MagicIDEnergized)) } }, } } func ActivateNAC(sim *Simulation) Aura { - debug(" -NAC active- ") + sim.debug(" -NAC active- \n") return Aura{ - ID: "nac", + ID: MagicIDNAC, Expires: sim.currentTick + 300*TicksPerSecond, OnCast: func(sim *Simulation, c *Cast) { c.Spellpower += 250 @@ -180,3 +217,55 @@ func ActivateNAC(sim *Simulation) Aura { }, } } + +func ActivateCSD(sim *Simulation) Aura { + return Aura{ + ID: MagicIDChaoticSkyfire, + Expires: math.MaxInt32, + OnCastComplete: func(sim *Simulation, c *Cast) { + if c.DidCrit { + c.DidDmg *= 1.09 // 150% crit * 1.03 = 154.5% crit dmg. Double crit dmg talent *= 2 -> 309-100 = x2.09. Crit calc earlier added the x2. We just add the 0.09 + } + }, + } +} + +func ActivateIED(sim *Simulation) Aura { + lastActivation := math.MinInt32 + return Aura{ + ID: MagicIDInsightfulEarthstorm, + Expires: math.MaxInt32, + OnCastComplete: func(sim *Simulation, c *Cast) { + if lastActivation+(15*TicksPerSecond) < sim.currentTick && sim.rando.Float64() < 0.04 { + lastActivation = sim.currentTick + sim.debug(" -Insightful Earthstorm Mana Restore- \n") + sim.CurrentMana += 300 + } + }, + } +} + +func ActivateMSD(sim *Simulation) Aura { + lastActivation := math.MinInt32 + const hasteBonus = 320.0 + return Aura{ + ID: MagicIDMysticSkyfire, + Expires: math.MaxInt32, + OnCastComplete: func(sim *Simulation, c *Cast) { + if lastActivation+(45*TicksPerSecond) < sim.currentTick && sim.rando.Float64() < 0.1 { + sim.debug(" -mystic skyfire- \n") + sim.Buffs[StatHaste] += hasteBonus + sim.addAura(AuraHasteRemoval(sim.currentTick, 4.0, hasteBonus, MagicIDMysticFocus)) + lastActivation = sim.currentTick + } + }, + } +} + +func ActivateESD(sim *Simulation) Aura { + sim.Buffs[StatInt] += (sim.Stats[StatInt] + sim.Buffs[StatInt]) * 0.02 + return Aura{ + ID: MagicIDEmberSkyfire, + Expires: math.MaxInt32, + } +} diff --git a/tbc/buffs.go b/tbc/buffs.go index a210dad..ddb76ac 100644 --- a/tbc/buffs.go +++ b/tbc/buffs.go @@ -1 +1,163 @@ package tbc + +import "fmt" + +type Options struct { + SpellOrder []string + RSeed int64 + ExitOnOOM bool + + NumBloodlust int + NumDrums int + + Buffs Buffs + Consumes Consumes + Talents Talents + Totems Totems + + Debug bool // enables debug printing. + // TODO: could change this to be a func/stream consumer could provide, + // make it easier to integrate into different output systems. +} + +func (o Options) StatTotal(e Equipment) Stats { + gearStats := e.Stats() + stats := o.BaseStats() + for i := range stats { + stats[i] += gearStats[i] + } + + stats = o.Talents.AddStats(o.Buffs.AddStats(o.Consumes.AddStats(o.Totems.AddStats(stats)))) + + if o.Buffs.BlessingOfKings { + stats[StatInt] *= 1.1 // blessing of kings + } + + // Final calculations + stats[StatSpellCrit] += (stats[StatInt] / 80) / 100 + stats[StatMana] += stats[StatInt] * 15 + fmt.Printf("\fFinal MP5: %f", (stats[StatMP5] + (stats[StatInt] * 0.06))) + + return stats +} + +func (o Options) BaseStats() Stats { + stats := Stats{ + StatInt: 104, // Base + StatMana: 2958, // level 70 shaman + StatLen: 0, + } + return stats +} + +type Totems struct { + TotemOfWrath int + WrathOfAir bool + ManaStream bool +} + +func (tt Totems) AddStats(s Stats) Stats { + s[StatSpellCrit] += 66.24 * float64(tt.TotemOfWrath) + s[StatSpellHit] += 37.8 * float64(tt.TotemOfWrath) + if tt.WrathOfAir { + s[StatSpellDmg] += 104 + } + if tt.ManaStream { + s[StatMP5] += 50 + } + return s +} + +type Talents struct { + LightninOverload int + ElementalPrecision int + NaturesGuidance int + TidalMastery int + ElementalMastery bool + UnrelentingStorm int + CallOfThunder int + Convection int + Concussion int +} + +func (t Talents) AddStats(s Stats) Stats { + s[StatSpellHit] += 25.2 * float64(t.ElementalPrecision) + s[StatSpellHit] += 12.6 * float64(t.NaturesGuidance) + s[StatSpellCrit] += 22.08 * float64(t.TidalMastery) + s[StatSpellCrit] += 22.08 * float64(t.CallOfThunder) + + return s +} + +type Buffs struct { + // Raid buffs + ArcaneInt bool + GiftOftheWild bool + BlessingOfKings bool + ImprovedBlessingOfWisdom bool + + // Party Buffs + Moonkin bool + SpriestDPS int // adds Mp5 ~ 25% (dps*5%*5sec = 25%) + + // Self Buffs + WaterShield bool + WaterShieldPPM int // how many procs per minute does watershield get? Every 3 requires a recast. + + // Target Debuff + JudgementOfWisdom bool +} + +func (b Buffs) AddStats(s Stats) Stats { + if b.ArcaneInt { + s[StatInt] += 40 + } + if b.GiftOftheWild { + s[StatInt] += 18 // assumes improved gotw, rounded down to nearest int... not sure if that is accurate. + } + if b.ImprovedBlessingOfWisdom { + s[StatMP5] += 42 + } + if b.Moonkin { + s[StatSpellCrit] += 110.4 + } + if b.WaterShield { + s[StatMP5] += 50 + } + s[StatMP5] += float64(b.SpriestDPS) * 0.25 + + return s +} + +type Consumes struct { + // Buffs + BrilliantWizardOil bool + MajorMageblood bool + FlaskOfBlindingLight bool + FlaskOfMightyRestoration bool + BlackendBasilisk bool + + // Used in rotations + SuperManaPotion bool + DarkRune bool +} + +func (c Consumes) AddStats(s Stats) Stats { + if c.BrilliantWizardOil { + s[StatSpellCrit] += 14 + s[StatSpellDmg] += 36 + } + if c.MajorMageblood { + s[StatMP5] += 16.0 + } + if c.FlaskOfBlindingLight { + s[StatSpellDmg] += 80 + } + if c.FlaskOfMightyRestoration { + s[StatMP5] += 25 + } + if c.BlackendBasilisk { + s[StatSpellDmg] += 23 + } + return s +} diff --git a/tbc/items.go b/tbc/items.go index 4117257..8f880b3 100644 --- a/tbc/items.go +++ b/tbc/items.go @@ -298,9 +298,9 @@ var items = struct { {Slot: EquipTrinket, Name: "Quagmirran's Eye", SourceZone: "The Slave Pens", SourceDrop: "Quagmirran", Stats: Stats{StatSpellDmg: 37}, Activate: ActivateQuagsEye, ActivateCD: -1}, // -1 will trigger an activation only once {Slot: EquipTrinket, Name: "Icon of the Silver Crescent", SourceZone: "Shattrath", SourceDrop: "G'eras - 41 Badges", Stats: Stats{StatSpellDmg: 44}, - Activate: ActivateSilverCrescent, ActivateCD: 120 * TicksPerSecond, CoolID: "icsctrink"}, + Activate: ActivateSilverCrescent, ActivateCD: 120 * TicksPerSecond, CoolID: MagicIDISCTrink}, {Slot: EquipTrinket, Name: "Natural Alignment Crystal", SourceZone: "BWL", SourceDrop: "", Stats: Stats{}, - Activate: ActivateNAC, ActivateCD: 300 * TicksPerSecond, CoolID: "nactrink"}, + Activate: ActivateNAC, ActivateCD: 300 * TicksPerSecond, CoolID: MagicIDNACTrink}, {Slot: EquipTrinket, Name: "Neltharion's Tear", SourceZone: "BWL", SourceDrop: "Nefarian", Stats: Stats{StatSpellDmg: 44, StatSpellHit: 16}}, }, Totem: []Item{ @@ -309,6 +309,27 @@ var items = struct { }, } +var Gems = []Gem{ + // {Name: "Destructive Skyfire Diamond", Color: GemColorMeta, Stats: Stats{}}, + // {Name: "Enigmatic Skyfire Diamond", Color: GemColorMeta, Stats: Stats{}}, + {Name: "Chaotic Skyfire Diamond", Color: GemColorMeta, Stats: Stats{StatSpellCrit: 12}, Activate: ActivateCSD}, + // {Name: "Swift Skyfire Diamond", Color: GemColorMeta, Stats: Stats{}}, + // {Name: "Potent Unstable Diamond", Color: GemColorMeta, Stats: Stats{}}, + // {Name: "Swift Windfire Diamond", Color: GemColorMeta, Stats: Stats{}}, + // {Name: "Powerful Earthstorm Diamond", Color: GemColorMeta, Stats: Stats{}}, + {Name: "Bracing Earthstorm Diamond", Color: GemColorMeta, Stats: Stats{StatSpellDmg: 14}}, + {Name: "Imbued Unstable Diamond", Color: GemColorMeta, Stats: Stats{StatSpellDmg: 14}}, + {Name: "Ember Skyfire Diamond", Color: GemColorMeta, Stats: Stats{StatSpellDmg: 14}, Activate: ActivateESD}, + {Name: "Swift Starfire Diamond", Color: GemColorMeta, Stats: Stats{StatSpellDmg: 12}}, + {Name: "Mystical Skyfire Diamond", Color: GemColorMeta, Stats: Stats{}, Activate: ActivateMSD}, + // {Name: "Thundering Skyfire Diamond", Color: GemColorMeta, Stats: Stats{}}, + // {Name: "Relentless Earthstorm Diamond", Color: GemColorMeta, Stats: Stats{}}, + // {Name: "Tenacious Earthstorm Diamond", Color: GemColorMeta, Stats: Stats{}}, + // {Name: "Eternal Earthstorm Diamond", Color: GemColorMeta, Stats: Stats{}}, + // {Name: "Brutal Earthstorm Diamond", Color: GemColorMeta, Stats: Stats{}}, + {Name: "Insightful Earthstorm Diamond", Color: GemColorMeta, Stats: Stats{StatInt: 12}, Activate: ActivateIED}, +} + var ItemLookup = map[string]*Item{} func IL(name string) *Item { @@ -410,20 +431,45 @@ func init() { // Figurine - Living Ruby Serpent Jewelcarfting BoP 33 23 type Item struct { - Slot int + Slot byte Name string SourceZone string SourceDrop string Stats Stats // Stats applied to wearer + GemSlots []GemColor + Gems []Gem + SocketBonus Stats + // For simplicity all items that produce an aura are 'activatable'. // Since we activate all items on CD, this works fine for stuff like Quags Eye. // TODO: is this the best design for this? Activate ItemActivation // Activatable Ability, produces an aura ActivateCD int // cooldown on activation, -1 means perm effect. - CoolID string // ID used for cooldown + CoolID int32 // ID used for cooldown +} + +type Gem struct { + Name string + Stats Stats // flat stats gem adds + Activate ItemActivation // Meta gems activate an aura on player when socketed. + Color GemColor + // Requirements // Validate the gem can be used... later } +type GemColor byte + +const ( + GemColorUnknown GemColor = iota + GemColorMeta + GemColorRed + GemColorBlue + GemColorYellow + GemColorGreen + GemColorOrange + GemColorPurple +) + type ItemActivation func(*Simulation) Aura type Equipment []Item @@ -456,7 +502,7 @@ func NewEquipmentSet(names ...string) Equipment { } const ( - EquipUnknown int = iota + EquipUnknown byte = iota EquipHead EquipNeck EquipShoulder @@ -634,16 +680,6 @@ var moreItems = []Item{ {Slot: 11, Name: "Cobalt Band of Tyrigosa", SourceZone: "H MT - Nexus-Prince Shaffar", SourceDrop: "", Stats: Stats{17, 19, 0, 0, 35, 0, 0}}, {Slot: 11, Name: "Seal of the Exorcist", SourceZone: "50 Spirit Shards ", SourceDrop: "", Stats: Stats{0, 24, 0, 12, 28, 0, 0}}, {Slot: 11, Name: "Lola's Eve", SourceZone: "BoE World Drop", SourceDrop: "", Stats: Stats{14, 15, 0, 0, 29, 0, 0}}, - {Slot: 11, Name: "Yor's Collapsing Band", SourceZone: "H MT - Yor (Summoned Boss)", SourceDrop: "", Stats: Stats{20, 0, 0, 0, 23, 0, 0}}, - // {Slot: 11, Name: "Darkmoon Card: Crusade", SourceZone: "Blessings Deck", SourceDrop: "", Stats: Stats{0, 0, 0, 0, 0, 0, 0}}, - // {Slot: 11, Name: "Scryer's Bloodgem", SourceZone: "The Scryers - Revered", SourceDrop: "", Stats: Stats{0, 0, 0, 32, 0, 0, 0}}, - // {Slot: 11, Name: "Quagmirran's Eye", SourceZone: "H SP - Quagmirran", SourceDrop: "", Stats: Stats{0, 0, 0, 0, 37, 0, 0}}, - // {Slot: 11, Name: "Arcanist's Stone", SourceZone: "H OHF - Epoch Hunter", SourceDrop: "", Stats: Stats{0, 0, 0, 25, 0, 0, 0}}, - // {Slot: 11, Name: "Icon of the Silver Crescent", SourceZone: "41 Badge of Justice - G'eras", SourceDrop: "", Stats: Stats{0, 0, 0, 0, 43, 0, 0}}, - // {Slot: 11, Name: "Shiffar's Nexus-Horn", SourceZone: "Arc - Harbinger Skyriss", SourceDrop: "", Stats: Stats{0, 0, 30, 0, 0, 0, 0}}, - // {Slot: 11, Name: "Xi'ri's Gift", SourceZone: "The Sha'tar - Revered", SourceDrop: "", Stats: Stats{0, 0, 32, 0, 0, 0, 0}}, - // {Slot: 11, Name: "Vengeance of the Illidari", SourceZone: "Cruel's Intentions/Overlord - HFP Quest", SourceDrop: "", Stats: Stats{0, 0, 26, 0, 0, 0, 0}}, - // {Slot: 11, Name: "Figurine - Living Ruby Serpent", SourceZone: "Jewelcarfting BoP", SourceDrop: "", Stats: Stats{23, 33, 0, 0, 0, 0, 0}}, {Slot: 19, Name: "Totem of the Void", SourceZone: "Mech - Cache of the Legion", SourceDrop: "", Stats: Stats{StatSpellDmg: 55}}, // TODO: Make an aura that effects only LB/CL {Slot: 19, Name: "Totem of the Pulsing Earth", SourceZone: "15 Badge of Justice - G'eras", SourceDrop: "", Stats: Stats{0, 0, 0, 0, 0, 0, 0}}, {Slot: 19, Name: "Totem of Impact", SourceZone: "15 Mark of Thrallmar/ Honor Hold", SourceDrop: "", Stats: Stats{0, 0, 0, 0, 0, 0, 0}}, diff --git a/tbc/sim.go b/tbc/sim.go index 44c1d86..44481d8 100644 --- a/tbc/sim.go +++ b/tbc/sim.go @@ -4,14 +4,13 @@ import ( "fmt" "math" "math/rand" - "strings" ) var IsDebug = false -func debug(s string, vals ...interface{}) { - if IsDebug { - fmt.Printf(s, vals...) +func debugFunc(sim *Simulation) func(string, ...interface{}) { + return func(s string, vals ...interface{}) { + fmt.Printf("[%0.1f] "+s, append([]interface{}{(float64(sim.currentTick) / float64(TicksPerSecond))}, vals...)...) } } @@ -24,14 +23,14 @@ type Simulation struct { activeEquip Equipment // cache of gear that can activate. Options Options - SpellRotation []string + SpellRotation []int32 RotationIdx int // ticks until cast is complete CastingSpell *Cast // timeToRegen := 0 - CDs map[string]int + CDs map[int32]int Auras []Aura // this is array instaed of map to speed up browser perf. // Clears and regenerates on each Run call. @@ -40,6 +39,8 @@ type Simulation struct { rando *rand.Rand rseed int64 currentTick int + + debug func(string, ...interface{}) } type SimMetrics struct { @@ -51,162 +52,6 @@ type SimMetrics struct { Rotation []string } -type Options struct { - SpellOrder []string - RSeed int64 - ExitOnOOM bool - - NumBloodlust int - NumDrums int - - Buffs Buffs - Consumes Consumes - Talents Talents - Totems Totems -} - -func (o Options) StatTotal(e Equipment) Stats { - gearStats := e.Stats() - stats := o.BaseStats() - for i := range stats { - stats[i] += gearStats[i] - } - - stats = o.Talents.AddStats(o.Buffs.AddStats(o.Consumes.AddStats(o.Totems.AddStats(stats)))) - - if o.Buffs.BlessingOfKings { - stats[StatInt] *= 1.1 // blessing of kings - } - - // Final calculations - stats[StatSpellCrit] += (stats[StatInt] / 80) / 100 - stats[StatMana] += stats[StatInt] * 15 - fmt.Printf("\fFinal MP5: %f", (stats[StatMP5] + (stats[StatInt] * 0.06))) - - return stats -} - -func (o Options) BaseStats() Stats { - stats := Stats{ - StatInt: 104, // Base - StatMana: 2958, // level 70 shaman - StatLen: 0, - } - return stats -} - -type Totems struct { - TotemOfWrath int - WrathOfAir bool - ManaStream bool -} - -func (tt Totems) AddStats(s Stats) Stats { - s[StatSpellCrit] += 66.24 * float64(tt.TotemOfWrath) - s[StatSpellHit] += 37.8 * float64(tt.TotemOfWrath) - if tt.WrathOfAir { - s[StatSpellDmg] += 104 - } - if tt.ManaStream { - s[StatMP5] += 50 - } - return s -} - -type Talents struct { - LightninOverload int - ElementalPrecision int - NaturesGuidance int - TidalMastery int - ElementalMastery bool - UnrelentingStorm int - CallOfThunder int - Convection int - Concussion int -} - -func (t Talents) AddStats(s Stats) Stats { - s[StatSpellHit] += 25.2 * float64(t.ElementalPrecision) - s[StatSpellHit] += 12.6 * float64(t.NaturesGuidance) - s[StatSpellCrit] += 22.08 * float64(t.TidalMastery) - s[StatSpellCrit] += 22.08 * float64(t.CallOfThunder) - - return s -} - -type Buffs struct { - // Raid buffs - ArcaneInt bool - GiftOftheWild bool - BlessingOfKings bool - ImprovedBlessingOfWisdom bool - - // Party Buffs - Moonkin bool - SpriestDPS int // adds Mp5 ~ 25% (dps*5%*5sec = 25%) - - // Self Buffs - WaterShield bool - WaterShieldPPM int // how many procs per minute does watershield get? Every 3 requires a recast. - - // Target Debuff - JudgementOfWisdom bool -} - -func (b Buffs) AddStats(s Stats) Stats { - if b.ArcaneInt { - s[StatInt] += 40 - } - if b.GiftOftheWild { - s[StatInt] += 18 // assumes improved gotw, rounded down to nearest int... not sure if that is accurate. - } - if b.ImprovedBlessingOfWisdom { - s[StatMP5] += 42 - } - if b.Moonkin { - s[StatSpellCrit] += 110.4 - } - if b.WaterShield { - s[StatMP5] += 50 - } - s[StatMP5] += float64(b.SpriestDPS) * 0.25 - - return s -} - -type Consumes struct { - // Buffs - BrilliantWizardOil bool - MajorMageblood bool - FlaskOfBlindingLight bool - FlaskOfMightyRestoration bool - BlackendBasilisk bool - - // Used in rotations - SuperManaPotion bool - DarkRune bool -} - -func (c Consumes) AddStats(s Stats) Stats { - if c.BrilliantWizardOil { - s[StatSpellCrit] += 14 - s[StatSpellDmg] += 36 - } - if c.MajorMageblood { - s[StatMP5] += 16.0 - } - if c.FlaskOfBlindingLight { - s[StatSpellDmg] += 80 - } - if c.FlaskOfMightyRestoration { - s[StatMP5] += 25 - } - if c.BlackendBasilisk { - s[StatSpellDmg] += 23 - } - return s -} - // New sim contructs a simulator with the given stats / equipment / options. // Technically we can calculate stats from equip/options but want the ability to override those stats // mostly for stat weight purposes. @@ -220,17 +65,31 @@ func NewSim(stats Stats, equip Equipment, options Options) *Simulation { rotIdx = -1 options.SpellOrder = options.SpellOrder[1:] } + rot := make([]int32, len(options.SpellOrder)) + + for i, v := range options.SpellOrder { + for _, sp := range spells { + if sp.Name == v { + rot[i] = sp.ID + break + } + } + } sim := &Simulation{ RotationIdx: rotIdx, Stats: stats, - SpellRotation: options.SpellOrder, + SpellRotation: rot, Options: options, - CDs: map[string]int{}, + CDs: map[int32]int{}, Buffs: Stats{StatLen: 0}, Auras: []Aura{}, Equip: equip, rseed: options.RSeed, rando: rand.New(rand.NewSource(options.RSeed)), + debug: func(a string, v ...interface{}) {}, + } + if IsDebug { + sim.debug = debugFunc(sim) } return sim } @@ -243,7 +102,7 @@ func (sim *Simulation) reset() { sim.CurrentMana = sim.Stats[StatMana] sim.CastingSpell = nil sim.Buffs = Stats{StatLen: 0} - sim.CDs = map[string]int{} + sim.CDs = map[int32]int{} sim.Auras = []Aura{} sim.metrics = SimMetrics{} @@ -264,9 +123,9 @@ func (sim *Simulation) reset() { } } - debug("\nRotation: %v\n", sim.SpellRotation) - debug("Effective MP5: %0.1f\n", sim.Stats[StatMP5]+sim.Buffs[StatMP5]) - debug("----------------------\n") + sim.debug("\nSIM RESET\nRotation: %v\n", sim.SpellRotation) + sim.debug("Effective MP5: %0.1f\n", sim.Stats[StatMP5]+sim.Buffs[StatMP5]) + sim.debug("----------------------\n") } func (sim *Simulation) Run(seconds int) SimMetrics { @@ -274,9 +133,9 @@ func (sim *Simulation) Run(seconds int) SimMetrics { return sim.Run2(seconds) } -func (sim *Simulation) cleanAuraName(name string) { +func (sim *Simulation) cleanAuraName(id int32) { for i := range sim.Auras { - if sim.Auras[i].ID == name { + if sim.Auras[i].ID == id { sim.cleanAura(i) break } @@ -292,7 +151,7 @@ func (sim *Simulation) cleanAura(i int) { sim.Auras[i].OnSpellHit = nil sim.Auras[i].OnExpire = nil - debug(" -removed: %s- ", sim.Auras[i].ID) + sim.debug(" -removed: %s- \n", sim.Auras[i].ID) sim.Auras = sim.Auras[:i+copy(sim.Auras[i:], sim.Auras[i+1:])] } @@ -350,7 +209,7 @@ func (sim *Simulation) ChooseSpell() int { } return cast.TicksUntilCast } else { - debug("Current Mana %0.0f, Cast Cost: %0.0f\n", sim.CurrentMana, cast.ManaCost) + sim.debug("Current Mana %0.0f, Cast Cost: %0.0f\n", sim.CurrentMana, cast.ManaCost) if sim.metrics.OOMAt == 0 { sim.metrics.OOMAt = sim.currentTick / TicksPerSecond sim.metrics.DamageAtOOM = sim.metrics.TotalDamage @@ -367,21 +226,24 @@ func (sim *Simulation) Cast(cast *Cast) { aur.OnCastComplete(sim, cast) } } + sim.debug("Completed Cast (%s)\n", cast.Spell.ID) + dbgCast := cast.Spell.Name if sim.rando.Float64() < cast.Hit { dmg := (float64(sim.rando.Intn(int(cast.Spell.MaxDmg-cast.Spell.MinDmg))) + cast.Spell.MinDmg) + (sim.Stats[StatSpellDmg] * cast.Spell.Coeff) if cast.DidDmg != 0 { // use the pre-set dmg dmg = cast.DidDmg } cast.DidHit = true - dbgCast := "hit" if sim.rando.Float64() < cast.Crit { cast.DidCrit = true dmg *= 2 sim.addAura(AuraElementalFocus(sim.currentTick)) - dbgCast = "crit" + dbgCast += " crit" + } else { + dbgCast += " hit" } - if sim.Options.Talents.Concussion > 0 && (strings.HasPrefix(cast.Spell.ID, "LB") || strings.HasPrefix(cast.Spell.ID, "CL")) { + if sim.Options.Talents.Concussion > 0 && cast.Spell.ID == MagicIDLB12 || cast.Spell.ID == MagicIDCL6 { // Talent Concussion dmg *= 1 + (0.01 * float64(sim.Options.Talents.Concussion)) } @@ -391,7 +253,7 @@ func (sim *Simulation) Cast(cast *Cast) { // For now hardcode the 25% chance resist at 2.5% (this assumes bosses have 0 nature resist) if sim.rando.Float64() < 0.025 { // chance of 25% resist dmg *= .75 - debug("(partial resist)") + dbgCast += " (partial resist)" } cast.DidDmg = dmg // Apply any effects specific to this cast. @@ -404,13 +266,13 @@ func (sim *Simulation) Cast(cast *Cast) { aur.OnSpellHit(sim, cast) } } - debug("%s: %0.0f\n", dbgCast, cast.DidDmg) sim.metrics.TotalDamage += cast.DidDmg sim.metrics.Casts = append(sim.metrics.Casts, cast) } else { - debug("miss.\n") + dbgCast += " miss" } + sim.debug("%s: %0.0f\n", dbgCast, cast.DidDmg) sim.CurrentMana -= cast.ManaCost sim.CastingSpell = nil if cast.Spell.Cooldown > 0 { diff --git a/tbc/sim1.go b/tbc/sim1.go deleted file mode 100644 index 7942a20..0000000 --- a/tbc/sim1.go +++ /dev/null @@ -1,87 +0,0 @@ -package tbc - -// Old fully ticking style simulator. -func (sim *Simulation) Run1(seconds int) SimMetrics { - sim.reset() - - ticks := seconds * TicksPerSecond - for i := 0; i < ticks; i++ { - sim.currentTick = i - sim.Tick1(i) - } - debug("(%0.0f/%0.0f mana)\n", sim.CurrentMana, sim.Stats[StatMana]) - return sim.metrics -} - -func (sim *Simulation) Tick1(tickID int) { - if sim.CurrentMana < 0 { - panic("you should never have negative mana.") - } - - secondID := tickID / TicksPerSecond - // MP5 regen - sim.CurrentMana += ((sim.Stats[StatMP5] + sim.Buffs[StatMP5]) / 5.0) / float64(TicksPerSecond) - - if sim.CurrentMana > sim.Stats[StatMana] { - sim.CurrentMana = sim.Stats[StatMana] - } - - if sim.CastingSpell != nil { - sim.CastingSpell.TicksUntilCast-- // advance state of current cast. - if sim.CastingSpell.TicksUntilCast == 0 { - sim.Cast(sim.CastingSpell) - } - } - - if sim.CastingSpell == nil { - // Pop potion before next cast. - if sim.Stats[StatMana]-sim.CurrentMana+sim.Stats[StatMP5] >= 1500 && sim.CDs["darkrune"] < 1 { - // Restores 900 to 1500 mana. (2 Min Cooldown) - sim.CurrentMana += float64(900 + sim.rando.Intn(1500-900)) - sim.CDs["darkrune"] = 120 * TicksPerSecond - debug("[%d] Used Mana Potion\n", secondID) - } - if sim.Stats[StatMana]-sim.CurrentMana+sim.Stats[StatMP5] >= 3000 && sim.CDs["potion"] < 1 { - // Restores 1800 to 3000 mana. (2 Min Cooldown) - sim.CurrentMana += float64(1800 + sim.rando.Intn(3000-1800)) - sim.CDs["potion"] = 120 * TicksPerSecond - debug("[%d] Used Mana Potion\n", secondID) - } - // Pop any on-use trinkets - - for _, item := range sim.Equip { - if item.Activate == nil || item.ActivateCD == -1 { // ignore non-activatable, and always active items. - continue - } - if sim.CDs[item.CoolID] > 0 { - continue - } - sim.addAura(item.Activate(sim)) - sim.CDs[item.CoolID] = item.ActivateCD * TicksPerSecond - } - - // Choose next spell - sim.ChooseSpell() - if sim.CastingSpell != nil { - debug("[%d] Casting %s (%0.1f) ...", secondID, sim.CastingSpell.Spell.ID, float64(sim.CastingSpell.TicksUntilCast)/float64(TicksPerSecond)) - } - } - - // CDS - for k := range sim.CDs { - sim.CDs[k]-- - if sim.CDs[k] <= 0 { - delete(sim.CDs, k) - } - } - - todel := []int{} - for i := range sim.Auras { - if sim.Auras[i].Expires <= tickID { - todel = append(todel, i) - } - } - for i := len(todel) - 1; i >= 0; i-- { - sim.cleanAura(todel[i]) - } -} diff --git a/tbc/sim2.go b/tbc/sim2.go index f0610b9..b9b949f 100644 --- a/tbc/sim2.go +++ b/tbc/sim2.go @@ -30,8 +30,6 @@ func (sim *Simulation) Run2(seconds int) SimMetrics { // Activates trinkets before spellcasting of off CD. // It will pop mana potions if needed. func (sim *Simulation) Spellcasting(tickID int) int { - secondID := tickID / TicksPerSecond - // technically we dont really need this check with the new advancer. if sim.CastingSpell != nil && sim.CastingSpell.TicksUntilCast == 0 { sim.Cast(sim.CastingSpell) @@ -39,28 +37,28 @@ func (sim *Simulation) Spellcasting(tickID int) int { if sim.CastingSpell == nil { // Activate any specials - if sim.Options.NumBloodlust > 0 && sim.CDs["bl"] < 1 { + if sim.Options.NumBloodlust > 0 && sim.CDs[MagicIDBloodlust] < 1 { sim.addAura(ActivateBloodlust(sim)) sim.Options.NumBloodlust-- // TODO: will this break anything? } - if sim.Options.Talents.ElementalMastery && sim.CDs["elemastery"] < 1 { + if sim.Options.Talents.ElementalMastery && sim.CDs[MagicIDEleMastery] < 1 { // Apply auras sim.addAura(AuraEleMastery()) } // Pop potion before next cast if we have less than the mana provided by the potion minues 1mp5 tick. - if sim.Stats[StatMana]-sim.CurrentMana+sim.Stats[StatMP5] >= 1500 && sim.CDs["darkrune"] < 1 { + if sim.Stats[StatMana]-sim.CurrentMana+sim.Stats[StatMP5] >= 1500 && sim.CDs[MagicIDRune] < 1 { // Restores 900 to 1500 mana. (2 Min Cooldown) sim.CurrentMana += float64(900 + sim.rando.Intn(1500-900)) - sim.CDs["darkrune"] = 120 * TicksPerSecond - debug("[%d] Used Mana Potion\n", secondID) + sim.CDs[MagicIDRune] = 120 * TicksPerSecond + sim.debug("Used Mana Potion\n") } - if sim.Stats[StatMana]-sim.CurrentMana+sim.Stats[StatMP5] >= 3000 && sim.CDs["potion"] < 1 { + if sim.Stats[StatMana]-sim.CurrentMana+sim.Stats[StatMP5] >= 3000 && sim.CDs[MagicIDPotion] < 1 { // Restores 1800 to 3000 mana. (2 Min Cooldown) sim.CurrentMana += float64(1800 + sim.rando.Intn(3000-1800)) - sim.CDs["potion"] = 120 * TicksPerSecond - debug("[%d] Used Mana Potion\n", secondID) + sim.CDs[MagicIDPotion] = 120 * TicksPerSecond + sim.debug("Used Mana Potion\n") } // Pop any on-use trinkets @@ -71,20 +69,20 @@ func (sim *Simulation) Spellcasting(tickID int) int { if sim.CDs[item.CoolID] > 0 { continue } - if item.Slot == EquipTrinket && sim.CDs["trinket"] > 0 { + if item.Slot == EquipTrinket && sim.CDs[MagicIDAllTrinket] > 0 { continue } sim.addAura(item.Activate(sim)) sim.CDs[item.CoolID] = item.ActivateCD * TicksPerSecond if item.Slot == EquipTrinket { - sim.CDs["trinket"] = 30 * TicksPerSecond + sim.CDs[MagicIDAllTrinket] = 30 * TicksPerSecond } } // Choose next spell ticks := sim.ChooseSpell() if sim.CastingSpell != nil { - debug("[%d] Casting %s (%0.1f) ...", secondID, sim.CastingSpell.Spell.ID, float64(sim.CastingSpell.TicksUntilCast)/float64(TicksPerSecond)) + sim.debug("Start Casting %s Cast Time: %0.1fs\n", sim.CastingSpell.Spell.ID, float64(sim.CastingSpell.TicksUntilCast)/float64(TicksPerSecond)) } return ticks } diff --git a/tbc/spells.go b/tbc/spells.go index f9e4ec9..166a05a 100644 --- a/tbc/spells.go +++ b/tbc/spells.go @@ -3,6 +3,7 @@ package tbc type Cast struct { Spell *Spell // Caster ... // Needed for onstruck effects? + isLO bool // stupid hack // Pre-hit Mutatable State TicksUntilCast int @@ -28,8 +29,8 @@ func NewCast(sim *Simulation, sp *Spell, spellDmg, spHit, spCrit float64) *Cast } castTime := sp.CastTime - isLB := sp.ID[0] == 'L' && sp.ID[1] == 'B' - isCL := sp.ID[0] == 'C' && sp.ID[1] == 'L' + isLB := sp.ID == MagicIDLB12 + isCL := sp.ID == MagicIDCL6 if isLB || isCL { // Talent to reduce cast time. @@ -56,7 +57,8 @@ func NewCast(sim *Simulation, sp *Spell, spellDmg, spHit, spCrit float64) *Cast } type Spell struct { - ID string + ID int32 + Name string CastTime float64 Cooldown int Mana float64 @@ -69,7 +71,7 @@ type Spell struct { DotDur float64 } -type DamageType int +type DamageType byte const ( DamageTypeUnknown DamageType = iota @@ -86,17 +88,17 @@ const ( // spells // TODO: DRP == (spellrankavailbetobetrained+11)/70 var spells = []Spell{ - {ID: "LB4", Coeff: 0.795, CastTime: 2.0, MinDmg: 88, MaxDmg: 100, Mana: 50, DamageType: DamageTypeNature}, - {ID: "LB10", Coeff: 0.795, CastTime: 2.5, MinDmg: 428, MaxDmg: 477, Mana: 265, DamageType: DamageTypeNature}, - {ID: "LB12", Coeff: 0.795, CastTime: 2.5, MinDmg: 563, MaxDmg: 643, Mana: 300, DamageType: DamageTypeNature}, - {ID: "CL4", Coeff: 0.643, CastTime: 2, Cooldown: 6, MinDmg: 505, MaxDmg: 564, Mana: 605, DamageType: DamageTypeNature}, - {ID: "CL6", Coeff: 0.643, CastTime: 2, Cooldown: 6, MinDmg: 734, MaxDmg: 838, Mana: 760, DamageType: DamageTypeNature}, - {ID: "ES8", Coeff: 0.3858, CastTime: 1.5, Cooldown: 6, MinDmg: 658, MaxDmg: 692, Mana: 535, DamageType: DamageTypeNature}, - {ID: "FrS5", Coeff: 0.3858, CastTime: 1.5, Cooldown: 6, MinDmg: 640, MaxDmg: 676, Mana: 525, DamageType: DamageTypeFrost}, - {ID: "FlS7", Coeff: 0.15, CastTime: 1.5, Cooldown: 6, MinDmg: 377, MaxDmg: 420, Mana: 500, DotDmg: 100, DotDur: 6, DamageType: DamageTypeFire}, + // {ID: MagicIDLB4, Name: "LB4", Coeff: 0.795, CastTime: 2.0, MinDmg: 88, MaxDmg: 100, Mana: 50, DamageType: DamageTypeNature}, + // {ID: MagicIDLB10, Name: "LB10", Coeff: 0.795, CastTime: 2.5, MinDmg: 428, MaxDmg: 477, Mana: 265, DamageType: DamageTypeNature}, + {ID: MagicIDLB12, Name: "LB12", Coeff: 0.795, CastTime: 2.5, MinDmg: 563, MaxDmg: 643, Mana: 300, DamageType: DamageTypeNature}, + // {ID: MagicIDCL4, Name: "CL4", Coeff: 0.643, CastTime: 2, Cooldown: 6, MinDmg: 505, MaxDmg: 564, Mana: 605, DamageType: DamageTypeNature}, + {ID: MagicIDCL6, Name: "CL6", Coeff: 0.643, CastTime: 2, Cooldown: 6, MinDmg: 734, MaxDmg: 838, Mana: 760, DamageType: DamageTypeNature}, + // {ID: MagicIDES8, Name: "ES8", Coeff: 0.3858, CastTime: 1.5, Cooldown: 6, MinDmg: 658, MaxDmg: 692, Mana: 535, DamageType: DamageTypeNature}, + // {ID: MagicIDFrS5, Name: "FrS5", Coeff: 0.3858, CastTime: 1.5, Cooldown: 6, MinDmg: 640, MaxDmg: 676, Mana: 525, DamageType: DamageTypeFrost}, + // {ID: MagicIDFlS7, Name: "FlS7", Coeff: 0.15, CastTime: 1.5, Cooldown: 6, MinDmg: 377, MaxDmg: 420, Mana: 500, DotDmg: 100, DotDur: 6, DamageType: DamageTypeFire}, } -var spellmap = map[string]*Spell{} +var spellmap = map[int32]*Spell{} func init() { for _, sp := range spells { diff --git a/tbc/stats.go b/tbc/stats.go index 9046d16..80d15e7 100644 --- a/tbc/stats.go +++ b/tbc/stats.go @@ -4,11 +4,11 @@ import ( "strconv" ) -var TicksPerSecond = 30 +const TicksPerSecond = 30 type Stats []float64 -type Stat int +type Stat byte const ( StatInt Stat = iota diff --git a/ui/lib.wasm b/ui/lib.wasm index 42146ca..c7da80f 100755 Binary files a/ui/lib.wasm and b/ui/lib.wasm differ diff --git a/ui/main_wasm.go b/ui/main_wasm.go index 79b965b..ccf3e67 100644 --- a/ui/main_wasm.go +++ b/ui/main_wasm.go @@ -26,20 +26,20 @@ func main() { // GearList reports all items of gear to the UI to display. func GearList(this js.Value, args []js.Value) interface{} { - slot := -1 + slot := byte(128) if len(args) == 1 { - slot = args[0].Int() + slot = byte(args[0].Int()) } gears := "[" for _, v := range tbc.ItemLookup { - if slot != -1 && v.Slot != slot { + if slot != 128 && v.Slot != slot { continue } if len(gears) != 1 { gears += "," } - gears += `{"name":"` + v.Name + `", "slot": ` + strconv.Itoa(v.Slot) + `}` + gears += `{"name":"` + v.Name + `", "slot": ` + strconv.Itoa(int(v.Slot)) + `}` } gears += "]" return gears @@ -185,7 +185,7 @@ type SimResult struct { } type CastMetric struct { - Spell string + Spell int32 Hit bool Crit bool Dmg float64 diff --git a/web.go b/web.go index 3e178c3..db5b466 100644 --- a/web.go +++ b/web.go @@ -1,13 +1,9 @@ package main import ( - "io/ioutil" "log" "net/http" - "strconv" "strings" - - "github.com/lologarithm/wowsim/tbc" ) func init() { @@ -20,55 +16,5 @@ func init() { log.Printf("Serving: %s", req.URL.String()) fs.ServeHTTP(resp, req) }) - http.HandleFunc("/simtbc", simTBCPage) -} - -func simTBCPage(w http.ResponseWriter, r *http.Request) { - fileData, err := ioutil.ReadFile("tbc/ui/index.html") - if err != nil { - log.Fatalf("Failed to read file: %s", err) - } - - // TODO: remove this page because the new UI can just be wired up to endpoints instead of form based. - // This web system would need to implement the functions found in ui/main.go - // Simulate - // GearStats - // GearList - - if r.ContentLength > 0 { - // parse form. - r.ParseForm() - intv, _ := strconv.Atoi(r.FormValue("int")) - sph, _ := strconv.ParseFloat(r.FormValue("spellhit"), 64) - spc, _ := strconv.ParseFloat(r.FormValue("spellcrit"), 64) - spd, _ := strconv.ParseFloat(r.FormValue("spelldmg"), 64) - mp5, _ := strconv.ParseFloat(r.FormValue("mp5"), 64) - haste, _ := strconv.ParseFloat(r.FormValue("haste"), 64) - spp, _ := strconv.ParseFloat(r.FormValue("spellpen"), 64) - - stats := tbc.Stats{ - tbc.StatInt: float64(intv) + 86, // Add base stats - tbc.StatSpellCrit: spc + 151, // Add base+talents to gear - tbc.StatSpellHit: sph/100 + 0.03, // Add talent hit - tbc.StatSpellDmg: spd, // gear - tbc.StatMP5: mp5, // gear - tbc.StatHaste: haste, - tbc.StatMana: 1240, // Base Mana L60 Troll Shaman - tbc.StatSpellPen: spp, // - } - stats[tbc.StatSpellCrit] += (stats[tbc.StatInt] / 59.5) / 100 // 1% crit per 59.5 int - stats[tbc.StatMana] += stats[tbc.StatInt] * 15 - - stats.Print() - - results := runTBCSim(stats, tbc.NewEquipmentSet(), 300, 500, nil) - fileData = append(fileData, "
"...)
-		for _, res := range results {
-			fileData = append(fileData, res...)
-			fileData = append(fileData, "\n"...)
-		}
-		fileData = append(fileData, "
"...) - } - - w.Write(fileData) + // http.HandleFunc("/simtbc", simTBCPage) }