Skip to content

Commit

Permalink
Merge pull request #1 from ambiguousname/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
ambiguousname authored Nov 9, 2020
2 parents 1672079 + 0cf0e78 commit 3f41b67
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 38 deletions.
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

__pycache__/jppc.cpython-39.pyc
build/jppc/Analysis-00.toc
build/jppc/base_library.zip
build/jppc/EXE-00.toc
build/jppc/jppc.exe.manifest
build/jppc/PKG-00.pkg
build/jppc/PKG-00.toc
build/jppc/PYZ-00.pyz
build/jppc/PYZ-00.toc
build/jppc/Tree-00.toc
build/jppc/Tree-01.toc
build/jppc/warn-jppc.txt
build/jppc/xref-jppc.html
dist/jppc.exe
jppc.spec
dist/Jackbox Party Pack Custom.exe
24 changes: 23 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@ otherwise you're going to have to uninstall and reinstall your entire game if so
- Unzip the contents of the .ZIP file into the "games" directory of your Jackbox Party Pack 7 install folder. (For steam on Windows: C:\Program Files (x86)\Steam\steamapps\common\The Jackbox Party Pack 7\games)
- Run "Jackbox Party Pack Custom.exe".

# Potential Questions

## HELP, EVERYTHING IS BROKEN

If you're using Steam, go to the Jackbox Party Pack 7 in your Steam Library. Right click on the game's icon or name, click "Properties".
In the popup window, click on "Local Files". Then click "Verify integrity of game files..." That should fix everything.

### Important note if you've clicked "verify integrity of game files..."

That means all your custom prompts have been removed from the game. To get your custom prompts back, use the import feature, and select the "custom_content.json" file that's in the same folder as Jackbox Party Pack Custom.exe

## EVERYTHING'S STILL BROKEN

Uninstall and reinstall the Jackbox Party Pack 7
Expand All @@ -25,9 +31,25 @@ Uninstall and reinstall the Jackbox Party Pack 7

Delete everything from your Jackbox Party Pack 7 folder, then find the "Verify integrity of game files..." button and click it.

# Why does this program sometimes use weird names for each game?

I have here a handy conversion for the games and their weird names:
`BlankyBlank - Blather 'Round

Everyday - Devils and the Details

JackboxTalks - Talking Points

World Champions - Champ'd Up

Quiplash3 - Quiplash 3`

The program does this because that's what the folders Jackbox Party Pack 7 uses for each game are called.

# Making custom responses to specific text for Quiplash 3:

So, you may notice that in Quiplash 3 the announcer will sometimes react to a specific prompt. You can do this too!

NOTE: This only works for Round 1 and 2 questions. You can't have custom responses for Final Round questions.

Okay, I'm note entirely sure how this works, but say you have a prompt like:
Expand Down Expand Up @@ -55,4 +77,4 @@ as a filter, trying to detect things like "A chicken", "chicken", "A chiken", "c
Note the spaces in between the "|" signs in the example above. It's just a formatting thing, I don't think it really matters. You can add spaces only if you want to.

Once you've added the filtering, then you can add your response audio as a .ogg file. Jackbox also requires a transcript of your response for captioning purposes (I think),
so you should write out what you've said in the "Transcript of your response: " field.
so you should write out what you've said in the "Transcript of your response: " field.
111 changes: 74 additions & 37 deletions jppc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from shutil import copyfile, rmtree

#TODO:
# Test safety quips
# Test adding and deleting specific content (Probably not gonna work right now)
# View your custom content
# Save your custom content as a .JSON to then import
# Test prompt creation for Quiplash 3
# Adding safety quips to Quiplash 3
# Test editing custom content
# Add other people's custom content with import
# Add Talking Points Content (Pictures, Prompts, Slide Transitions)

def id_gen(custom_values): #custom_values should be a dict that passes on any other identifying information for the user
ids = None #Start IDs from 100k (to make it distingusihable from other IDs), go from there.
Expand All @@ -27,8 +27,9 @@ def id_gen(custom_values): #custom_values should be a dict that passes on any ot
_id = str(100000 + len(id_dict.keys()))
custom_values.update({"id": _id}) #Need to store the id twice so that things can work. The .jet files need a reference to the id.
id_dict[_id] = {"id": _id, "values": custom_values}
ids.truncate(0)
ids.write(json.dumps(id_dict))
ids.truncate()
new_json = json.dumps(id_dict)
ids.write(new_json)
ids.close()
return _id

Expand All @@ -54,6 +55,8 @@ def write_to_json(self, p=None, delete=False): #Right now, add_custom_files does
if delete == True:
json_file["content"].remove(self.values)
else:
if self.values in json_file["content"]:
json_file["content"].remove(self.values)
json_file["content"].append(self.values)
jf.close()
#Close and reopen to write, because writing with utf-8 encoding gets... weird.
Expand All @@ -70,7 +73,7 @@ def save_to_custom_content(self): #Save to custom_content.json file which keeps
ids = open("./custom_content.json", "r+")
content = json.load(ids)
content[self.id].update({"id": self.id, "values": self.values})
ids.truncate(0)
ids.truncate()
ids.write(json.dumps(content))
ids.close()

Expand All @@ -90,9 +93,11 @@ def add_custom_files(self, *args, **kwargs): #Construct the path from what we al
copyfile(file['path'], path + file['name']) #From shutil
else: #If we're going to be writing a custom file from like a .JSON or whatever.
if isinstance(file, CustomContent):
if os.path.exists(path + "data.jet"):
os.remove(path + "data.jet")
file.write_to_json(path + "data.jet")
elif 'str' in file: #Just making sure there are no files that have an empty path. "str" is if a file has specific data that we're writing.
f = open(path + file['name'], "w")
f = open(path + file['name'], "w+")
f.write(file['str'])
f.close()

Expand Down Expand Up @@ -128,13 +133,13 @@ def run(self, inputs=None): #Have to add inputs as an argument because the "Ok"
break
if event == "Ok":
window.close()
window = None
func = self.selector.get(values[self.list_key][0])
func(values[self.list_key][0]) #What we need the "inputs" argument for.
break
if event == "Go Back" and self.previous_window:
window.close()
window = None
window_mapping[self.previous_window].run()
break
window.close()
window = None

Expand Down Expand Up @@ -163,29 +168,41 @@ def view_content(selected=None):
sg.Popup("You have no custom content.")
main_window.run()

def select_delete_content(selected=None): #Selected goes unused because of how SelectWindow works.
def edit_content(selected=None): #Selected goes unused because of how SelectWindow works.
if os.path.exists("./custom_content.json"):
ids = open("./custom_content.json", 'r+')
content = json.load(ids)
content_list = []
for item in content:
content_list.append(content[item]["id"] + ": " + content[item]["values"]["content_type"] + " - " + content[item]["values"]["descriptor_text"])
layout = [[sg.Text("Choose Content to Delete:")], [sg.Listbox(content_list, key="delete_content", size=(100, 5))], [sg.Button("Delete"), sg.Button("Go Back")]]
window = sg.Window("Choose Content to Delete", layout)
layout = [[sg.Text("Choose Content to Edit/Delete:")], [sg.Listbox(content_list, key="content_selection", size=(100, 5))], [sg.Button("Edit"), sg.Button("Delete"), sg.Button("Show Folder"), sg.Button("Go Back")]]
window = sg.Window("Choose Content to Edit/Delete", layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
if event == "Show Folder":
_id = values["content_selection"][0].split(":")[0]
existing_data = content[_id]["values"]
path = os.path.realpath("./" + existing_data["game"] + "/content/" + existing_data["content_type"] + "/" + existing_data["id"])
if (os.path.exists(path)):
os.startfile(path)
else:
sg.Window("There is no folder containing the content (If the content contains only text (like a safety quip), it's probably just stored ... not in a folder).")
if event == "Edit":
_id = values["content_selection"][0].split(":")[0]
existing_data = content[_id]["values"]
content_type_mapping[existing_data["content_type"]](selected, existing_data)
if event == "Delete":
_id = values["delete_content"][0].split(":")[0]
_id = values["content_selection"][0].split(":")[0]
custom_content = CustomContent(content[_id]["values"], content[_id]["values"]["game"], content[_id]["values"]["content_type"], content[_id]["values"]["descriptor_text"], _id) #Setting None because values already has the game, type, and descriptor_text.
#Remove the content from the custom_content JSON file
content.pop(_id)
#Remove the content from the game's master .JET file
custom_content.write_to_json(None, True) #Delete the JSON file, using the pre-existing path.
#Remove the content's custom folder (will do nothing if one doesn't exist)
custom_content.add_custom_files(delete=True)
ids.truncate(0)
ids.truncate()
if len(content.keys()) != 0:
ids.write(json.dumps(content))
ids.close()
Expand All @@ -195,14 +212,16 @@ def select_delete_content(selected=None): #Selected goes unused because of how S
window.close()
window = None
sg.Popup("Content deleted!")
select_delete_content() #To update the list of content
edit_content() #To update the list of content
if event == "Go Back":
window.close()
window = None
main_window.run()
ids.close()
window.close()
window = None
else:
sg.Popup("Sorry, no content to delete.")
sg.Popup("Sorry, no content to edit.")
main_window.run()

#Stuff for Quiplash 3
Expand All @@ -219,31 +238,33 @@ def create_quiplash_data_jet(prompt_content):
data.add_data("S", prompt_content.values["safetyQuips"], "SafetyQuips")
return data

def round_prompt(selection):
layout = [[sg.Text("Prompt Text: "), sg.InputText("Hey, <ANYPLAYER> needs to <BLANK>.", size=(50,1), key="text")], [sg.Text("Safety Quip(s) (separate by |): "), sg.InputText("learn how the prompt system works|learn how safety quips work|eat all my garbage", size=(50,1), key="safety-quips")],
[sg.Checkbox("Includes Player Name", default=True, key="player-name"), sg.Checkbox("Contains Adult Content", key="x"), sg.Checkbox("Content is US specific", key="us")],
def round_prompt(selection, existing_data=None):
layout = [[sg.Text("Prompt Text: "), sg.InputText("Hey, <ANYPLAYER> needs to <BLANK>." if existing_data == None else existing_data["prompt"], size=(50,1), key="text")], [sg.Text("Safety Quip(s) (separate by |): "), sg.InputText("learn how the prompt system works|learn how safety quips work|eat all my garbage" if existing_data == None else existing_data["safetyQuips"], size=(50,1), key="safety-quips")],
[sg.Checkbox("Includes Player Name", default=(True if existing_data == None else existing_data["includesPlayerName"]), key="player-name"), sg.Checkbox("Contains Adult Content", default=(False if existing_data == None else existing_data["x"]), key="x"), sg.Checkbox("Content is US specific", default=(False if existing_data == None else existing_data["us"]), key="us")],
[sg.Text(".ogg files of you reading things (Optional):")], [sg.Text(".ogg of you saying the prompt: "), sg.InputText(key="prompt"), sg.FileBrowse()],
[sg.Text("Add a response to specific text (Very optional, see Readme for information):")],
[sg.InputText(key="response"), sg.FileBrowse()],
[sg.Text("What to filter (See Readme): "), sg.InputText(key="response-filter")],
[sg.Text("Transcript of your response: "), sg.InputText(key="response-narration")],
[sg.Button("Make a prompt"), sg.Button("Go Back")]]
window = sg.Window("Round " + selection[-1] + " Prompt", layout)
window = sg.Window(("Round " + selection[-1] + " Prompt") if existing_data == None else existing_data["content_type"], layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
if event == "Go Back":
window.close()
quiplash_prompt.run()
window = None
if existing_data == None:
quiplash_prompt.run()
if event == "Make a prompt":
prompt = CustomContent({
"includesPlayerName": values["player-name"],
"prompt": values["text"],
"safetyQuips": values["safety-quips"],
"safetyQuips": values["safety-quips"].split("|"), #We make an array because that's how they're formatted in the master .jet file.
"x": values["x"],
"us": values["us"]
}, "Quiplash3", "Quiplash3Round" + selection[-1] + "Question", values["text"])
}, "Quiplash3", ("Quiplash3Round" + selection[-1] + "Question") if existing_data == None else existing_data["content_type"], values["text"], None if existing_data == None else existing_data["id"])
prompt.response_filter = values["response-filter"]
prompt.response_narration = values["response-narration"]
prompt.write_to_json() #Get round number from the choice passed through in quiplash_prompt
Expand All @@ -254,9 +275,9 @@ def round_prompt(selection):
window.close()
window = None

def round_prompt_final(selection): #I'm making this separate because it's just easier to do this than to explain the last round prompt syntax.
layout = [[sg.Text("Prompt Text: "), sg.InputText("Three things a stranger would say about <ANYPLAYER>.", key="lastround-prompt")], [sg.Text("Safety Quip(s) (separate by |): "), sg.InputText("not|funny|didn't laugh|get|out of|my face|learn|how the safety quips|work", key="lastround-safety-quips")],
[sg.Checkbox("Includes Player Name", default=True, key="player-name"), sg.Checkbox("Contains Adult Content", key="x"), sg.Checkbox("Content is US specific", key="us")],
def round_prompt_final(selection, existing_data=None): #I'm making this separate because it's just easier to do this than to explain the last round prompt syntax.
layout = [[sg.Text("Prompt Text: "), sg.InputText("Three things a stranger would say about <ANYPLAYER>." if existing_data == None else existing_data["prompt"], key="lastround-prompt")], [sg.Text("Safety Quip(s) (separate by |): "), sg.InputText("not|funny|didn't laugh|get|out of|my face|learn|how the safety quips|work" if existing_data == None else existing_data["safetyQuips"], key="lastround-safety-quips")],
[sg.Checkbox("Includes Player Name", default=(True if existing_data == None else existing_data["includesPlayerName"]), key="player-name"), sg.Checkbox("Contains Adult Content", default=(False if existing_data == None else existing_data["x"]), key="x"), sg.Checkbox("Content is US specific", default=(False if existing_data == None else existing_data["us"]), key="us")],
[sg.Text(".ogg file of you reading the prompt (Optional):")], [sg.InputText(key="prompt"), sg.FileBrowse()],
[sg.Button("Make a prompt"), sg.Button("Go Back")]]
window = sg.Window("Make a Quiplash 3 Final Round Prompt", layout)
Expand All @@ -266,15 +287,23 @@ def round_prompt_final(selection): #I'm making this separate because it's just e
break
if event == "Go Back":
window.close()
quiplash_prompt.run()
window = None
if existing_data == None:
quiplash_prompt.run()
if event == "Make a prompt":
#Safety quips for the final round are a bit weird:
safety_quips = values["lastround-safety-quips"].split("|")
formatted_quips = []
for i in range(len(safety_quip)):
if not (i + 3 > len(safety_quip)):
formatted_quips.append(safety_quips[0] + "|" + safety_quips[1] + "|" + safety_quips[2])
prompt = CustomContent({
"includesPlayerName": values["player-name"],
"prompt": values["lastround-prompt"],
"safetyQuips": values["lastround-safety-quips"],
"safetyQuips": formatted_quips,
"x": values["x"],
"us": values["us"]
}, "Quiplash3", "Quiplash3FinalQuestion", values["lastround-prompt"])
}, "Quiplash3", "Quiplash3FinalQuestion", values["lastround-prompt"], None if existing_data == None else existing_data["id"])
prompt.write_to_json()
prompt.response_filter = ""
prompt.response_narration = ""
Expand All @@ -284,19 +313,20 @@ def round_prompt_final(selection): #I'm making this separate because it's just e
window.close()
window = None

def safety_quip(selection):
layout = [[sg.Text("Safety Quip Text (Should be generic): "), sg.InputText(key="safety-quip")], [sg.Button("Make Quip"), sg.Button("Go Back")]]
def safety_quip(selection, existing_data=None):
layout = [[sg.Text("Safety Quip Text (Should be generic): "), sg.InputText("" if existing_data == None else existing_data["value"], key="safety-quip")], [sg.Button("Make Quip"), sg.Button("Go Back")]]
window = sg.Window("Make a Safety Quip", layout)
while True:
event, values = window.read()
if event == "Make Quip":
quip = CustomContent({"value": values["safety-quip"]}, "Quiplash3", "Quiplash3SafetyQuips", values["safety-quip"])
quip = CustomContent({"value": values["safety-quip"]}, "Quiplash3", "Quiplash3SafetyQuips", values["safety-quip"], None if existing_data == None else existing_data["id"])
quip.write_to_json()
sg.Popup("Safety Quip Created. ID: " + quip.id)
if event == "Go Back":
window.close()
window = None
quiplash_3.run()
if existing_data == None:
quiplash_3.run()

quiplash_prompt = SelectionWindow("Choose a Round", ["Choose a round.", ("Round 1", "Round 2", "Final Round"), "round-number"], {
"Round 1": round_prompt,
Expand All @@ -318,9 +348,10 @@ def safety_quip(selection):
"Champ'd Up": None
}, "main_window")

main_window = SelectionWindow("Select an option", ["Please select an option.", ("View My Custom Content", "Create Custom Content", "Delete Content"), "option"], {
main_window = SelectionWindow("Select an option", ["Please select an option.", ("View My Custom Content", "Create Custom Content", "Edit Content"), "option"], {
"Create Custom Content": create_content.run,
"Delete Content": select_delete_content,
"Edit Content": edit_content,
"Import Content": None,
"View My Custom Content": view_content
})
window_mapping = { #Used for backing out of stuff.
Expand All @@ -329,4 +360,10 @@ def safety_quip(selection):
"create_content": create_content,
"main_window": main_window
}
main_window.run()
content_type_mapping = { #Used in editing content to change data.
"Quiplash3Round1Question": round_prompt,
"Quiplash3Round2Question": round_prompt,
"Quiplash3FinalQuestion": round_prompt_final,
"Quiplash3SafetyQuips": safety_quip
}
main_window.run()

0 comments on commit 3f41b67

Please sign in to comment.