Pages:<< prev 1 next >>
Fledgling

GroupMembers
Posts2
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!
Fledgling

GroupMembers
Posts2
JoinedJul 31, 2026
After finding an issue, I have updated the code to resolved it.
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: "Grapple",
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}"
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()
# --- NEW AFFECT PARSING BASED ON THE EXAMPLE ---
# Expecting: Affect <num> <num> <modifier> <location_id> <other>
if len(aff_parts) >= 5:
try:
# Check if first token is 'Affect'
if aff_parts[0].lower() == 'affect':
modifier = int(aff_parts[2])
loc_id = int(aff_parts[3])
else:
# fallback
modifier = int(aff_parts[2])
loc_id = int(aff_parts[3])
stat_name = AFFECT_LOCATIONS.get(loc_id)
if stat_name and 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_matrix2.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}'.")
Fledgling

GroupMembers
Posts1
JoinedNov 9, 2026
I truly appreciate the effort put into developing the Object Reader for SmaugFUSS. It’s fascinating how it enhances gameplay by making object data more accessible. This tool can really help players dive deeper into their experience. Thank you for sharing this resource; it’s a valuable addition to our community! speed stars
Pages:<< prev 1 next >>