-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathRateEngine.cs
More file actions
170 lines (149 loc) · 6.36 KB
/
Copy pathRateEngine.cs
File metadata and controls
170 lines (149 loc) · 6.36 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
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace DropRateChanger
{
// Current min/max of a rate column across the file.
public struct RateRange
{
public bool Has;
public decimal Min;
public decimal Max;
}
// Pure, UI-free transform logic for S_DropItem.ini so it can be unit-tested
// independently of the WinForms front end.
//
// Column layout of a drop entry:
// Max Drop = column 6 (single, per row)
// Drop Number = columns 11, 15, 19 … (one per drop slot, +4)
// Drop Luck = columns 12, 16, 20 … (one per drop slot, +4)
//
// The apply loop walks EVERY column up to the header's declared count, so a
// multiplier hits every drop slot of every item — not just the first entry.
public static class RateEngine
{
public const int MaxDropCol = 6;
public const int DropNumberCol = 11;
public const int DropLuckCol = 12;
public static string[] Split(string line)
{
if (string.IsNullOrEmpty(line))
return new[] { "0", "0" };
return line.Split('|');
}
private static bool AnyDigit(string s)
{
for (int i = 0; i < s.Length; i++)
if (char.IsDigit(s[i])) return true;
return false;
}
// Returns true for any column that carries a drop rate we may edit.
public static bool IsRateColumn(int i)
{
return i == MaxDropCol
|| (i >= DropNumberCol && (i - DropNumberCol) % 4 == 0)
|| (i >= DropLuckCol && (i - DropLuckCol) % 4 == 0);
}
// Rebuilds one row, multiplying each rate column by its multiplier. Values
// are truncated/padded to the header's declared column count and separated
// and terminated by '|', matching the game's format.
// Luck is a drop chance (0..100 = 0..100%). When capLuck is true, a
// multiplied luck value is clamped to 100 so it never exceeds 100%.
public const decimal LuckCap = 100m;
public static string ApplyRates(string line, int cols, float dropNum, float dropLuck, float maxDrop, bool capLuck)
{
string[] rd = Split(line);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < cols; i++)
{
string val = i < rd.Length ? rd[i] : "";
if (i == MaxDropCol && maxDrop != 1f && AnyDigit(val))
{
int m;
int.TryParse(val, out m);
val = Convert.ToInt32(m * maxDrop).ToString();
}
else if (i >= DropNumberCol && (i - DropNumberCol) % 4 == 0 && dropNum != 1f && AnyDigit(val))
{
int m;
int.TryParse(val, out m);
val = Convert.ToInt32(m * dropNum).ToString();
}
else if (i >= DropLuckCol && (i - DropLuckCol) % 4 == 0 && dropLuck != 1f && val.Trim() != "")
{
decimal content = decimal.Parse(val, CultureInfo.InvariantCulture.NumberFormat);
content = content * (decimal)dropLuck;
if (capLuck && content > LuckCap)
content = LuckCap;
val = content.ToString("0.00", CultureInfo.InvariantCulture.NumberFormat);
}
sb.Append(val);
sb.Append('|');
}
return sb.ToString();
}
// Applies the multipliers to every data row (line 0 is the header and is
// left unchanged). Reports 0..100 via the optional progress callback.
public static List<string> ApplyAll(List<string> lines, float dropNum, float dropLuck, float maxDrop, bool capLuck, Action<int> progress)
{
int cols = int.Parse(Split(lines[0])[2]);
List<string> outLines = new List<string>(lines.Count);
outLines.Add(lines[0]);
int total = lines.Count;
int lastPct = -1;
for (int k = 1; k < lines.Count; k++)
{
string ln = lines[k];
if (string.IsNullOrEmpty(ln) || ln.IndexOf('|') < 0)
outLines.Add(ln);
else
outLines.Add(ApplyRates(ln, cols, dropNum, dropLuck, maxDrop, capLuck));
if (progress != null)
{
int pct = (int)((long)k * 100 / total);
if (pct != lastPct) { lastPct = pct; progress(pct); }
}
}
if (progress != null && lastPct != 100)
progress(100);
return outLines;
}
// Current min/max for each rate across all data rows.
public static void ComputeRanges(List<string> lines, out RateRange number, out RateRange luck, out RateRange maxdrop)
{
int cols = int.Parse(Split(lines[0])[2]);
RateRange n = new RateRange(), l = new RateRange(), m = new RateRange();
for (int k = 1; k < lines.Count; k++)
{
string[] rd = Split(lines[k]);
if (rd.Length < 2) continue;
for (int i = 0; i < cols && i < rd.Length; i++)
{
string v = rd[i];
if (i == MaxDropCol)
{
int iv;
if (int.TryParse(v, out iv)) Acc(ref m, iv);
}
else if (i >= DropNumberCol && (i - DropNumberCol) % 4 == 0)
{
int iv;
if (int.TryParse(v, out iv)) Acc(ref n, iv);
}
else if (i >= DropLuckCol && (i - DropLuckCol) % 4 == 0 && v.Trim() != "")
{
decimal dv;
if (decimal.TryParse(v, NumberStyles.Any, CultureInfo.InvariantCulture, out dv)) Acc(ref l, dv);
}
}
}
number = n; luck = l; maxdrop = m;
}
private static void Acc(ref RateRange r, decimal v)
{
if (!r.Has) { r.Has = true; r.Min = r.Max = v; }
else { if (v < r.Min) r.Min = v; if (v > r.Max) r.Max = v; }
}
}
}