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

Okay, maybe overkill, but I got frustrated by the lack of decent modding tools in Prison Architect. So I set out to write something that could (A) read in the save file format, (B) make modifications to the data, and then (C) write those modifications back into the format in question.

The net result here is an example of a mod I'd like to be able to build in game (1h security hearing sessions for prisoners with a psychologist that can automatically adjust their security ratings). Until such time as the mod tools are improved, this'll do just fine.

@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