-
Notifications
You must be signed in to change notification settings - Fork 774
Expand file tree
/
Copy pathproc_rng.hpp
More file actions
320 lines (253 loc) · 8.88 KB
/
Copy pathproc_rng.hpp
File metadata and controls
320 lines (253 loc) · 8.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// ==========================================================================
// Dedmonwakeen's Raid DPS/TPS Simulator.
// Send questions to natehieter@gmail.com
// ==========================================================================
#pragma once
#include "config.hpp"
#include "sc_enums.hpp"
#include "util/timespan.hpp"
#include <functional>
#include <string>
#include <utility>
struct action_state_t;
struct item_t;
struct player_t;
struct sim_t;
struct spell_data_t;
namespace rng {
struct rng_t;
}
enum class reset_type_e : int
{
COMBAT = 0,
ITERATION = 1
};
struct proc_rng_t
{
protected:
std::string name_str;
player_t* player;
const rng_type_e rng_type_;
public:
static constexpr rng_type_e rng_type = RNG_NONE;
proc_rng_t( rng_type_e type_ );
proc_rng_t( rng_type_e type_, std::string_view n, player_t* p );
virtual ~proc_rng_t() = default;
virtual int trigger( action_state_t* = nullptr ) = 0;
virtual void reset( reset_type_e reset_type ) = 0;
std::string_view name() const
{ return name_str; }
rng_type_e type() const
{ return rng_type_; }
};
struct simple_proc_t final : public proc_rng_t
{
private:
double chance;
public:
static constexpr rng_type_e rng_type = RNG_SIMPLE;
simple_proc_t( std::string_view n, player_t* p, double c = 0.0 );
void reset( reset_type_e /* reset_type */ ) override {}
int trigger( action_state_t* = nullptr ) override;
};
// "Real" 'Procs per Minute' helper class =====================================
struct real_ppm_t final : public proc_rng_t
{
enum blp : int
{
BLP_DISABLED = 0,
BLP_ENABLED
};
private:
double freq;
double modifier;
double rppm;
timespan_t last_trigger_attempt;
timespan_t accumulated_blp;
unsigned scales_with;
blp blp_state;
static constexpr timespan_t max_interval = 3.5_s;
static constexpr timespan_t max_bad_luck_prot = 1000_s;
public:
static constexpr rng_type_e rng_type = RNG_RPPM;
real_ppm_t( std::string_view n, player_t* p, double f = 0, double mod = 1.0, unsigned s = RPPM_NONE,
blp b = BLP_ENABLED );
real_ppm_t( std::string_view n, player_t* p, const spell_data_t* data, const item_t* item = nullptr );
double proc_chance();
void reset( reset_type_e reset_type ) override;
int trigger( action_state_t* = nullptr) override;
void set_scaling( unsigned s )
{ scales_with = s; }
void set_modifier( double mod )
{ modifier = mod; rppm = freq * modifier; }
void set_frequency( double frequency )
{ freq = frequency; rppm = freq * modifier; }
void set_blp_state( blp state )
{ blp_state = state; }
unsigned get_scaling() const
{ return scales_with; }
double get_frequency() const
{ return freq; }
double get_modifier() const
{ return modifier; }
double get_rppm() const
{ return rppm; }
blp get_blp_state() const
{ return blp_state; }
timespan_t get_last_trigger_attempt()
{ return last_trigger_attempt; }
void set_last_trigger_attempt( timespan_t ts )
{ last_trigger_attempt = ts; }
void set_accumulated_blp( timespan_t ts )
{ accumulated_blp = ts; }
};
// Extended "Deck of Cards" to support multiple success/failure types
// Described at https://www.reddit.com/r/wow/comments/6j2wwk/wow_class_design_ama_june_2017/djb8z68/
enum shuffled_rng_e : int
{
FAIL = 0,
SUCCESS = 1
};
struct shuffled_rng_t : public proc_rng_t
{
using initializer = std::initializer_list<std::pair<int, int>>;
private:
void init( initializer data );
protected:
std::vector<int>::iterator position;
std::vector<int> entries;
public:
static constexpr rng_type_e rng_type = RNG_SHUFFLE;
shuffled_rng_t( std::string_view n, player_t* p, initializer data );
shuffled_rng_t( std::string_view n, player_t* p, int success_entries = 0, int total_entries = 0 );
void reset( reset_type_e reset_type ) override;
int trigger( action_state_t* = nullptr ) override;
int count_remains( int key );
int entry_remains();
};
namespace prd {
// Computes the expected number of attempts before getting a proc given the
// PRD constant C and a cap K after which the proc is guaranteed. Returns the
// expectation and its derivative with respect to C.
constexpr std::pair<double, double> expected_attempts( double C, unsigned K )
{
double chain = 1.0; // Chance of getting a chain of unsuccessful procs
double d_chain = 0.0; // and its derivative
double expected = 0.0;
double d_expected = 0.0;
for ( unsigned i = 1; i <= K; i++ )
{
expected += chain;
d_expected += d_chain;
double p = i * C;
if ( p >= 1.0 )
break;
d_chain = d_chain * ( 1 - p ) - chain * i; // Product rule
chain *= 1 - p;
}
return { expected, d_expected };
}
// Finds the PRD constant C given the average proc rate p and a cap K
// after which a proc is guaranteed. K == 0 is treated as no cap.
constexpr double find_constant( double p, unsigned K = 0 )
{
// This isn't strictly speaking correct for really small p values
// (< 0.05%) but such values aren't realistic in the sim setting.
constexpr unsigned max_K = 100000;
if ( K == 0 || K > max_K )
K = max_K;
if ( p <= 0.0 )
return 0.0;
if ( p <= 1.0 / K )
return 1e-300; // Non-zero value to indicate that procs can occur
if ( p >= 1.0 )
return 1.0;
double tgt_expected = 1.0 / p;
// Initial guess that works well for normal p and K values.
double guess = std::min( 1.25 * p * p, 0.99 );
constexpr int max_iterations = 20;
constexpr double precision = 1e-12;
for ( int i = 0; i < max_iterations; i++ )
{
auto [ expected, d_expected ] = expected_attempts( guess, K );
double error = expected - tgt_expected;
// TODO: Change to std::fabs when we switch to C++ version where it's constexpr
if ( error < precision && -error < precision )
break;
// Newton's method update
guess -= error / d_expected;
}
return guess;
}
} // namespace prd
// Accumulated back luck protection rng helper class ==========================
//
// This class of rng will increase the chance of success with each failed trigger. By default, the chance of success is
// given by:
//
// % success = proc_chance * trigger_count
//
// where trigger_count is the number of trigger attempts since the last success, including the current attempt.
// The first trigger attempt after a successful proc will have a trigger count of 1.
//
// cap is an optional parameter that sets the maximum number of attempts before the proc is guaranteed. If set
// to a nonzero value, it guarantees a proc when trigger_count == cap (even if proc_chance * trigger_count < 1).
//
// accumulator_fn is an optional functor that takes the proc chance and current trigger count and returns the chance of
// success. Overrides the previous cap behavior if present.
//
// initial_count is an optional parameter that sets the initial trigger count. If initial_count is set, the first
// trigger after a successful proc will have a trigger count of initial_count + 1.
using accumulated_rng_fn = std::function<double( double, unsigned, action_state_t* )>;
// Extra information about the outcome of accumulated_rng_t::trigger.
enum accumulated_rng_e : int
{
ARNG_FAIL = 0,
ARNG_SUCCESS = 1,
ARNG_GUARANTEED = 2
};
struct accumulated_rng_t : public proc_rng_t
{
private:
accumulated_rng_fn accumulator_fn;
double proc_chance;
unsigned max_count;
unsigned initial_count;
unsigned trigger_count;
public:
static constexpr rng_type_e rng_type = RNG_ACCUMULATE;
accumulated_rng_t( std::string_view n, player_t* p, double c, unsigned cap = 0,
accumulated_rng_fn fn = nullptr, unsigned initial_count = 0 );
void reset( reset_type_e reset_type ) override;
int trigger( action_state_t* = nullptr ) override;
};
// Threshold RNG rng helper class ==========================
//
// This accumulates an incremental value, returning a successful trigger when the accumulated value goes over 1.
//
// roll_over is an optional parameter, default false. if set the accumulated amount is decremented by 1, else reset to 0
// for the next trigger.
//
// random_initial_state is an optional parameter, default true, that indicates the RNG will start somewhere between [0,1)
// upon reset();
//
// accumulator_fn is an optional functor that takes the increment_max value as a parameter and returns the amount accumulated
// by that call.
using threshold_rng_fn = std::function<double( double, action_state_t* )>;
struct threshold_rng_t : public proc_rng_t
{
private:
threshold_rng_fn accumulator_fn;
double increment_max;
double accumulated_chance;
bool random_initial_state;
bool roll_over;
public:
static constexpr rng_type_e rng_type = RNG_THRESHOLD;
threshold_rng_t( std::string_view n, player_t* p, double increment_max, threshold_rng_fn fn = nullptr,
bool random_initial_state = true, bool roll_over = false );
void reset( reset_type_e reset_type ) override;
int trigger( action_state_t* = nullptr ) override;
double get_accumulated_chance();
double get_increment_max();
};