Last active
January 5, 2023 04:51
-
-
Save richtan/f686d63424c0537874f097bc35d292d1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import time | |
import logging | |
from _remote import ffi, lib | |
import os | |
from manager import PluginBase, report_exception | |
import util | |
''' | |
By richtan | |
Based on playerlist.py and Alexrns's sc loot logger plugin | |
WIP | |
Last updated: 1/4/23 | |
''' | |
''' | |
struct Chest { | |
struct ClientObject asClientObject; | |
struct RepeatedPtrField items; // ItemProperties | |
struct STDVector angles; // int,std::allocator<int> | |
int pos; | |
int untilThrow; | |
}; | |
struct ItemProperties { | |
void *classptr; | |
int32_t type; | |
int32_t slotpos; | |
struct RepeatedPtrField statboosts; // StatBoost | |
int32_t _cached_size; | |
uint32_t _has_bits[1]; | |
}; | |
struct StatBoost { | |
void *classptr; | |
uint32_t stat; | |
int32_t val; | |
int32_t level; | |
bool increment; | |
struct STDString *subtypestr; | |
int32_t subtype; | |
int32_t _cached_size; | |
uint32_t _has_bits[1]; | |
}; | |
struct Loot { | |
struct ClientObject asClientObject; | |
struct ItemProperties itemProps; | |
struct ItemDescription *itemDesc; | |
int untilAutoApply; | |
}; | |
struct ItemDescription { | |
void *classptr; | |
struct STDString *name; | |
uint32_t type; | |
uint32_t build; | |
int32_t tier; | |
uint32_t price; | |
uint32_t currency; | |
uint32_t itemclass; | |
int32_t itemsubclass; | |
uint32_t slot; | |
struct RepeatedField_int usableby; | |
struct STDString *lootvid; | |
struct STDString *lootdestroyvid; | |
struct STDString *lootsound; | |
int32_t lootlifetime; | |
uint32_t anim; | |
struct STDString *aaid; | |
struct STDString *imagemap; | |
struct STDString *sound; | |
struct STDString *altsound; | |
struct STDString *emitsound; | |
int32_t cooldown; | |
int32_t numsubs; | |
int32_t jitter; | |
bool autoapply; | |
bool candrop; | |
bool lootisglobalplayerevent; | |
bool unhitcancel; | |
int32_t maxheat; | |
int32_t speed; | |
int32_t secondarysubs; | |
int32_t simultaneous; | |
struct AnchoredImage *anchoredimage; | |
struct RepeatedPtrField statboosts; // StatBoost | |
struct RepeatedPtrField obj; // ItemObjDescription | |
int32_t bounces; | |
int32_t _cached_size; | |
uint32_t _has_bits[2]; | |
}; | |
''' | |
ITEMS = { | |
1: 'Aesc', 2: 'Frag', 4: 'Shotgun', 5: 'Arblast', 6: 'Sledge', 7: 'Kyeser', 9: 'Skein', 10: 'Warp', | |
11: 'Mantlet', 12: 'Macron', 14: 'Jerkin', 15: 'Trackers', 16: 'Empyreals', 18: 'Health Boost 1', | |
19: 'Max Health Boost 1', 20: 'Max Health Boost 2', 21: 'Ammo Boost', 22: 'Max Ammo Boost 1', | |
23: 'Max Ammo Boost 2', 31: 'Kantikoy Repeater', 32: 'Relentless Mantlet', 33: 'Plating', 34: 'Hyrst', | |
35: 'Nacre Skein', 36: 'Cruel Sledge', 38: 'Improved Frag', 39: 'Cluster', 40: 'Swarm Cluster', | |
41: 'Biting Aesc', 42: 'Acrotic Warp', 43: 'Sabatons', 44: 'Irascent Kyeser', 45: 'Swarmer', | |
46: 'Polychrome Skein', 47: 'Adjudicator', 48: 'Protracted Macron', 49: '1 EC', 50: '10 EC', | |
51: 'Implant Slot 1', 53: 'Dancer', 54: 'Antiphon', 55: 'Durable Jerkin', 58: 'Deepfire Dancer', | |
59: 'Defiant Antiphon', 60: 'Woven', 61: 'Valenki', 63: 'Blast', 66: 'Trinity', 67: 'Whispered Antiphon', | |
68: 'Mechanized Macron', 69: 'Empowered Blast', 70: 'Forceful Blast', 71: 'Scattered Blast', | |
72: 'Gnawing Aesc', 73: 'Inferior Bellites', 74: 'Bellites', 76: 'Inferior Salites', 77: 'Salites', | |
79: 'Inferior Delites', 80: 'Delites', 82: 'Inferior Calites', 83: 'Calites', 85: 'Inferior Armites', | |
86: 'Armites', 88: 'Exites', 89: 'Inferior Emites', 90: 'Emites', 92: 'Max Health Boost 3', | |
93: 'Max Health Boost 4', 94: 'Max Ammo Boost 3', 95: 'Max Ammo Boost 4', 96: '5 EC', 97: '25 EC', | |
98: 'Abiding Warp', 99: 'Fungus Cave Key', 100: 'Underground Base Key', 101: 'Graveyard Key', | |
102: 'Durable Plating', 103: 'Durable Hyrst', 104: 'Durable Woven', 105: 'Gliders', 106: 'Volatile Gliders', | |
107: 'Strands Key', 108: 'Soidal Repeater', 109: 'Bolt Thrower', 110: 'Proxies', 111: 'Kulbeda', | |
112: 'Mirrored Warp', 113: 'Gavotte', 114: 'Quickstep', 115: 'Burner', 116: 'Balefire Skein', 117: 'Baetyl', | |
118: 'Preceptor', 119: 'Baculus', 120: 'Armor Boost 1', 121: 'Armor Boost 2', 122: 'Armor Boost 3', | |
123: 'Armor Boost 4', 124: 'Movement Speed Boost 1', 125: 'Movement Speed Boost 2', | |
126: 'Movement Speed Boost 3', 127: 'Movement Speed Boost 4', 128: 'Jump Height Boost 1', | |
129: 'Jump Height Boost 2', 130: 'Jump Height Boost 3', 131: 'Jump Height Boost 4', 132: 'Damage Boost 1', | |
133: 'Damage Boost 2', 134: 'Damage Boost 3', 135: 'Damage Boost 4', 136: 'Crit Chance Boost 1', | |
137: 'Crit Chance Boost 2', 138: 'Crit Chance Boost 3', 139: 'Crit Chance Boost 4', 140: 'Crit Damage Boost 1', | |
141: 'Crit Damage Boost 2', 142: 'Crit Damage Boost 3', 143: 'Crit Damage Boost 4', 144: 'Stonebow', | |
145: 'Temple of the Lost Key', 146: 'Lightfoot', 147: 'Compound Repeater', 148: 'Redshift Repeater', | |
149: 'Tektite Repeater', 150: 'Dragoon', 151: 'Contrived Frag', 152: 'Synthetic Frag', 153: 'Reforged Frag', | |
154: 'Withering Blast', 155: 'Voracious Blast', 156: 'Zealous Blast', 157: 'Cobalt Skein', | |
158: 'Sapphire Skein', 159: 'Berylblade Skein', 160: 'Falcate Aesc', 161: 'Keen Aesc', 162: 'Violet Aesc', | |
163: 'Beloid Warp', 164: 'Daedal Warp', 165: 'Aeon Warp', 166: 'Vengeful Sledge', 167: 'Sinful Sledge', | |
168: 'Baleful Sledge', 169: 'Caustic Kyeser', 170: 'Insidious Kyeser', 171: 'Nefarious Kyeser', | |
172: 'Fortified Mantlet', 173: 'Banded Mantlet', 174: 'Annealed Mantlet', 175: 'Everdawn Dancer', | |
176: 'Fellwhip Dancer', 177: 'Darkstar Dancer', 178: 'Invidious Antiphon', 179: 'Mocking Antiphon', | |
180: 'Profane Antiphon', 181: 'Recalcitant Macron', 182: 'Inveterate Macron', 183: 'Composite Macron', | |
184: 'Augmented Trackers', 185: 'Augmented Sabatons', 186: 'Augmented Valenki', 187: 'Stompers', | |
188: 'Bandolier', 189: 'Double Bandolier', 190: 'Berserker', 191: 'Blockers', 192: 'Enraged Blockers', | |
193: 'Needlebow', 194: 'Tripwire', 195: 'Haunting Aesc', 196: 'Residual Warp', 197: 'Lancet', 198: 'Hotfoot', | |
199: 'Telson', 200: 'Mangonel', 201: 'Vanquished Key', 205: 'Unknown Key', 206: 'Mysterious Key', | |
207: 'Aerolith Repeater', 208: 'Bombard', 209: 'Jansky Repeater', 210: 'Unstable Frag', 212: 'Hydra', | |
213: 'Furious Blast', 214: 'Fireform', 215: 'Vitreous Skein', 216: 'Motley', 217: 'Skyfire Skein', | |
218: 'Radiant Aesc', 219: 'Kunai', 221: 'Tacent Warp', 222: 'Mimic', 223: 'Corrupt Sledge', 224: 'Quaestor', | |
226: 'Diabolical Kyeser', 227: 'Locust', 228: 'Eternal Mantlet', 229: 'Direfall Dancer', 230: 'Volta', | |
232: 'Blasphemous Antiphon', 233: 'Scorpion', 234: 'Devoted Macron', 235: 'Skiff', 236: 'Max Health Boost 5', | |
237: 'Max Ammo Boost 5', 238: 'Armor Boost 5', 239: 'Movement Speed Boost 5', 240: 'Jump Height Boost 5', | |
241: 'Damage Boost 5', 242: 'Crit Chance Boost 5', 243: 'Crit Damage Boost 5', 244: 'Augmented Plating', | |
245: 'Reinforced Plating', 246: 'Resilient Plating', 247: 'Augmented Jerkin', 248: 'Reinforced Jerkin', | |
249: 'Resilient Jerkin', 250: 'Augmented Hyrst', 251: 'Reinforced Hyrst', 252: 'Resilient Hyrst', | |
253: 'Augmented Woven', 254: 'Reinforced Woven', 255: 'Resilient Woven', 256: 'Frenzied Berserker', | |
257: 'Enlarged Blockers', 258: 'Durable Trackers', 259: 'Reinforced Trackers', 260: 'Resilient Trackers', | |
261: 'Talons', 262: 'Durable Talons', 263: 'Augmented Talons', 264: 'Reinforced Talons', | |
265: 'Resilient Talons', 266: 'Aeonic Empyreals', 267: 'Durable Sabatons', 268: 'Reinforced Sabatons', | |
269: 'Resilient Sabatons', 270: 'Spiked Stompers', 271: 'Durable Valenki', 272: 'Reinforced Valenki', | |
273: 'Resilient Valenki', 274: 'Overloaded Boosters', 277: 'Boosters', 291: '1 UC', 292: '5 UC', | |
299: 'Inferior Velites', 300: 'Velites', 302: 'Inferior Firites', 303: 'Firites', 305: 'Blink', | |
308: 'Inferior Ultrites', 309: 'Ultrites', 311: 'Silent Antiphon', 312: 'Triad' | |
} | |
STATS = { | |
4: 'max ammo', | |
10: 'armor', | |
11: 'mvs', | |
12: 'jh', | |
13: 'range', | |
14: 'precise', | |
18: 'dmg', | |
23: 'cc', | |
20: 'cd', | |
21: 'air', | |
22: 'kbr', | |
24: 'cc', | |
25: 'cd', | |
27: 'max ammo', | |
32: 'pen', | |
33: 'bounce', | |
34: 'exp ammo', | |
35: 'inc', | |
37: 'speed', | |
38: 'life', | |
39: 'seek', | |
42: 'acc', | |
43: 'ap', | |
45: 'sim', | |
46: 'shrap', | |
47: 'barb', | |
48: 'tri', | |
49: 'gravity', | |
50: 'leech', | |
51: 'exhit', | |
52: 'reflect', | |
53: 'spintime', | |
54: 'armor', | |
55: 'qs', | |
56: 'shield', | |
} | |
MODIFIER_PREFIXES = ["", "Modified ", "Custom ", "Experimental ", "Prototype "] | |
COLUMNS = 'modifiers name'.split() | |
def boostToModifier(item, stat, val): | |
try: | |
if stat == 57: | |
if item in [200,233]: | |
modName = "cooldown" | |
elif item in [113,199]: | |
modName = "300 ct" | |
else: | |
modName = "100 ct" | |
elif stat == 36: | |
if item in [69,70,71,154,155,156,213]: | |
modName = "#blasts" | |
elif item in [31,108,147,148,149,207,209, 5,150,208]: | |
modName = "#shots" | |
elif item in [113,199,230,66]: | |
modName = "#dances" | |
elif item in [39,40,212]: | |
modName = "#bombs" | |
elif item == 118: | |
modName = "height" | |
elif item in [59,67,178,179,180,232,311, 191,192,257]: | |
modName = "+1 blockers" | |
elif item in [47,224]: | |
modName = "#plinks" | |
else: | |
modName = "num subs" | |
else: | |
modName = STATS[stat] | |
# operator = "+" if val > 0 else "" | |
# value = str(val / 100 if "crit" in modName.lower() else val) | |
# value = value.rstrip('0').rstrip('.') if '.' in value else value | |
# return STATS[stat].format(o=operator, v=value) | |
return modName | |
except KeyError: | |
return str(stat) + " " + str(val) | |
def reFieldToList(rf, itemtype=None): # TODO: cast inside of `logChest` | |
'''struct RepeatedField_int -> list | |
struct RepeatedPtrField works too if there is a single struct to cast on all elements''' | |
if rf.elements == ffi.NULL: | |
return [] | |
lst = ffi.unpack(rf.elements, rf.current_size) | |
if itemtype != None: | |
for e in range(len(lst)): | |
lst[e] = ffi.cast(itemtype, lst[e]) | |
return lst | |
def item_sort(itemlist): | |
name = itemlist[1] | |
if name.endswith("ites"): return 0 | |
if name.startswith("Prototype "): return 2 | |
if name.startswith("Experimental "): return 3 | |
if name.startswith("Custom "): return 4 | |
if name.startswith("Modified "): return 5 | |
if name.endswith(" Key"): return 7 | |
if " Boost " in name: return 9 | |
if name == "Implant Slot 1": return 10 | |
if name.endswith(" UC") or name.endswith(" EC"): return 11 | |
if name.endswith(" Boost"): return 12 | |
return 6 | |
def getBoosts(item): | |
boosts = list() | |
i = 0 | |
for boost in reFieldToList(item.statboosts, 'struct StatBoost *'): | |
boosts.append(boostToModifier(item.type, boost.stat, boost.val)) | |
if boosts[-1] == "300 ct": | |
i += 1 | |
elif boosts[-1] == "100 ct": | |
i -= 1 | |
if i > 1: | |
boosts.remove("300 ct") | |
boosts.remove("300 ct") | |
boosts.append("250 ct") | |
elif i < -1: | |
boosts.remove("100 ct") | |
boosts.remove("100 ct") | |
boosts.append("50 ct") | |
return boosts | |
def isEC(item): | |
return item in [49,50,96,97] | |
def isStat(item): | |
return item in [18,19,20,21,22,23, 92,93,94,95,96,97] + [i for i in range(120, 144)] + [i for i in range(236, 244)] | |
class Plugin(PluginBase): | |
def onInit(self): | |
self.config.option('visible', False, 'bool') | |
self.config.option('show_dropped', False, 'bool') | |
self.config.option('show_ec', False, 'bool') | |
self.config.option('show_stats', False, 'bool') | |
self.config.option('size', 14, 'int') | |
self.columns = {} | |
for cname in COLUMNS: | |
self.columns[cname] = util.MultilineText( | |
size=self.config.size, spacing=self.config.size) | |
self.chests = {} | |
def afterUpdate(self): | |
cw = self.refs.ClientWorld | |
if cw == ffi.NULL: return | |
objs = util.worldobjects(cw.mySubWorld.asNativeSubWorld) | |
dropped_loot = [] | |
chestNum = 0 | |
for obj in objs: | |
if util.getClassName(obj) == 'Chest': | |
chest = [] | |
chestNum += 1 | |
for item in reFieldToList(ffi.cast('struct Chest *', obj).items, 'struct ItemProperties *'): | |
if (isEC(item.type) and not self.config.show_ec) or (isStat(item.type) and not self.config.show_stats): continue | |
boosts = getBoosts(item) | |
item_name = ITEMS[item.type] | |
chest.append(list(map(str, ( | |
', '.join(boosts), | |
MODIFIER_PREFIXES[len(boosts)] + item_name, | |
)))) | |
self.chests["Chest {}: {} items".format(chestNum, len(chest))] = chest | |
elif util.getClassName(obj) == "Loot" and self.config.show_dropped: | |
loot = ffi.cast('struct Loot *', obj) | |
item = loot.itemProps | |
if (isEC(item.type) and not self.config.show_ec) or (isStat(item.type) and not self.config.show_stats): continue | |
boosts = getBoosts(item) | |
item_name = ITEMS[item.type] | |
dropped_loot.append(list(map(str, ( | |
', '.join(boosts), | |
MODIFIER_PREFIXES[len(boosts)] + item_name, | |
)))) | |
if dropped_loot and self.config.show_dropped: | |
self.chests["Dropped Loot: {} items".format(len(dropped_loot))] = dropped_loot | |
def onPresent(self): | |
chestlist = dict(self.chests) | |
self.chests = {} | |
if not chestlist: | |
return | |
for key in chestlist.keys(): | |
chestlist[key].sort(key=item_sort) | |
txts = [''] * len(COLUMNS) | |
def addList(lst, num, name): | |
nonlocal txts | |
lst = list(lst) | |
if len(lst) == 0: | |
return | |
txts[0] += '{}\n'.format(name) | |
for i in range(1, len(txts)): | |
txts[i] += '\n' | |
for x in lst: | |
for i in range(len(txts)): | |
txts[i] += x[i] + '\n' | |
cw = self.refs.ClientWorld | |
if cw != ffi.NULL and cw.asWorld.props.safe: | |
# wipe old records in safe zones | |
self.items = {} | |
if not self.config.visible: | |
return | |
i = 0 | |
for key in chestlist.keys(): | |
i += 1 | |
addList(chestlist[key], i, key) | |
self.columns['modifiers'].text = txts[0] | |
self.columns['name'].text = txts[1] | |
x = self.refs.windowW // 2 | |
y = 10 | |
spacing = 5 | |
self.columns['modifiers'].draw(x - 1*spacing, y, anchorX=1) | |
self.columns['name'].draw(x + 1*spacing, y) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment