SHOW:
|
|
- or go back to the newest paste.
1 | --- | |
2 | title: "vard sim" | |
3 | author: "jeb" | |
4 | date: "8/7/2023" | |
5 | output: html_document | |
6 | --- | |
7 | ||
8 | ```{r constants} | |
9 | # lord forgive me for how sloppy this code becomes the further down you scroll | |
10 | library(dplyr) | |
11 | set.seed(69420) | |
12 | ||
13 | # Fang on slash | |
14 | # 1-handed weapons assumed to be used with Avernic | |
15 | gear_values <- matrix(c(111, 75, 121, 18, 12, 10, 8, 12, 6, 8, 88, 56, 132, 48, | |
16 | 104, 110, 134, 0, 0, 15, 10, 0, 20, 0, 109, 57, 132, 49),ncol=14,byrow=TRUE) | |
17 | colnames(gear_values) <- c("fang", "scythe", "axe", "torva", "bandos", "torture", "fury", "ultor", "bellator", "berserker", "vw", "claws", "bgs", "dds") | |
18 | rownames(gear_values) <- c("strength", "attack") | |
19 | ||
20 | str_level <- floor(118 * 1.23) + 3 + 8 | |
21 | atk_level <- floor(118 * 1.2) + 8 | |
22 | ||
23 | vard_max_hp <- 700 | |
24 | vard_base_def <- 215 | |
25 | vard_slash_def <- 65 | |
26 | vard_def <- vard_base_def | |
27 | vard_lowered_def <- vard_base_def | |
28 | ``` | |
29 | ||
30 | ```{r top level attack functions} | |
31 | # the basic attack roll is the same no matter what weapon you use | |
32 | # returns TRUE on a hit and FALSE on a miss | |
33 | attack <- function(atk_roll_max, vard_hp, weapon) { | |
34 | vard_def <- vard_base_def - (floor((vard_max_hp - vard_hp)/10)) # comment out this line to disable vard's defense decay, useful to compare to DPS calc | |
35 | ||
36 | # BGS mechanics: assume that if the lowered defense is lower than Vard's naturally decaying defense, it takes priority, but does not stack | |
37 | if (vard_lowered_def < vard_def) { | |
38 | vard_def <- vard_lowered_def | |
39 | } | |
40 | ||
41 | def_roll_max <- (vard_def + 9) * (vard_slash_def + 64) | |
42 | ||
43 | # these are not ints but it shouldn't really matter | |
44 | atk_roll <- runif(1, 0, atk_roll_max) | |
45 | def_roll <- runif(1, 0, def_roll_max) | |
46 | ||
47 | if (atk_roll > def_roll) { | |
48 | return (TRUE) | |
49 | } | |
50 | ||
51 | # fang rolls again if the first misses | |
52 | if (weapon == "fang") { | |
53 | atk_roll <- runif(1, 0, atk_roll_max) | |
54 | } | |
55 | ||
56 | return (atk_roll > def_roll) | |
57 | } | |
58 | ||
59 | attack_with <- function(gear, hp) { | |
60 | if (!is.vector(gear) || length(gear) != 4) { | |
61 | stop("must pass gear loadout as a vector of 4 strings") | |
62 | } | |
63 | ||
64 | # assumes infernal cape, ferocious gloves, and prims for a total 27 strength and 22 attack bonus | |
65 | gear_bonuses <- rowSums(gear_values[,gear]) | |
66 | str_bonus <- gear_bonuses[["strength"]] + 27 | |
67 | atk_bonus <- gear_bonuses[["attack"]] + 22 | |
68 | ||
69 | if (gear[1] == "fang") { | |
70 | return (attack_with_fang(str_bonus, atk_bonus, hp)) | |
71 | } else if (gear[1] == "scythe") { | |
72 | return (attack_with_scythe(str_bonus, atk_bonus, hp)) | |
73 | } else if (gear[1] == "axe") { | |
74 | return (attack_with_axe(str_bonus, atk_bonus, hp)) | |
75 | } else { | |
76 | stop("must pass fang, scythe, or axe as weapon parameter") | |
77 | } | |
78 | } | |
79 | ||
80 | attack_with_spec <- function(gear, hp) { | |
81 | # assumes infernal cape, ferocious gloves, and prims for a total 27 strength and 22 attack bonus | |
82 | gear_bonuses <- rowSums(gear_values[,gear]) | |
83 | str_bonus <- gear_bonuses[["strength"]] + 27 | |
84 | atk_bonus <- gear_bonuses[["attack"]] + 22 | |
85 | ||
86 | if (gear[1] == "vw") { | |
87 | return (spec_with_vw(str_bonus, atk_bonus, hp)) | |
88 | } else if (gear[1] == "claws") { | |
89 | return (spec_with_claws(str_bonus, atk_bonus, hp)) | |
90 | } else if (gear[1] == "bgs") { | |
91 | return (spec_with_bgs(str_bonus, atk_bonus, hp)) | |
92 | } else if (gear[1] == "dds") { | |
93 | return (spec_with_dds(str_bonus, atk_bonus, hp)) | |
94 | } else if (gear[1] == "axe") { | |
95 | return (spec_with_axe(str_bonus, atk_bonus, hp)) | |
96 | } else { | |
97 | stop("must pass vw, claws, bgs, dds, or axe as weapon parameter in first element of 'gear' vector") | |
98 | } | |
99 | } | |
100 | ``` | |
101 | ||
102 | ```{r basic autoattacks} | |
103 | attack_with_fang <- function(str_bonus, atk_bonus, vard_hp) { | |
104 | maxhit <- floor((str_level * (str_bonus + 64) + 320)/640) | |
105 | atk_roll_max <- atk_level * (atk_bonus + 64) | |
106 | ||
107 | # fang max hit mechanics | |
108 | minhit <- floor(0.15 * maxhit) | |
109 | maxhit <- maxhit - minhit | |
110 | ||
111 | hit <- attack(atk_roll_max, vard_hp, "fang") | |
112 | ||
113 | damage <- 0 | |
114 | if (hit) { | |
115 | damage <- sample(minhit:maxhit, 1) | |
116 | } | |
117 | return (damage) | |
118 | } | |
119 | ||
120 | attack_with_scythe <- function(str_bonus, atk_bonus, vard_hp) { | |
121 | maxhit <- floor((str_level * (str_bonus + 64) + 320)/640) | |
122 | minhit <- 0 | |
123 | ||
124 | atk_roll_max <- atk_level * (atk_bonus + 64) | |
125 | ||
126 | # against a single 2x2 enemy, scythe hits twice with both hits rolling accuracy independently and the second hit capped at 1/2 the max hit of the first | |
127 | hit <- attack(atk_roll_max, vard_hp, "") | |
128 | damage <- 0 | |
129 | if (hit) { | |
130 | damage <- damage + sample(minhit:maxhit, 1) | |
131 | } | |
132 | hit <- attack(atk_roll_max, vard_hp, "") | |
133 | if (hit) { | |
134 | damage <- damage + sample(minhit:floor(maxhit/2), 1) | |
135 | } | |
136 | return (damage) | |
137 | } | |
138 | ||
139 | attack_with_axe <- function(str_bonus, atk_bonus, vard_hp) { | |
140 | # axe passive boosts strength additively with prayer | |
141 | str_level <- floor(118 * (1.23 + 0.06 * axe_stacks)) + 3 + 8 | |
142 | maxhit <- floor((str_level * (str_bonus + 64) + 320)/640) | |
143 | minhit <- 0 | |
144 | ||
145 | atk_roll_max <- atk_level * (atk_bonus + 64) | |
146 | hit <- attack(atk_roll_max, vard_hp, "") | |
147 | ||
148 | damage <- 0 | |
149 | if (hit) { | |
150 | damage <- sample(minhit:maxhit, 1) | |
151 | ||
152 | } | |
153 | if (axe_stacks < 5) { | |
154 | axe_stacks <<- axe_stacks + 1 | |
155 | } | |
156 | return (damage) | |
157 | } | |
158 | ``` | |
159 | ||
160 | ```{r special attacks} | |
161 | spec_with_vw <- function(str_bonus, atk_bonus, vard_hp) { | |
162 | # voidwaker spec guarantees a hit between 50-150% of its max hit | |
163 | maxhit <- floor((str_level * (str_bonus + 64) + 320)/640) | |
164 | minhit <- floor(maxhit * 0.5) | |
165 | maxhit <- maxhit + minhit | |
166 | ||
167 | damage <- sample(minhit:maxhit, 1) | |
168 | ||
169 | return (damage) | |
170 | } | |
171 | ||
172 | spec_with_claws <- function(str_bonus, atk_bonus, vard_hp) { | |
173 | maxhit <- floor((str_level * (str_bonus + 64) + 320)/640) | |
174 | minhit <- 0 | |
175 | ||
176 | atk_roll_max <- atk_level * (atk_bonus + 64) | |
177 | ||
178 | # Claws roll accuracy up to four times depending on how many hitsplats miss | |
179 | # The following is a DPS approximation from the wiki | |
180 | # With this implementation it appears to outperform VW which should not be the case | |
181 | # but I cba to fix it since we don't know the exact mechanics | |
182 | hit <- attack(atk_roll_max, vard_hp, "") | |
183 | if (hit) { | |
184 | minhit <- maxhit | |
185 | maxhit <- 2 * maxhit | |
186 | } else { | |
187 | hit <- attack(atk_roll_max, vard_hp, "") | |
188 | if (hit) { | |
189 | minhit <- floor(0.75 * maxhit) | |
190 | maxhit <- floor(1.75 * maxhit) | |
191 | } else { | |
192 | hit <- attack(atk_roll_max, vard_hp, "") | |
193 | if (hit) { | |
194 | minhit <- floor(0.5 * maxhit) | |
195 | maxhit <- floor(1.5 * maxhit) | |
196 | } else { | |
197 | hit <- attack(atk_roll_max, vard_hp, "") | |
198 | if (hit) { | |
199 | minhit <- floor(0.25 * maxhit) | |
200 | maxhit <- floor(1.25 * maxhit) | |
201 | } | |
202 | } | |
203 | } | |
204 | } | |
205 | ||
206 | damage <- sample(minhit:maxhit, 1) | |
207 | return (damage) | |
208 | } | |
209 | ||
210 | spec_with_bgs <- function(str_bonus, atk_bonus, vard_hp) { | |
211 | maxhit <- floor((str_level * (str_bonus + 64) + 320)/640) | |
212 | minhit <- 0 | |
213 | ||
214 | atk_roll_max <- atk_level * (atk_bonus + 64) | |
215 | ||
216 | # BGS spec does 21% more damage and doubles the maximum attack roll | |
217 | maxhit <- floor(maxhit * 1.21) | |
218 | atk_roll_max <- atk_roll_max * 2 | |
219 | ||
220 | hit <- attack(atk_roll_max, vard_hp, "") | |
221 | damage <- 0 | |
222 | if (hit) { | |
223 | damage <- sample(minhit:maxhit, 1) | |
224 | } | |
225 | vard_lowered_def <<- vard_def - damage | |
226 | return (damage) | |
227 | } | |
228 | ||
229 | spec_with_dds <- function(str_bonus, atk_bonus, vard_hp) { | |
230 | maxhit <- floor((str_level * (str_bonus + 64) + 320)/640) | |
231 | minhit <- 0 | |
232 | ||
233 | atk_roll_max <- atk_level * (atk_bonus + 64) | |
234 | ||
235 | # DDS spec does 15% more damage with a 15% higher maximum attack roll | |
236 | maxhit <- floor(maxhit * 1.15) | |
237 | atk_roll_max <- atk_roll_max * 1.15 | |
238 | ||
239 | hit <- attack(atk_roll_max, vard_hp, "") | |
240 | damage <- 0 | |
241 | if (hit) { | |
242 | damage <- damage + sample(minhit:maxhit, 1) | |
243 | } | |
244 | hit <- attack(atk_roll_max, vard_hp, "") | |
245 | if (hit) { | |
246 | damage <- damage + sample(minhit:maxhit, 1) | |
247 | } | |
248 | return (damage) | |
249 | } | |
250 | ||
251 | spec_with_axe <- function(str_bonus, atk_bonus, vard_hp) { | |
252 | # axe spec boosts strength additively with prayer | |
253 | str_level <- floor(118 * (1.23 + 0.06 * axe_stacks)) + 3 + 8 | |
254 | maxhit <- floor((str_level * (str_bonus + 64) + 320)/640) | |
255 | minhit <- 0 | |
256 | ||
257 | atk_roll_max <- atk_level * (atk_bonus + 64) | |
258 | ||
259 | # axe spec increases the maximum attack roll by 6% per stack | |
260 | atk_roll_max <- atk_roll_max * (1 + 0.06 * axe_stacks) | |
261 | ||
262 | hit <- attack(atk_roll_max, vard_hp, "") | |
263 | damage <- 0 | |
264 | if (hit) { | |
265 | damage <- sample(minhit:maxhit, 1) | |
266 | } | |
267 | axe_stacks <<- 0 | |
268 | return (damage) | |
269 | } | |
270 | ``` | |
271 | ||
272 | ```{r farming Vardorvis} | |
273 | # given one of the kill_vard functions below, | |
274 | # a gear setup of weapon, armor, ring, and optional spec weapon, | |
275 | # and a total N number of kills to be done, | |
276 | # returns the average time (in seconds) to kill Vard with that setup | |
277 | farm_vard <- function(FUN, gear, N) { | |
278 | TTK <- 0 | |
279 | for(i in 1:N) { | |
280 | TTK <- TTK + FUN(gear) | |
281 | } | |
282 | return(TTK / N) | |
283 | } | |
284 | ||
285 | # given a gear setup, kills vard once | |
286 | kill_vard <- function(gear) { | |
287 | time <- 0 | |
288 | hp <- vard_max_hp | |
289 | vard_lowered_def <- vard_base_def | |
290 | axe_stacks <<- 0 | |
291 | ||
292 | while (hp > 0) { | |
293 | hp <- hp - attack_with(gear, hp) | |
294 | time <- time + 5 | |
295 | } | |
296 | # the last hit kills vard instantly, we do not need to wait for its cooldown | |
297 | time <- time - 4 | |
298 | return (time*0.6) | |
299 | } | |
300 | ||
301 | # given a gear setup and either vw or claws as gear[5], kills vard once by speccing immediately | |
302 | kill_vard_spec_start <- function(gear) { | |
303 | time <- 0 | |
304 | hp <- vard_max_hp | |
305 | vard_lowered_def <<- vard_base_def | |
306 | axe_stacks <<- 0 | |
307 | ||
308 | hp <- hp - attack_with_spec(gear[c(5, 2, 3, 4)], hp) | |
309 | time <- time + 4 | |
310 | if(gear[5] == "bgs") { | |
311 | time <- time + 2 | |
312 | } | |
313 | ||
314 | while (hp > 0) { | |
315 | hp <- hp - attack_with(gear[-5], hp) | |
316 | time <- time + 5 | |
317 | } | |
318 | time <- time - 4 | |
319 | return (time*0.6) | |
320 | } | |
321 | ||
322 | # given a gear setup and either vw or claws as gear[5], kills vard once by speccing once vard is < 200 HP | |
323 | kill_vard_spec_enrage <- function(gear) { | |
324 | time <- 0 | |
325 | hp <- vard_max_hp | |
326 | vard_lowered_def <<- vard_base_def | |
327 | axe_stacks <<- 0 | |
328 | has_specced <- FALSE | |
329 | while (hp > 0) { | |
330 | hp <- hp - attack_with(gear[-5], hp) | |
331 | time <- time + 5 | |
332 | if (hp < 200 && !has_specced) { | |
333 | hp <- hp - attack_with_spec(gear[c(5, 2, 3, 4)], hp) | |
334 | time <- time + 4 | |
335 | has_specced <- TRUE | |
336 | } | |
337 | } | |
338 | time <- time - 4 | |
339 | return (time*0.6) | |
340 | } | |
341 | ||
342 | # axe spec into spec weapon into scythe switch, supposedly "optimal" | |
343 | kill_vard_optimal <- function(gear) { | |
344 | time <- 0 | |
345 | hp <- vard_max_hp | |
346 | vard_lowered_def <<- vard_base_def | |
347 | axe_stacks <<- 0 | |
348 | while (hp > 150) { | |
349 | hp <- hp - attack_with(gear[-5], hp) | |
350 | time <- time + 5 | |
351 | } | |
352 | # axe spec (maxes 77) | |
353 | hp <- hp - attack_with_spec(gear[c(1, 2, 3, 4)], hp) | |
354 | time <- time + 5 | |
355 | if(hp <= 0) { | |
356 | time <- time - 4 | |
357 | return (time*0.6) | |
358 | } | |
359 | ||
360 | # vw spec (maxes 76) | |
361 | hp <- hp - attack_with_spec(gear[c(5, 2, 3, 4)], hp) | |
362 | time <- time + 4 | |
363 | if(hp <= 0) { | |
364 | time <- time - 3 | |
365 | return (time*0.6) | |
366 | } | |
367 | ||
368 | #finish with scythe | |
369 | gear <- c("scythe", "torva", "bellator", "fury") | |
370 | while (hp > 0) { | |
371 | hp <- hp - attack_with(gear, hp) | |
372 | time <- time + 5 | |
373 | } | |
374 | time <- time - 4 | |
375 | return (time*0.6) | |
376 | } | |
377 | ||
378 | - | # |
378 | + | # Kills vard switching the main weapon to switchwep at a certain HP threshold. Tests every threshold from 100-200 in multiples of 10 |
379 | # and returns the fastest TTK as well as the optimal threshold to switch weapons | |
380 | farm_vard_switch_wep <- function(gear, N, switchwep) { | |
381 | HPs <- seq(160, 160, 10) | |
382 | fastest_TTK <- 69420 | |
383 | fastest_threshold <- 0 | |
384 | ||
385 | for(index in 1:length(HPs)) { | |
386 | TTK <- 0 | |
387 | for(i in 1:N) { | |
388 | TTK <- TTK + kill_vard_switch_wep(gear, HPs[index], switchwep) | |
389 | } | |
390 | if(TTK / N < fastest_TTK) { | |
391 | fastest_TTK <- TTK / N | |
392 | fastest_threshold <- HPs[index] | |
393 | } | |
394 | } | |
395 | ||
396 | return(c(fastest_TTK, fastest_threshold)) | |
397 | } | |
398 | ||
399 | kill_vard_switch_wep <- function(gear, threshold, switchwep) { | |
400 | time <- 0 | |
401 | hp <- vard_max_hp | |
402 | vard_lowered_def <<- vard_base_def | |
403 | axe_stacks <<- 0 | |
404 | ||
405 | while (hp > threshold) { | |
406 | hp <- hp - attack_with(gear, hp) | |
407 | time <- time + 5 | |
408 | } | |
409 | gear <- c(switchwep, "torva", "bellator", "fury") | |
410 | while (hp > 0) { | |
411 | hp <- hp - attack_with(gear, hp) | |
412 | time <- time + 5 | |
413 | } | |
414 | time <- time - 4 | |
415 | return (time*0.6) | |
416 | } | |
417 | ``` | |
418 | ||
419 | ```{r use cases} | |
420 | N <- 50000 | |
421 | sets <- data.frame( | |
422 | Weapon = rep(c("fang", "scythe", "axe"), each=6), | |
423 | Armor = rep(rep(c("torva", "bandos"), each=3), 3), | |
424 | Ring = rep(c("bellator", "ultor", "berserker"), 6), | |
425 | stringsAsFactors=FALSE | |
426 | ) | |
427 | ||
428 | # This test compares all 18 combinations of weapon, armor, and ring in the dataframe 'sets' and runs N Vardorvis kills each, storing the results in 'sets' | |
429 | # TTK: The average time to kill Vardorvis, in seconds | |
430 | # DPS: The average damage per second | |
431 | TTK <- numeric(nrow(sets)) | |
432 | DPS <- numeric(nrow(sets)) | |
433 | for (i in 1:nrow(sets)) { | |
434 | gear <- c(t(sets[i,]), "fury") | |
435 | time <- farm_vard(kill_vard, gear, N) | |
436 | TTK[i] <- time | |
437 | DPS[i] <- vard_max_hp / time | |
438 | cat(sprintf("Setup %s, %s, %s takes %#.2f seconds\n", gear[1], gear[2], gear[3], time)) | |
439 | } | |
440 | sets2 <- cbind(sets, TTK, DPS) | |
441 | ||
442 | # This test assumes torva + bellator, and tests using 1 dragon claws or 1 voidwaker spec along with the fang, scythe, or axe | |
443 | # For each combination, it tests the average TTK if you spec at the start of the fight, or if you spec when Vard falls below 200 HP (roughly when he enrages) | |
444 | sets_spec <- data.frame( | |
445 | Weapon = rep(c("fang", "scythe", "axe"), each=3), | |
446 | Spec = rep(c("vw", "claws", "bgs"), 3), | |
447 | stringsAsFactors=FALSE | |
448 | ) | |
449 | TTK_spec_start <- numeric(nrow(sets_spec)) | |
450 | DPS_spec_start <- numeric(nrow(sets_spec)) | |
451 | TTK_spec_enrage <- numeric(nrow(sets_spec)) | |
452 | DPS_spec_enrage <- numeric(nrow(sets_spec)) | |
453 | for (i in 1:nrow(sets_spec)) { | |
454 | gear <- c(sets_spec[i,1], "torva", "bellator", "fury", sets_spec[i,2]) | |
455 | time <- farm_vard(kill_vard_spec_start, gear, N) | |
456 | TTK_spec_start[i] <- time | |
457 | DPS_spec_start[i] <- vard_max_hp / time | |
458 | cat(sprintf("Setup %s, %s (spec start), takes %#.2f seconds\n", gear[1], gear[5], time)) | |
459 | ||
460 | if(gear[5] == "bgs") | |
461 | next | |
462 | ||
463 | time <- farm_vard(kill_vard_spec_enrage, gear, N) | |
464 | TTK_spec_enrage[i] <- time | |
465 | DPS_spec_enrage[i] <- vard_max_hp / time | |
466 | cat(sprintf("Setup %s, %s (spec enrage), takes %#.2f seconds\n", gear[1], gear[5], time)) | |
467 | } | |
468 | ||
469 | sets_spec <- cbind(sets_spec, TTK_spec_start, TTK_spec_enrage, old_TTK=sets$TTK[rep(c(1, 7, 13), each=3)]) | |
470 | sets_spec <- sets_spec %>% mutate(timesave = old_TTK - TTK_spec_start) | |
471 | ||
472 | gear <- c("axe", "torva", "bellator", "fury", "vw") | |
473 | time <- farm_vard(kill_vard_spec_enrage, gear, N) | |
474 | cat(sprintf("Setup %s, %s (spec enrage, axe spec), takes %#.2f seconds\n", gear[1], gear[5], time)) | |
475 | ||
476 | gear <- c("axe", "torva", "bellator", "fury") | |
477 | time <- farm_vard(kill_vard, gear, N) # manually change starting axe_stacks to 3 | |
478 | cat(sprintf("Setup %s (start with 3 axe stacks), takes %#.2f seconds\n", gear[1], time)) | |
479 | ||
480 | # Axe spec maxes 77 and VW maxes 76. send both specs at 150 HP and finish with scythe | |
481 | gear <- c("axe", "torva", "bellator", "fury", "vw") | |
482 | time <- farm_vard(kill_vard_optimal, gear, N) | |
483 | cat(sprintf("Setup %s (at 150 HP, axe spec into VW spec into scythe), takes %#.2f seconds\n", gear[1], time)) | |
484 | ||
485 | gear <- c("fang", "bandos", "berserker", "fury", "bgs") | |
486 | time <- farm_vard(kill_vard_spec_start, gear, N) | |
487 | cat(sprintf("Setup %s, %s (spec start), takes %#.2f seconds\n", gear[1], gear[5], time)) | |
488 | - | # This test |
488 | + | |
489 | # This test finds the optimal threshold at which to switch from fang to scythe and returns the average kill time with this strat | |
490 | gear <- c("fang", "torva", "bellator", "fury") | |
491 | ret <- farm_vard_switch_wep(gear, 50000, "scythe") | |
492 | switch_scythe_ttk <- ret[1] | |
493 | switch_scythe_threshold <- ret[2] | |
494 | cat(sprintf("The optimal HP to switch from fang to scythe is at %d HP. The average time to kill is %f\n", ret[2], ret[1])) | |
495 | ``` |