feat: player tracker

This commit is contained in:
Hikyu
2026-10-05 21:04:27 +02:00
parent ddcbd2303c
commit c0868c2703
52 changed files with 9411 additions and 965 deletions
@@ -49,16 +49,16 @@ public class AccountData
public static class GeBuyLimitSlot
{
public int itemId;
public int price;
public long price;
public int total;
public int quantity;
public int spent;
public long spent;
public GeBuyLimitSlot()
{
}
public GeBuyLimitSlot(int itemId, int price, int total, int quantity, int spent)
public GeBuyLimitSlot(int itemId, long price, int total, int quantity, long spent)
{
this.itemId = itemId;
this.price = price;
@@ -302,6 +302,82 @@ public class AccountData
|| baseDelta <= (long) (0.05 * Math.max(Math.abs(vBefore), Math.abs(vAfter)) + 500_000L);
}
/**
* Checks if a sequence of consecutive snapshots starting at index 'start' with length 'count'
* represents a transient plateau spike (e.g. 4 to 25 points / up to 35 minutes) between
* before (start - 1) and after (start + count).
*/
public static boolean isPlateauSpike(List<Snapshot> list, int start, int count)
{
if (list == null || start + count >= list.size())
{
return false;
}
Snapshot after = list.get(start + count);
return isPlateauSpike(list, start, count, after.t, after.total());
}
public static boolean isPlateauSpike(List<Snapshot> list, int start, int count, long tAfter, long vAfter)
{
if (list == null || start <= 0 || count < 1 || start + count > list.size())
{
return false;
}
Snapshot before = list.get(start - 1);
Snapshot first = list.get(start);
Snapshot last = list.get(start + count - 1);
long legBefore = first.t - before.t;
long legAfter = tAfter - last.t;
if (legBefore <= 0 || legAfter <= 0)
{
return false;
}
long maxPlateauDuration = 35 * 60 * 1000L; // 35 minutes max
if (tAfter - before.t > maxPlateauDuration)
{
return false;
}
long vBefore = before.total();
boolean positive = (first.total() - vBefore) > 0;
long minDev = Long.MAX_VALUE;
for (int i = 0; i < count; i++)
{
long val = list.get(start + i).total();
long d1 = val - vBefore;
long d2 = vAfter - val;
if (positive)
{
if (d1 <= 0 || d2 >= 0)
{
return false;
}
}
else
{
if (d1 >= 0 || d2 <= 0)
{
return false;
}
}
minDev = Math.min(minDev, Math.min(Math.abs(d1), Math.abs(d2)));
}
long baseDelta = Math.abs(vAfter - vBefore);
long baseWealth = Math.max(10_000L, Math.min(Math.abs(vBefore), Math.abs(vAfter)));
boolean significant = minDev >= MIN_SPIKE_ABS_GP || (double) minDev / baseWealth >= MIN_SPIKE_REL_RATIO;
if (!significant)
{
return false;
}
return baseDelta <= 0.35 * minDev
|| baseDelta <= (long) (0.05 * Math.max(Math.abs(vBefore), Math.abs(vAfter)) + 500_000L);
}
/**
* Filters out momentary outlier spikes from the timeline (e.g. 1-minute spikes caused by
* transient container reloading, temporary GE duplication, or pricing glitches).
@@ -371,10 +447,88 @@ public class AccountData
i--;
}
}
// Pass 4: check multi-point plateau spikes (4 to 25 consecutive points)
for (int k = 25; k >= 4; k--)
{
for (int i = 1; i <= list.size() - k - 1; i++)
{
if (isPlateauSpike(list, i, k))
{
for (int j = 0; j < k; j++)
{
list.remove(i);
}
changed = true;
i--;
}
}
}
// Pass 5: check login step glitches (unverified cache before bank sync after a gap)
for (int i = 1; i < list.size() - 2; i++)
{
Snapshot b = list.get(i - 1);
Snapshot g = list.get(i);
Snapshot a1 = list.get(i + 1);
Snapshot a2 = list.get(i + 2);
if (isLoginStepGlitch(b.t, b.total(), g.t, g.total(), a1.t, a1.total(), a2.t, a2.total()))
{
list.remove(i);
changed = true;
i--;
}
}
}
return list;
}
/**
* Checks if an isolated point immediately following a logout gap (>= 10 minutes) represents
* an unverified cache artifact from before the bank was synced in that session.
*/
public static boolean isLoginStepGlitch(long tBefore, long vBefore,
long tGlitch, long vGlitch,
long tAfter1, long vAfter1,
long tAfter2, long vAfter2)
{
if (tGlitch - tBefore < 10 * 60 * 1000L)
{
return false;
}
long glitchDuration = tAfter1 - tGlitch;
if (glitchDuration <= 0 || glitchDuration > 5 * 60 * 1000L)
{
return false;
}
if (tAfter2 - tAfter1 <= 0)
{
return false;
}
long drop = Math.abs(vAfter1 - vGlitch);
long baseWealth = Math.max(10_000L, Math.min(Math.abs(vGlitch), Math.abs(vAfter1)));
boolean significant = drop >= MIN_SPIKE_ABS_GP || (double) drop / baseWealth >= MIN_SPIKE_REL_RATIO;
if (!significant)
{
return false;
}
// Sustained at after1 and after2
if (Math.abs(vAfter2 - vAfter1) > Math.max(500_000L, (long) (0.35 * drop)))
{
return false;
}
// Glitch point closely matches the old before value from before the logout gap
if (Math.abs(vGlitch - vBefore) > Math.max(500_000L, (long) (0.35 * drop)))
{
return false;
}
return true;
}
public static final long FOUR_HOURS_MS = 4 * 3600 * 1000L;
/**
@@ -459,4 +613,3 @@ public class AccountData
}
}
}
@@ -8,15 +8,15 @@ public class GeRow
/** null = empty slot */
public final GeSlot offer;
public final String name;
public final int marketPrice;
public final long marketPrice;
public final GeLimitInfo limitInfo;
public GeRow(String character, int slot, GeSlot offer, String name, int marketPrice)
public GeRow(String character, int slot, GeSlot offer, String name, long marketPrice)
{
this(character, slot, offer, name, marketPrice, null);
}
public GeRow(String character, int slot, GeSlot offer, String name, int marketPrice, GeLimitInfo limitInfo)
public GeRow(String character, int slot, GeSlot offer, String name, long marketPrice, GeLimitInfo limitInfo)
{
this.character = character;
this.slot = slot;
@@ -27,4 +27,9 @@ public class GeSlot
public long trackedSince;
public long trackedDone;
public long lastTradeAt;
/** Baseline holdings of the offer item across visible containers when offer was placed */
public long baselineHoldings;
public long baselineCoins;
public boolean baselineInitialized;
}
@@ -11,7 +11,7 @@ public class ItemSnapshot
public long t;
public int[] ids;
public long[] q;
public int[] p;
public long[] p;
/** pinned (session start) - kept by compaction */
public Boolean k;
@@ -19,7 +19,7 @@ public class ItemSnapshot
{
}
public ItemSnapshot(long t, int[] ids, long[] q, int[] p)
public ItemSnapshot(long t, int[] ids, long[] q, long[] p)
{
this.t = t;
this.ids = ids;