User:Lafore/Enhancement Shaman Simulation

NOTE: A better simulator is EnhSim. I felt like releasing mine even though it's not nearly as complete.

This is a simulator I created for my Shaman in 3.1 or 3.2. It has some problems which I will list. I created it for personal use to try new gear and talent variations. I did my best to make it accurate according to the information available on wowwiki at the time.

Problems:
 * 1) It doesn't take into account armor penetration.
 * 2) You have to enter all your stats into the program's source and recompile, this is related to the next problem:
 * 3) The gui is very spartan.
 * 4) It does not calculate DPS, only damage done.
 * 5) Not enough comments.
 * 6) It wasn't designed to be easily extended.
 * 7) It is outdated.

To use it, install objective caml and TCL/TK 8.4, such as (for windows) ActiveTCL. Mac OS X users already have TCL/TK installed.

compile: ocamlc -o shaman.exe -I +labltk -I +threads unix.cma threads.cma labltk.cma tkthread.cmo shaman.ml

The source is released under the GPLv3 license. Please feel free to improve on it in any way you like! Obviously sharing any modifications is encouraged.

(* simulation section *)

let hz = 50.0

let period = 1.0 /. hz

let hit_rating = 455.0

let crit_rating = 681.0

let haste_rating = 229.0

let expertise = 114

let dodge_reduction = 5.50

let mh_range = (356,535)

let oh_range = (274,509)

let mh_speed = 2.6

let oh_speed = 2.5

let ap = 3567

let armor_piercing = 28

let has_wf_glyph = true

let round n = let int = int_of_float n in let remainder = fst (modf n) in  if remainder >= 0.5 then int + 1 else int

let weapon_hit hand (min,max) speed = let base_hit = float_of_int (Random.int (max-min) + min + ap / 14) *. speed in round (match hand with	 `OH -> base_hit /. 2.0      | `MH -> base_hit    )

let agility = 787.0

let intellect = 494.0

let spellpower = 1344.0

let targetmitigation = 0.30

type talents = {     maelstrom : int; static_shock : int; use_lava_lash : bool; dual_wield_spec : int; unleashed_rage : int; weapon_mastery : int; flurry : int; elemental_weapons : int; thundering_strikes : int; elemental_fury : int; elemental_devestation : int; concussion : int; enhancing_totems : int; improved_shields : int; improved_stormstrike : int; improved_windfury : int; reverberation : int; }

let character = {   maelstrom = 5; static_shock = 0; use_lava_lash = false; dual_wield_spec = 3; unleashed_rage = 5; weapon_mastery = 3; flurry = 5; elemental_weapons = 3; thundering_strikes = 5; elemental_fury = 4; elemental_devestation = 3; concussion = 5; enhancing_totems = 3; improved_shields = 3; improved_stormstrike = 2; improved_windfury = 2; reverberation = 4; }

let melee_hitpercent = (hit_rating /. 32.97 +. float_of_int (character.dual_wield_spec * 2)) /. 100.0

let spell_hitpercent = (hit_rating /. 26.232 +. float_of_int (character.dual_wield_spec * 2)) /. 100.0

let melee_critpercent = 0.05 +. (agility /. 83.3 +. crit_rating /. 45.91) /. 100.0 +. (float_of_int character.thundering_strikes) *. 0.01

let spell_critpercent = 0.26

type character_state = {     last_melee_crit : float; last_spell_crit : float; last_oh_hit : float; last_mh_hit : float; last_melee_hit : float; maelstrom_stack : int; flurry_stack : int; gcd_end : float; shock_cd_end : float; windfury_cd_end : float; stormstrike_cd_end : float; stormstrike_end : float; stormstrike_stack : int; lightning_shield_cd_end : float; flameshock_ticks_remain : int; flameshock_end : float; lightning_shield : int; elemental_devestation_end : float; }

let pick_spell state time = if state.gcd_end > time then `None else if state.maelstrom_stack = 5 then `LB else if state.stormstrike_cd_end < time then `SS else if state.shock_cd_end < time then `ES else `None

let fs_tick = round (139.0 +. 0.2142 *. spellpower)

let fs_hit = 500 + (round (0.2142 *. spellpower))

let es_hit = (849+895)/2 + (round (0.3858 *. spellpower))

let lb_hit = (715+815)/2 + (round (0.7143 *. spellpower))

let ls_hit = 380 + round (spellpower /. 3.0)

let ticks callback state time = if state.flameshock_ticks_remain > 0 then let todo = float state.flameshock_ticks_remain -. (state.flameshock_end -. time) /. 3.0 in   let (frac,int) = modf todo in    if frac < period && int = 1.0 then (callback (`FS_tick fs_tick); {state with flameshock_ticks_remain = state.flameshock_ticks_remain - 1}) else state else state

let melee_swing state time = let zero_fix base bonus round = let minimum = match round with `Up -> 0.0001 | `Down -> 0.0 in   let difference = base -. bonus in   if difference <= minimum then minimum else difference in let miss = zero_fix 0.28 melee_hitpercent `Up in  let dodge = miss +. zero_fix 0.065 dodge_reduction `Down in let glancing = dodge +. 0.25 in let critical = glancing +. 0.05 +. melee_critpercent +. if state.last_spell_crit +. 10.0 > time then 0.1 else 0.0 +.      if state.elemental_devestation_end >= time then 0.03 *. float_of_int character.elemental_devestation else 0.0 in let roll = Random.float (1.0 +. sqrt epsilon_float) in  let hit_type = match roll with x when x <= miss -> `Miss | x when x <= dodge -> `Dodge | x when x <= glancing -> `Glancing | x when x <= critical -> `Critical | _ -> `Hit in match hit_type with `Miss | `Dodge as x -> (state,false,x) | `Hit | `Glancing | `Critical as x -> let state = {state with last_melee_hit = time; last_melee_crit = if x = `Critical then time else state.last_melee_crit; }	in if if state.windfury_cd_end < time then Random.int 5 = 0 else false then ({state with windfury_cd_end = time +. 3.0},true,x) else (state,false,x)

let add_wf (min,max) = let bonus = 1250 / 100 / 14 in (min+bonus,max+bonus)

let stormstrike state time = let (state,wf_proc,hit_type) = melee_swing state time in let state = {state with flurry_stack = if hit_type = `Critical then 3 else state.flurry_stack; stormstrike_stack = 2 + character.improved_stormstrike; stormstrike_cd_end = time +. 10.0 -. float character.improved_stormstrike; stormstrike_end = time +. 12.0;		 windfury_cd_end = if wf_proc then time +. 3.0 else state.windfury_cd_end } in  let mh_damage = weapon_hit `MH (if wf_proc then add_wf mh_range else mh_range) time in  let oh_damage = weapon_hit `OH (if wf_proc then add_wf oh_range else oh_range) time in  (state,   (if wf_proc then 3 else 1) *     (match hit_type with `Glancing -> (mh_damage + oh_damage / 2) * 7 / 10 | `Critical -> (mh_damage + oh_damage / 2) * 2 | `Hit -> mh_damage + oh_damage / 2 | _ -> 0    ),   hit_type  )

let cast callback state time = if Random.float 1.0 < 0.17 -. spell_hitpercent then (     callback `Spell_miss;      state    ) else let crit n = if Random.float 1.0 < spell_critpercent then (true,2 * n) else (false,n) in   match pick_spell state time with `None -> state | `LB -> callback (`LB(crit lb_hit)); {state with maelstrom_stack = 0; gcd_end = time +. 1.5}   | `ES -> callback (`ES(crit lb_hit)); {state with flameshock_end = time +. 6.0; shock_cd_end = time +. 6.0 -. float_of_int character.reverberation *. 0.2; gcd_end = time +. 1.5}   | `FS -> callback (`FS(crit lb_hit)); {state with flameshock_ticks_remain = 4; flameshock_end = time +. 12.0;shock_cd_end = time +. 6.0 -. float_of_int character.reverberation; gcd_end = time +. 1.5}   | `LS -> {state with lightning_shield = 3 + 2 * character.static_shock; gcd_end = time +. 1.5}   | `SS -> let (state,damage,hit_type) = stormstrike state time in	let state = match hit_type with | `Glancing | `Critical | `Hit -> if (Random.float 1.0) < (0.05 *. float character.maelstrom) && hit_type <> `Dodge && hit_type <> `Miss then (callback `MS; {state with maelstrom_stack = state.maelstrom_stack + 1}) else state | _ -> state in let state = {state with gcd_end = time +. 1.5} in	callback (`SS(hit_type,damage)); state

let haste state = let flurry_lookup x = List.assoc x [(0,0.0);(1,0.15);(2,0.20);(3,0.25);(4,0.30);(5,0.35)] in (+.) (if state.flurry_stack > 0 then flurry_lookup character.flurry else 0.0) (haste_rating /. 32.79 /. 100.0)

let melee callback state time = let hand = if state.last_mh_hit +. (mh_speed *. (1.0 -. haste state)) < time then Some `MH else if state.last_oh_hit +. (oh_speed *. (1.0 -. haste state)) < time then Some `OH else None in match hand with None -> state | Some h -> let (state,wf_proc,hit_type) = melee_swing state time in     let range = if h = `MH then mh_range else oh_range in     let damage = let temp = weapon_hit h (if wf_proc then add_wf range else range) (match h with `MH -> mh_speed | `OH -> oh_speed) in	let temp = if wf_proc then 3*temp else temp in	( / ) (match hit_type with	      `Glancing -> temp * 7 / 10	     | `Critical -> temp * 2	     | `Hit -> temp	     | _ -> 0) (if h = `MH then 1 else 2) in     let state = if (Random.float 1.0) < (0.05 *. float character.maelstrom) && hit_type <> `Dodge && hit_type <> `Miss then (callback `MS; {state with maelstrom_stack = state.maelstrom_stack + 1}) else state in     let state = match h with `MH -> {state with last_mh_hit = time} | `OH -> {state with last_oh_hit = time} in     let state = if hit_type = `Critical then {state with flurry_stack = 3} else if state.flurry_stack <= 0 then state else {state with flurry_stack = state.flurry_stack - 1} in     callback (`Melee (damage,wf_proc,hit_type)); state

let step callback state time = let state = ticks callback state time in let state = melee callback state time in  cast callback state time

let init_state = {   last_melee_crit = -10.0; last_spell_crit = -10.0; last_oh_hit = 0.0 -. (oh_speed *. (1.0 -. 0.09)) /. 2.0;   last_mh_hit = -10.0; last_melee_hit = -10.0; maelstrom_stack = 0; flurry_stack = 0; gcd_end = -10.0; shock_cd_end = -10.0; windfury_cd_end = -10.0; stormstrike_cd_end = -10.0; stormstrike_end = -10.0; stormstrike_stack = 0; lightning_shield_cd_end = -10.0; flameshock_ticks_remain = 0; flameshock_end = 0.0; lightning_shield = 0; elemental_devestation_end = -10.0; }

let start_sim hz callback = let rec loop state time = let newstate = step callback state time in   Thread.delay period; loop newstate (time +. period) in loop init_state 0.0

(* UI section *)

open Tk

open Tkthread

let tk = start ;;

Thread.delay 0.1;;

module type EL = sig type t val label : t -> Widget.label Widget.widget val compare : t -> t -> int val get : t -> string val set : t -> string -> unit val seti : t -> int -> unit val setf : t -> float -> unit val addi : t -> int -> unit val create : ?init:string -> Widget.frame Widget.widget -> t end module EventLabel : EL = struct type t = {	tv : Textvariable.textVariable; label : Widget.label Widget.widget; n : int; } let label t = t.label let compare t1 t2 = Pervasives.compare t1.n t2.n let equal t1 t2 = t1.n = t2.n  let hash t = t.n  let get el = sync Textvariable.get el.tv let set el v = async (Textvariable.set el.tv) v let seti el v = async (Textvariable.set el.tv) (string_of_int v) let setf el v = async (Textvariable.set el.tv) (string_of_float v) let addi el v = let old = get el in   let oldi = int_of_string old in    seti el (oldi+v) let counter = ref 0 let create ?(init="0") f = let tv = sync Textvariable.create in    async (Textvariable.set tv) init; let l = {	tv = tv; label = sync (Label.create ~textvariable:tv) f;	n = !counter; }   in    incr counter; l end

module LabeledEventLabel = struct type t = Widget.frame Widget.widget * EventLabel.t let frame (t : t) = fst t  let el (t : t) = snd t  let create ?(init="0") name = let f = sync Frame.create top in   let lel = sync (Label.create ~text:name) f in    let el = sync (EventLabel.create ~init) f in    let ctv = sync Textvariable.create  in    async (Textvariable.set ctv) "0"; let c = sync (Label.create ~textvariable:ctv) f in   async (pack ~side:`Left ~fill:`X) [lel]; async (pack ~side:`Left ~fill:`X) [EventLabel.label el]; async (pack ~side:`Left ~fill:`X) [c]; (f,el,ctv) end

module LEL = LabeledEventLabel

let ui = List.rev [(`Dodge,"Dodge"); (`Miss,"Miss"); (`Glancing,"Glancing"); (`Hit,"Hit"); (`Critical,"Crit"); (`SS,"SS"); (`LB,"LB"); (`ES,"ES"); (`FS,"FS"); (`MS,"Maelstrom"); ]

module EVCollection = Map.Make (struct type t = [`SS|`LB|`ES|`FS|`Hit|`Miss|`Hit|`Dodge|`Critical|`Glancing|`MS] let compare = Pervasives.compare end)

module EVC = EVCollection

let (evFrameStore,evstore) = List.fold_left (fun (frameaccum,mapaccum) (a,name) ->      let (f,el,ctv) = LEL.create name in       (f::frameaccum, EVC.add a (el,ctv) mapaccum)    ) ([],EVC.empty) ui

let update ui d = let (el,ctv) = EVC.find ui evstore in EventLabel.addi el d;  (match ui with       `LB -> let (ms_el,_) = EVC.find `MS evstore in EventLabel.seti ms_el 0     | _ ->   ); let i = int_of_string (sync Textvariable.get ctv) in async (Textvariable.set ctv) (string_of_int (i+1))

let callback = function `FS_tick d -> update `FS d | `SS (c,d) -> update `SS d  | `FS (c,d) -> update `FS d  | `Melee (d,wf,hit_type) -> (	match hit_type with	   `Miss | `Dodge -> update hit_type 0	  | _ -> update hit_type d      ) | `ES (c,d) -> update `ES d | `Spell_miss -> | `LB (c,d) -> update `LB d | `MS -> update `MS 1

let started = ref false

let b = sync (Button.create      ~text:"Start"       ~command:(fun  -> if !started then else (		    Random.self_init ;		     started := true;		     ignore (Thread.create (start_sim hz) callback)		   ) )   )    top;;

async pack evFrameStore;;

async pack [b];;

mainLoop ;;

Thread.join tk;;