Skip to content

Instantly share code, notes, and snippets.

@bradbeattie
Last active August 29, 2015 14:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bradbeattie/276db65f08730f296cb3 to your computer and use it in GitHub Desktop.
Save bradbeattie/276db65f08730f296cb3 to your computer and use it in GitHub Desktop.
Parse and modify prison architect save files
# These functions will allow you to
# - Read in Prison Architect save files
# - Modify the read content
# - Save content back into Prison Architect save file formats
import re
from collections import OrderedDict
from pprint import PrettyPrinter
TOKEN_BEGIN = 1
TOKEN_END = 2
TOKEN_CONTENT = 3
CATEGORY_PROTECTED = "Protected"
CATEGORY_MINSEC = "MinSec"
CATEGORY_MEDSEC = "Normal"
CATEGORY_MAXSEC = "MaxSec"
CATEGORY_SUPERMAX = "SuperMax"
PRISONER_CATEGORIES = (CATEGORY_PROTECTED, CATEGORY_MINSEC, CATEGORY_MEDSEC, CATEGORY_MAXSEC, CATEGORY_SUPERMAX)
RELEASED = "Released"
DANGEROUS_REPUTATIONS = set(("Volatile", "Instigator", "CopKiller", "Deadly"))
DANGEROUS_MISCONDUCTS = set(("InjuredPrisoner", "InjuredStaff", "Murder", "Destruction"))
def parse_tokens(tokens):
content = OrderedDict()
index = 0
if type(tokens) is list:
tokens = enumerate(tokens)
for index, token in tokens:
if token[0] == TOKEN_END:
break
elif token[0] == TOKEN_BEGIN:
secondIndex, secondToken = next(tokens)
if secondToken[0] != TOKEN_CONTENT:
raise Exception("Expected a CONTENT token after a BEGIN token")
parsed_tokens, subcontentLength = parse_tokens(tokens)
content.setdefault(secondToken[1].strip('"'), [])
content[secondToken[1].strip('"')].append(parsed_tokens)
else:
secondIndex, secondToken = next(tokens)
content.setdefault(token[1].strip('"'), [])
content[token[1].strip('"')].append(secondToken[1])
return content, index
def escaped(name):
return ('"%s"' % name) if " " in name else name
def generate_prison_format(parsedPrison, indent=0):
content = []
for name, values in parsedPrison.iteritems():
for value in values:
if type(value) is OrderedDict:
subContent = generate_prison_format(value, indent + 1)
if len(subContent) >= 2:
content.append("BEGIN %s" % escaped(name))
for subEntry in subContent:
content.append("".join((" ", subEntry)))
content.append("END")
else:
content.append("BEGIN %s END" % " ".join([escaped(name)] + subContent))
else:
content.append(" ".join((escaped(name), str(value))))
return content
def security_hearings(parsedPrison):
for id, entry in parsedPrison["Objects"][0].iteritems():
if type(entry[0]) == OrderedDict and entry[0].get("Type", None) == ["Prisoner"]:
prisonerGrade = grade_prisoner(parsedPrison, id, entry)
if prisonerGrade == RELEASED:
print "EARLY PAROLE FOR", id
entry[0]["Bio"][0]["Sentence"][0] = float(entry[0]["Bio"][0]["Served"][0])
elif prisonerGrade is not None:
if entry[0]["Category"][0] != prisonerGrade:
print "CHANGING", id, "FROM", entry[0]["Category"][0], "TO", prisonerGrade
entry[0]["Category"][0] = prisonerGrade
def grade_prisoner(parsedPrison, id, entry):
# Don't grade unrevealed prisoners
reputationRevealed = entry[0]["Bio"][0]["ReputationRevealed"][0] == "true"
if not reputationRevealed:
return
# Collect relevant prisoner information
daysInPrison = float(entry[0]["Experience"][0]["Experience"][0]["TotalTime"][0].rstrip(".")) / 1440
sentence = float(entry[0]["Bio"][0].get("Sentence", [100])[0])
served = float(entry[0]["Bio"][0].get("Served", [0])[0])
parole = float(entry[0]["Bio"][0]["Parole"][0])
reputations = set(entry[0]["Bio"][0].get("Reputation", []))
highReputations = set(entry[0]["Bio"][0].get("ReputationHigh", []))
programsPassed = sum(int(program[0].get("Passed", [0])[0]) for program in entry[0]["Experience"][0]["Results"][0].values())
dangerous = reputations & DANGEROUS_REPUTATIONS or highReputations & DANGEROUS_REPUTATIONS
try:
misconducts = len([
True
for misconduct in parsedPrison["Misconduct"][0]["MisconductReports"][0][id][0]["MisconductEntries"][0].values()
if type(misconduct[0]) is OrderedDict and misconduct[0]["Convicted"][0] == "true"
])
except KeyError:
misconducts = 0
try:
violent_behavior = len([
True
for misconduct in parsedPrison["Misconduct"][0]["MisconductReports"][0][id][0]["MisconductEntries"][0].values()
if type(misconduct[0]) is OrderedDict and misconduct[0]["Type"][0] in DANGEROUS_MISCONDUCTS
])
except KeyError:
violent_behavior = 0
# Grading a prisoner costs $100
parsedPrison["Finance"][0]["Balance"][0] = "%g" % (float(parsedPrison["Finance"][0]["Balance"][0]) - 100)
# Consider early release for well-behaved prisoners
if not dangerous and not violent_behavior and served + programsPassed > parole + misconducts and programsPassed - misconducts > 2:
parsedPrison["Finance"][0]["Balance"][0] = "%g" % (float(parsedPrison["Finance"][0]["Balance"][0]) + 1000)
return RELEASED
# Don't recategorize Protected prisoners
category = entry[0]["Category"][0]
if category == CATEGORY_PROTECTED:
return
# Violent or dangerous MinSec prisoners will get boosted up to MedSec
if category == CATEGORY_MINSEC:
return CATEGORY_MEDSEC if violent_behavior or dangerous else CATEGORY_MINSEC
# Legendary prisoners have two options: MaxSec or SuperMax
if "Legendary" in highReputations:
if violent_behavior or dangerous:
return CATEGORY_SUPERMAX
else:
return CATEGORY_MAXSEC if daysInPrison > 8 else category
# All others have two options: MedSec or MaxSec
else:
if violent_behavior or dangerous:
return CATEGORY_MAXSEC
else:
return CATEGORY_MEDSEC if daysInPrison > 4 else category
inFile = r"Alpha27-600.prison"
outFile = r"Alpha27-600-new.prison"
with open(inFile, "r") as oldPrisonFile, open(outFile, "w") as newPrisonFile:
scanner=re.Scanner([
(r"\s+", None),
(r"BEGIN", lambda scanner,token:(TOKEN_BEGIN, token)),
(r"END", lambda scanner,token:(TOKEN_END, token)),
(r'".*"', lambda scanner,token:(TOKEN_CONTENT, token)),
(r"[^\s]*", lambda scanner,token:(TOKEN_CONTENT, token)),
])
tokens, remainder = scanner.scan(oldPrisonFile.read())
parsedPrison = parse_tokens(tokens)[0]
security_hearings(parsedPrison)
newPrisonFile.write("\n".join(generate_prison_format(parsedPrison)))
@bradbeattie
Copy link
Author

Todo:

  • Is there a way to get prisoner grading out or do I have to generate that myself?
  • Ideally, each security hearing would be a 1h session with the security chief, each parole hearing through referrals by the security chief to the warden, making them useful again. I can create the first session with the current modding tools, but I can't read the necessary misconduct data nor create a referral to the warden. Ugh.

@WhiteyDude
Copy link

Hey thanks heaps for posting this - I've been mulling over in my head the best way to parse a .prison file for the last couple of days (for completely different reasons), I'll use your code as a reference point! I'm guessing from syntax this is in Python?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment