Pages:<< prev 1 next >>
Fledgling

GroupMembers
Posts1
JoinedJul 31, 2026
I created a python area directory parser that when run from the area directory, scans every .are file, pulls the objects and exports them to a .csv file. It translates bitvectors into human readable names as well. save the into a .py file, run it, and open the .csv that's created., that's it. Find the stats on all the stuff! I ran it against the default of 1.9.9 and it worked fine. Usage may very.
Good Luck!
import os
import csv
import glob
import re
# --- EXACT AFFECT FLAGS FROM YOUR BUILD.C ---
AFFECT_FLAGS = [
"blind", "invisible", "detect_evil", "detect_invis", "detect_magic",
"detect_hidden", "hold", "sanctuary", "faerie_fire", "infrared", "curse",
"_flaming", "poison", "protect", "_paralysis", "sneak", "hide", "sleep",
"charm", "flying", "pass_door", "floating", "truesight", "detect_traps",
"scrying", "fireshield", "shockshield", "r1", "iceshield", "possess",
"berserk", "aqua_breath", "recurringspell", "contagious", "acidmist",
"venomshield", "grapple"
]
ITEM_TYPES = {
"light": "Light", "scroll": "Scroll", "wand": "Wand", "staff": "Staff",
"weapon": "Weapon", "treasure": "Treasure", "armor": "Armor", "potion": "Potion",
"furniture": "Furniture", "trash": "Trash", "container": "Container",
"drinkcon": "DrinkCon", "key": "Key", "food": "Food", "money": "Money",
"boat": "Boat", "corpsenpc": "CorpseNpc", "corpsepc": "CorpsePc",
"fountain": "Fountain", "pill": "Pill", "armortinkered": "ArmorTinkered"
}
WEAPON_TYPES = {
0: "Hit/Buff", 1: "Slice", 2: "Stab", 3: "Slash", 4: "Whip",
5: "Claw", 6: "Blast", 7: "Pound", 8: "Crush", 9: "Grepe",
10: "Bite", 11: "Sting", 12: "Gouge"
}
AFFECT_LOCATIONS = {
1: "Strength", 2: "Dexterity", 3: "Intelligence", 4: "Wisdom", 5: "Constitution",
12: "Mana", 13: "HP", 14: "Move", 17: "AC", 18: "Hitroll", 19: "Damroll",
24: "Saving_Spell"
}
LIQUID_TYPES = {
0: "Water", 1: "Beer", 2: "Wine", 3: "Ale", 4: "Dark_Ale", 5: "Whisky",
6: "Lemonade", 7: "Firebreather", 8: "Local_Specialty", 9: "Slime",
10: "Milk", 11: "Tea", 12: "Coffee", 13: "Blood", 14: "Salt_Water", 15: "Juice"
}
def decode_specific_values(item_type, values, explicit_spells):
readable = {"V0_Meaning": "", "V1_Meaning": "", "V2_Meaning": "", "V3_Meaning": "", "V4_Meaning": "", "V5_Meaning": ""}
while len(values) < 6:
values.append("0")
v0, v1, v2, v3, v4, v5 = values[0], values[1], values[2], values[3], values[4], values[5]
try:
clean_type = item_type.lower().replace('~', '').strip()
if clean_type == "weapon":
readable["V3_Meaning"] = WEAPON_TYPES.get(int(v3), f"Unknown({v3})")
readable["V1_Meaning"] = f"{v1} (Min Damage)"
readable["V2_Meaning"] = f"{v2} (Max Damage)"
elif clean_type in ["scroll", "potion", "pill"]:
readable["V0_Meaning"] = f"Cast Level: {v0}"
# 🛠️ CHOOSE EXPLICIT SPELLS INSTEAD OF VALUE INTEGERS
s1 = explicit_spells[0] if len(explicit_spells) >= 1 else "None"
s2 = explicit_spells[1] if len(explicit_spells) >= 2 else "None"
s3 = explicit_spells[2] if len(explicit_spells) >= 3 else "None"
readable["V1_Meaning"] = f"Spell 1: {s1}"
readable["V2_Meaning"] = f"Spell 2: {s2}"
readable["V3_Meaning"] = f"Spell 3: {s3}"
elif clean_type in ["wand", "staff"]:
readable["V0_Meaning"] = f"Cast Level: {v0}"
readable["V1_Meaning"] = f"Max Charges: {v1}"
readable["V2_Meaning"] = f"Current Charges: {v2}"
s1 = explicit_spells[0] if explicit_spells else "None"
readable["V3_Meaning"] = f"Spell: {s1}"
elif clean_type == "container":
readable["V0_Meaning"] = f"Max Weight Capacity: {v0}"
flags = []
val_v1 = int(v1)
if val_v1 & 1: flags.append("Closeable")
if val_v1 & 2: flags.append("Pickproof")
if val_v1 & 4: flags.append("Closed")
if val_v1 & 8: flags.append("Locked")
readable["V1_Meaning"] = "|".join(flags) if flags else "None"
readable["V2_Meaning"] = f"Key VNUM: {v2}" if int(v2) > 0 else "No Key"
elif clean_type == "drinkcon":
readable["V0_Meaning"] = f"Capacity: {v0}"
readable["V1_Meaning"] = f"Current Quantity: {v1}"
readable["V2_Meaning"] = LIQUID_TYPES.get(int(v2), f"Unknown Liquid({v2})")
readable["V3_Meaning"] = "Poisoned!" if int(v3) != 0 else "Safe"
elif clean_type == "light":
readable["V2_Meaning"] = "Infinite Light" if int(v2) == -1 else f"Fuel: {v2} hours"
except ValueError:
pass
return readable
def format_flag_string(raw_flags):
if not raw_flags: return "None"
clean = raw_flags.replace('~', '').strip()
parts = [p.strip().capitalize() for p in clean.split() if p.strip()]
return "; ".join(parts) if parts else "None"
class KeyedSmaugParser:
def __init__(self, filepath):
self.filepath = filepath
self.filename = os.path.basename(filepath)
self.object_rows = []
def clean_val(self, val_str):
return val_str.replace('~', '').strip()
def parse(self):
with open(self.filepath, 'r', encoding='latin-1', errors='ignore') as f:
content = f.read()
object_blocks = re.findall(r'#OBJECT\b(.*?)(?=#ENDOBJECT|#OBJECT|\Z)', content, re.DOTALL | re.IGNORECASE)
for block in object_blocks:
lines = [line.strip() for line in block.split('\n') if line.strip()]
obj_data = {
'Vnum': '', 'Keywords': '', 'Type': '', 'Short': '', 'Long': '',
'WFlags': '', 'Flags': '', 'Values': ['0', '0', '0', '0', '0', '0'],
'Weight': '0', 'Cost': '0', 'Level': '0', 'Layers': '0',
'Spells': []
}
stats = {"Strength": 0, "Dexterity": 0, "Intelligence": 0, "Wisdom": 0, "Constitution": 0, "Mana": 0, "HP": 0, "Move": 0, "AC": 0, "Hitroll": 0, "Damroll": 0}
for line in lines:
parts = line.split(None, 1)
if len(parts) < 2: continue
key = parts[0].lower()
val = parts[1]
if key == 'vnum':
obj_data['Vnum'] = val
elif key == 'keywords':
obj_data['Keywords'] = self.clean_val(val)
elif key == 'type':
obj_data['Type'] = self.clean_val(val)
elif key == 'short':
obj_data['Short'] = self.clean_val(val)
elif key == 'long':
obj_data['Long'] = self.clean_val(val)
elif key == 'wflags':
obj_data['WFlags'] = format_flag_string(val)
elif key == 'flags':
obj_data['Flags'] = format_flag_string(val)
elif key == 'values':
obj_data['Values'] = val.split()
elif key == 'spells':
# 🛠️ PARSE SINGLE QUOTE SPELL STRINGS ACCURATELY:
# 'cure critical' 'armor' 'refresh' -> ["Cure Critical", "Armor", "Refresh"]
found_spells = re.findall(r"'(.*?)'", val)
obj_data['Spells'] = [s.strip().title() for s in found_spells if s.strip()]
elif key == 'stats':
s_parts = val.split()
if len(s_parts) >= 1: obj_data['Weight'] = s_parts[0]
if len(s_parts) >= 2: obj_data['Cost'] = s_parts[1]
if len(s_parts) >= 4: obj_data['Level'] = s_parts[3]
if len(s_parts) >= 5: obj_data['Layers'] = s_parts[4]
elif key == 'affect':
aff_parts = val.split()
if len(aff_parts) >= 4:
try:
modifier = int(aff_parts[1])
loc_id = int(aff_parts[2])
stat_name = AFFECT_LOCATIONS.get(loc_id)
if stat_name in stats:
stats[stat_name] += modifier
except (ValueError, IndexError):
pass
if not obj_data['Vnum']: continue
mapped_type = ITEM_TYPES.get(obj_data['Type'].lower(), obj_data['Type'])
meanings = decode_specific_values(mapped_type, obj_data['Values'], obj_data['Spells'])
record = {
'Source_File': self.filename, 'VNUM': obj_data['Vnum'], 'Name': obj_data['Keywords'],
'Short_Desc': obj_data['Short'], 'Item_Type': mapped_type,
'Wear_Flags': obj_data['WFlags'] if obj_data['WFlags'] else "None",
'Extra_Flags': obj_data['Flags'] if obj_data['Flags'] else "None",
'Weight': obj_data['Weight'], 'Gold_Cost': obj_data['Cost'],
'Level': obj_data['Level'], 'Layers': obj_data['Layers'],
'Value_0': obj_data['Values'][0] if len(obj_data['Values']) > 0 else '0', 'V0_Meaning': meanings["V0_Meaning"],
'Value_1': obj_data['Values'][1] if len(obj_data['Values']) > 1 else '0', 'V1_Meaning': meanings["V1_Meaning"],
'Value_2': obj_data['Values'][2] if len(obj_data['Values']) > 2 else '0', 'V2_Meaning': meanings["V2_Meaning"],
'Value_3': obj_data['Values'][3] if len(obj_data['Values']) > 3 else '0', 'V3_Meaning': meanings["V3_Meaning"],
'Value_4': obj_data['Values'][4] if len(obj_data['Values']) > 4 else '0', 'V4_Meaning': meanings["V4_Meaning"],
'Value_5': obj_data['Values'][5] if len(obj_data['Values']) > 5 else '0', 'V5_Meaning': meanings["V5_Meaning"]
}
record.update(stats)
self.object_rows.append(record)
if __name__ == "__main__":
all_objects = []
area_files = glob.glob("*.are")
if not area_files:
print("Error: No .are files found in the current working directory.")
else:
print(f"Deep parsing keyed objects across {len(area_files)} area files...")
for filepath in area_files:
parser = KeyedSmaugParser(filepath)
parser.parse()
print(f" - Found {len(parser.object_rows)} objects inside '{parser.filename}'")
all_objects.extend(parser.object_rows)
csv_filename = "smaug_object_comprehensive_matrix.csv"
headers = [
'Source_File', 'VNUM', 'Name', 'Short_Desc', 'Item_Type', 'Wear_Flags', 'Extra_Flags',
'Weight', 'Gold_Cost', 'Level', 'Layers',
'Value_0', 'V0_Meaning', 'Value_1', 'V1_Meaning', 'Value_2', 'V2_Meaning',
'Value_3', 'V3_Meaning', 'Value_4', 'V4_Meaning', 'Value_5', 'V5_Meaning',
'Strength', 'Dexterity', 'Intelligence', 'Wisdom', 'Constitution', 'Mana', 'HP', 'Move', 'AC', 'Hitroll', 'Damroll'
]
with open(csv_filename, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=headers)
writer.writeheader()
writer.writerows(all_objects)
print(f"\nExecution Complete! Successfully populated '{csv_filename}'.")
Good Luck!
Pages:<< prev 1 next >>