Skip to content

Instantly share code, notes, and snippets.

@Jimmajones
Forked from Hulzenga/ShowSkillChecks.py
Last active April 26, 2024 16:29
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Jimmajones/afb879e29bc7102294fd2475f8f388fb to your computer and use it in GitHub Desktop.
Save Jimmajones/afb879e29bc7102294fd2475f8f388fb to your computer and use it in GitHub Desktop.
Python script to extract Disco Elysium passive checks, updated for the Final Cut update
import json
import csv
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
'''
Difficulty checks are coded in the following format:
[3]
0 Field data
1 string title = "DifficultyPass"
1 string value = "2"
0 int type = 1
1 string typeString = "CustomFieldType_Number"
[4]
0 Field data
1 string title = "Actor"
1 string value = "386"
0 int type = 5
1 string typeString = "CustomFieldType_Actor"
[5]
The value under the "DifficultyPass" sets the difficulty of the check
The value below "Actor" sets the skill which is checked, in this case "Suggestion" (see ACTOR_DICT)
'''
#mapping of actor codes to in-game skills, note that there are 5 different perception checks!
#('normal', smell, hearing, taste, sight)
ACTOR_DICT = {
389: 'Conceptualization',
390: 'Logic',
391: 'Encyclopedia',
392: 'Rhetoric',
393: 'Drama',
394: 'Visual Calculus',
395: 'Empathy',
396: 'Inland Empire',
397: 'Volition',
398: 'Authority',
399: 'Suggestion',
400: 'Esprit de Corps',
401: 'Endurance',
402: 'Physical Instrument',
403: 'Shivers',
404: 'Pain Threshold',
405: 'Electro-Chemistry',
406: 'Half Light',
407: 'Hand-Eye Coordination',
408: 'Reaction Speed',
409: 'Savoir Faire',
410: 'Interfacing',
411: 'Composure',
412: 'Perception',
413: 'Perception',
414: 'Perception',
415: 'Perception',
416: 'Perception',
}
#map of DifficultyPass values to in-game difficulty
DIFF_MAP = {
0: 6,
1: 8,
2: 10,
3: 12,
4: 14,
5: 16,
6: 18,
7: 20,
8: 7,
9: 9,
10: 11,
11: 13,
12: 15,
13: 17,
14: 19
}
#in-game skill order
DISPLAY_ORDER = [
'Logic',
'Encyclopedia',
'Rhetoric',
'Drama',
'Conceptualization',
'Visual Calculus',
'Volition',
'Inland Empire',
'Empathy',
'Authority',
'Esprit de Corps',
'Suggestion',
'Endurance',
'Pain Threshold',
'Physical Instrument',
'Electro-Chemistry',
'Shivers',
'Half Light',
'Hand-Eye Coordination',
'Perception',
'Reaction Speed',
'Savoir Faire',
'Interfacing',
'Composure'
]
#highest check value is 14
HIGH_CHECK = 15
#-------------------------------------------#
# extract check counts from game code #
#-------------------------------------------#
#check count arrays
passive_checks = {k: [0] * HIGH_CHECK for k in ACTOR_DICT.values()}
antipassive_checks = {k: [0] * HIGH_CHECK for k in ACTOR_DICT.values()}
# Open and read in dialogue JSON data (I extracted it using AssetStudio by Perfare)
with open("Disco Elysium.json", encoding="utf-8") as disco_data_json:
disco_data = json.load(disco_data_json)
# Go through each conversation.
for conversation in disco_data["conversations"]:
# Go through each piece of dialogue within a conversation.
for dialogue in conversation["dialogueEntries"]:
# Construct a dictionary of this dialogue block with the dialogue's speaker
# and, if it has them, difficulty and antipassiveness.
# (We do this as dialogue entries are weirdly formatted and difficult to
# manipulate otherwise).
dialogue_field_dict = {}
dialogue_field_dict["Antipassive"] = False
for dialogue_field in dialogue["fields"]:
if dialogue_field["title"] == "Antipassive":
dialogue_field_dict["Antipassive"] = True
elif dialogue_field["title"] == "Actor":
dialogue_field_dict["Actor"] = int(dialogue_field["value"])
elif dialogue_field["title"] == "DifficultyPass":
dialogue_field_dict["DifficultyPass"] = int(dialogue_field["value"])
# If this dialogue block is a skill check, add it to the count(s).
if "DifficultyPass" in dialogue_field_dict:
if dialogue_field_dict["Antipassive"]:
antipassive_checks[ACTOR_DICT[dialogue_field_dict["Actor"]]][dialogue_field_dict["DifficultyPass"]] += 1
else:
passive_checks[ACTOR_DICT[dialogue_field_dict["Actor"]]][dialogue_field_dict["DifficultyPass"]] += 1
#--------------------#
#Write result tables #
#--------------------#
# Write a table of tallied difficulty checks for both passives and antipassives for each skill.
for tup in [('Passive Checks', passive_checks), ('Antipassive Checks', antipassive_checks)]:
# The table will have the skill's name and sum of checks as first two columns.
header = ["Skill", "Total"] + list(range(HIGH_CHECK))
data = []
title = tup[0]
checks = tup[1]
for skill in DISPLAY_ORDER:
# The +2 is for the skill name and sum of checks.
skill_checks = [0] * (HIGH_CHECK + 2)
skill_checks[0] = skill
skill_checks[1] = sum(checks[skill])
for i, value in enumerate(checks[skill]):
skill_checks[DIFF_MAP[i] - 6 + 2] = value
data.append(skill_checks)
# Write to file.
with open(title + ".csv", 'w', newline='') as file:
writer = csv.writer(file)
writer.writerow(header)
writer.writerows(data)
#--------------------#
# Plot results #
#--------------------#
#skill grid dimensions
X = 6
Y = 4
#chart colours
ROW_COLOURS = ['skyblue', 'orchid', 'indianred', 'gold']
BG_COLOUR = 'black'
SPINE_COLOUR = 'white'
for tup in [('Passive Checks', passive_checks), ('Antipassive Checks', antipassive_checks)]:
#unpack tuple
title = tup[0]
checks = tup[1]
#setup all Y*X subplopts
fig, ax = plt.subplots(nrows=Y, ncols=X, sharex=True)
ax[0,0].set_xticks(list(range(6,20+1,2)))
#set figure title, size and, background colour
fig.suptitle(title, color=SPINE_COLOUR, fontsize=22)
fig.set_size_inches(24, 13.5)
fig.set_facecolor('black')
#go through all skills
for y in range(0,Y):
for x in range(0, X):
#skill being drawn
skill = DISPLAY_ORDER[x + X*y]
#calculate total number of skill checks and find highest skill check
count = sum(checks[skill])
max_skill = max(map(lambda dc: DIFF_MAP[dc], [ j for (i,j) in zip(checks[skill], range(15)) if i > 0 ]), default=0)
#style chart
col = ROW_COLOURS[y]
ax[y, x].set_title(DISPLAY_ORDER[X*y+x], color=col)
ax[y, x].set_facecolor(BG_COLOUR)
for spine in ax[y,x].spines.values():
spine.set_color(SPINE_COLOUR)
ax[y, x].tick_params(colors=SPINE_COLOUR)
#allow max of 5 y ticks and only use integer values
ax[y, x].yaxis.set_major_locator(ticker.MaxNLocator(nbins=5, integer=True))
#remove y ticks if there is no data
if count == 0:
ax[y,x].tick_params(axis='y',which='both', left=False, labelleft=False)
## else:
## ax[y,x].set_yscale('log')
#draw bar chart
ax[y, x].bar(list(map(lambda d: DIFF_MAP[d], range(0,15))), checks[skill], color=col)
#add stat data
ax[y, x].text(0.75, 0.85, f'Σ={count}', color=SPINE_COLOUR, transform = ax[y, x].transAxes)
ax[y, x].text(0.75, 0.75, f'M={max_skill}', color=SPINE_COLOUR, transform = ax[y, x].transAxes)
#save figures with max window size
mng = plt.get_current_fig_manager()
mng.window.state('zoomed')
fig.savefig(title + ".png", facecolor=fig.get_facecolor(), transparent=False)
plt.show()
@Jimmajones
Copy link
Author

Outputs:
Passive Checks
Antipassive Checks

Passive Checks.csv

Skill Total 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
Logic 793 54 1 120 32 459 38 59 9 17 0 4 0 0 0 0
Encyclopedia 413 7 1 41 10 251 28 42 5 19 2 6 0 1 0 0
Rhetoric 778 11 1 86 14 551 22 64 11 11 1 5 0 1 0 0
Drama 383 7 0 35 22 234 21 44 2 14 1 3 0 0 0 0
Conceptualization 500 5 1 27 2 290 23 97 10 28 4 7 0 5 0 1
Visual Calculus 186 6 0 29 3 105 15 18 1 4 0 5 0 0 0 0
Volition 439 10 0 65 7 249 25 53 7 16 1 6 0 0 0 0
Inland Empire 651 30 0 93 10 391 27 62 13 18 5 2 0 0 0 0
Empathy 895 5 1 92 10 613 39 79 12 27 2 14 0 1 0 0
Authority 407 10 0 38 6 266 30 47 5 2 1 2 0 0 0 0
Esprit de Corps 483 34 0 50 14 295 14 44 14 12 2 4 0 0 0 0
Suggestion 386 7 0 42 12 250 26 35 2 9 0 3 0 0 0 0
Endurance 232 9 0 18 3 135 25 31 5 3 0 1 0 2 0 0
Pain Threshold 197 2 1 26 3 116 11 32 1 5 0 0 0 0 0 0
Physical Instrument 283 16 0 23 3 146 23 49 10 7 1 2 2 1 0 0
Electro-Chemistry 421 7 0 40 18 253 24 46 8 17 2 5 0 1 0 0
Shivers 314 2 0 53 4 177 12 30 9 13 1 12 1 0 0 0
Half Light 499 3 0 40 11 302 27 64 10 24 5 10 3 0 0 0
Hand-Eye Coordination 114 2 1 10 6 62 11 15 3 2 1 1 0 0 0 0
Perception 510 25 1 80 9 288 21 54 3 14 0 10 1 3 0 1
Reaction Speed 409 16 0 103 20 218 8 25 9 5 1 4 0 0 0 0
Savoir Faire 201 20 0 29 4 113 10 17 2 5 0 1 0 0 0 0
Interfacing 281 11 0 51 6 163 15 23 7 5 0 0 0 0 0 0
Composure 358 5 0 39 8 230 24 34 5 8 0 3 0 2 0 0

Antipassive Checks.csv

Skill Total 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
Logic 18 0 0 6 1 6 1 3 0 1 0 0 0 0 0 0
Encyclopedia 17 0 0 1 0 1 2 2 1 2 0 0 1 0 0 7
Rhetoric 14 0 0 0 1 7 1 2 3 0 0 0 0 0 0 0
Drama 5 0 0 0 0 1 0 0 0 0 0 3 0 1 0 0
Conceptualization 16 0 0 1 0 3 0 5 1 3 0 0 1 1 0 1
Visual Calculus 2 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1
Volition 18 0 0 1 0 5 1 2 0 3 0 3 0 2 0 1
Inland Empire 4 0 0 0 0 3 0 0 0 0 0 1 0 0 0 0
Empathy 3 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0
Authority 4 0 0 0 0 0 0 0 0 3 1 0 0 0 0 0
Esprit de Corps 7 2 0 0 0 1 0 2 1 1 0 0 0 0 0 0
Suggestion 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Endurance 17 0 0 1 1 7 0 2 0 2 0 2 0 1 1 0
Pain Threshold 31 0 0 0 2 11 1 3 5 2 3 2 0 2 0 0
Physical Instrument 5 0 0 0 0 0 0 3 1 1 0 0 0 0 0 0
Electro-Chemistry 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0
Shivers 5 0 0 0 0 3 0 0 1 0 0 1 0 0 0 0
Half Light 5 0 0 0 0 3 1 1 0 0 0 0 0 0 0 0
Hand-Eye Coordination 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Perception 18 0 0 3 0 9 2 3 0 1 0 0 0 0 0 0
Reaction Speed 5 0 0 1 0 0 0 1 0 1 0 0 0 1 0 1
Savoir Faire 5 0 0 0 0 1 0 2 0 0 0 2 0 0 0 0
Interfacing 6 0 0 0 0 0 1 0 0 2 0 0 0 3 0 0
Composure 5 0 0 0 0 0 0 3 0 0 0 1 0 1 0 0

@RikCost
Copy link

RikCost commented Apr 26, 2024

Where i can find Disco Elysium.json?

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