make the mod menu work (not testing mods) and slightly better the mod processing code

This commit is contained in:
2024-10-04 20:55:43 -05:00
parent 85d71da086
commit 7d2203dc04
7 changed files with 180 additions and 70 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

View File

@ -121,8 +121,13 @@ init -999 python:
mod_menu_moddir = android_mods_path
#
# Helper functions
#
# Determines if a filename with it's extension is valid for renpy's image displaying and for our specified mod metadata.
def is_valid_metadata_image(filename, name_of_mod):
def is_valid_metadata_image(filename, mod_name):
error_trigger = True
for ext in valid_image_filetypes:
@ -134,6 +139,23 @@ init -999 python:
return True
# Moves a key from one list to another, deleting the key from the source list
def move_key_to_dict(source_dict, destination_dict, key):
if key in source_dict.keys():
destination_dict[key] = source_dict[key]
del source_dict[key]
# Checks to see if the key in the metadata dict exists/is correct, and tries to set a default if it isn't.
# For keys that are set to None, it will be treated as if it doesn't exist
def value_isnt_valid_string(metadata, key):
return metadata.get(key) != None and not isinstance(metadata.get(key), str)
def value_isnt_valid_list(metadata, key):
return metadata.get(key) != None and not isinstance(metadata.get(key), list)
# Start finding mods. Find json files within each folder and mod disablers in the mods directory if there's any.
# NOMODS - Disables mod loading entirely
@ -170,7 +192,7 @@ init -999 python:
for file in loadable_mod_metadata:
mod_data_final = {}
mod_jsonfail_list = [] # List of langauges that has an associated metadata that failed to load.
mod_jsonfail_list = [] # List of languages that has an associated metadata language file that failed to load.
mod_preferred_modname = []
mod_exception = False
mod_in_root_folder = file.count("/", len(mods_dir)) is 0
@ -178,7 +200,8 @@ init -999 python:
# mod_name is used only to display debugging information via mod_menu_errorcodes. Contains the mod folder name and whatever translations of
# the mod's name that exist. Kind of a cursed implemnetation but with how early error reporting this is before solidifying the mod name
# this is just what I came up with.
# Other than what's directly defined here, it will contain mod names by their language code if they exist. English will go by the "None" key.
# Other than what's directly defined here, it will contain mod names by their language code if they exist. The "None" key will be the fallback if a name of user's
# current language is not there.
mod_name = {}
if mod_in_root_folder:
mod_name["Folder"] = None # 'None' will make it default to 'in root of mods folder' when used in the errorcode conversion.
@ -188,7 +211,7 @@ init -999 python:
# Quickly get the names of mods for debugging information, and in the process get raw values from each metadata file that exists.
# Make the base metadata file (english) organized like a lang_data object, moving the ID to the mod_data_final object.
# Make the base metadata file (presumably english) organized like a lang_data object, moving the ID to the mod_data_final object.
try:
mod_data = json.load(renpy.open_file(file))
except Exception as e:
@ -205,19 +228,15 @@ init -999 python:
if _preferences.language == None and isinstance(mod_data.get("Name"), str):
mod_name["None"] = mod_data["Name"]
# Move these non-language specific pairs out of the way, into the base of the final mod dict.
if "ID" in mod_data.keys():
mod_data_final["ID"] = mod_data["ID"]
del mod_data["ID"]
if "Label" in mod_data.keys():
mod_data_final["Label"] = mod_data["Label"]
del mod_data["Label"]
# Move these non-language specific pairs out of mod_data, into the base of the final mod dict.
move_key_to_dict(mod_data, mod_data_final, "ID")
move_key_to_dict(mod_data, mod_data_final, "Label")
# Then store the rest like any other language, just our default one.
mod_data_final['None'] = mod_data
# Find language metadata files in the same place as our original metadata file, and then get values from it.
for lang in renpy.known_languages():
lang_file = file[:-5] + "_" + lang + ".json"
lang_file = file[:-5] + "_" + lang + ".json" # Finds the metadata file. ex: metadata_es.json
if renpy.loadable(lang_file):
try:
lang_data = (json.load(renpy.open_file(lang_file)))
@ -241,7 +260,7 @@ init -999 python:
mod_menu_errorcodes.append([ ModError.Metadata_Fail, { "mod_name": mod_name, "lang_code": lang_code }])
#
# Sanitize/Clean metadata values
# Sanitize/Clean metadata values
#
# Make sure our main metadata loaded
@ -266,18 +285,19 @@ init -999 python:
mod_exception = True
break
# Since lang keys will only be added to the mod data dict if their respective metadata successfully loaded, no need to check.
# Since lang keys will only be added to the mod data dict if their respective metadata successfully loaded, no need to check if they can.
for lang_key in mod_data_final.keys():
if lang_key is "None" or lang_key in renpy.known_languages():
lang_data = mod_data_final[lang_key]
# The JSON object returns an actual python list, but renpy only works with it's own list object and the automation for this fails with JSON.
# So we gotta make all lists revertable
for x in lang_data.keys():
if type(lang_data[x]) == python_list:
lang_data[x] = renpy.revertable.RevertableList(lang_data[x])
# Automatically give the name of the mod from the folder it's using if there's no defined name, but report an error if one is defined but not a string
if lang_data.get("Name") != None and not isinstance(lang_data.get("Name"), str):
if value_isnt_valid_string(lang_data, "Name"):
if lang_data.get("Name") != None:
mod_menu_errorcodes.append([ ModError.Name_Not_String, { "mod_name": mod_name, "lang_code": lang_key }])
@ -293,12 +313,12 @@ init -999 python:
mod_menu_errorcodes.append([ ModError.Display_Invalid_Mode, { "mod_name": mod_name, "display_mode": lang_data["Display"], "lang_code": lang_key }])
lang_data["Display"] = "both"
if lang_data.get("Version") != None and not isinstance(lang_data.get("Version"), str):
if value_isnt_valid_string(lang_data, "Version"):
mod_menu_errorcodes.append([ ModError.Version_Not_String, { "mod_name": mod_name, "lang_code": lang_key }])
lang_data["Version"] = None
# See if "Authors" is a list or string, and if it's a list search through the contents of the list to check if they're valid strings
if lang_data.get("Authors") != None and (not isinstance(lang_data.get("Authors"), str) and not isinstance(lang_data.get("Authors"), list)):
if value_isnt_valid_string(lang_data, "Authors") and value_isnt_valid_list(lang_data, "Authors"):
mod_menu_errorcodes.append([ ModError.Authors_Not_String_Or_List, { "mod_name": mod_name, "lang_code": lang_key }])
lang_data["Authors"] = None
elif isinstance(lang_data.get("Authors"), list):
@ -313,7 +333,7 @@ init -999 python:
lang_data["Authors"] = None
# Do the same as 'Authors' to 'Links'
if lang_data.get("Links") != None and (not isinstance(lang_data.get("Links"), str) and not isinstance(lang_data.get("Links"), list)):
if value_isnt_valid_string(lang_data, "Links") and value_isnt_valid_list(lang_data, "Links"):
mod_menu_errorcodes.append([ ModError.Links_Not_String_Or_List, { "mod_name": mod_name, "lang_code": lang_key }])
lang_data["Links"] = None
elif isinstance(lang_data.get("Links"), list):
@ -325,11 +345,11 @@ init -999 python:
if lang_data["Links"] == []:
lang_data["Links"] = None
if lang_data.get("Description") != None and not isinstance(lang_data.get("Description"), str):
if value_isnt_valid_string(lang_data, "Description"):
mod_menu_errorcodes.append([ ModError.Description_Not_String, { "mod_name": mod_name, "lang_code": lang_key }])
lang_data["Description"] = None
if lang_data.get("Mobile Description") != None and not isinstance(lang_data.get("Mobile Description"), str):
if value_isnt_valid_string(lang_data, "Mobile Description"):
mod_menu_errorcodes.append([ ModError.Mobile_Description_Not_String, { "mod_name": mod_name, "lang_code": lang_key }])
lang_data["Mobile Description"] = None
@ -348,11 +368,11 @@ init -999 python:
if lang_data["Screenshot Displayables"] == []:
lang_data["Screenshot Displayables"] = None
if lang_data.get("Icon Displayable") != None and not isinstance(lang_data.get("Icon Displayable"), str):
if value_isnt_valid_string(lang_data, "Icon Displayable"):
mod_menu_errorcodes.append([ ModError.Icon_Displayable_Not_String, { "mod_name": mod_name, "lang_code": lang_key }])
lang_data["Icon Displayable"] = None
if lang_data.get("Thumbnail Displayable") != None and not isinstance(lang_data.get("Thumbnail Displayable"), str):
if value_isnt_valid_string(lang_data, "Thumbnail Displayable"):
mod_menu_errorcodes.append([ ModError.Thumbnail_Displayable_Not_String, { "mod_name": mod_name, "lang_code": lang_key }])
lang_data["Thumbnail Displayable"] = None
@ -448,7 +468,7 @@ init -999 python:
# We're now gonna clean up the screenshots to be more usable as-is.
# Refine collected screenshots so that translated screenshots use the english screenshots (the ones without a lang code)
# Refine collected screenshots so that translated screenshots use the 'None' screenshots (the ones without a lang code)
# as a base, and then either replacing or adding the translated screenshots according to their order/number.
for lang_key in mod_screenshots.keys():
if lang_key != "None":
@ -626,10 +646,6 @@ init python:
## then fine by me
##
transform tf_modmenu_slide:
xoffset 600
linear 0.25 xoffset 0
# Some gay python workarounds for screen jank
init python:
def toggle_persistent_mods(index):
@ -658,14 +674,15 @@ init python:
default persistent.seenModWarning = False
screen mod_menu():
key "game_menu" action ShowMenu("extras")
tag menu
style_prefix "main_menu"
add gui.main_menu_background
add "gui/title_overlay.png"
add "gui/overlay/sidemenu.png" at tf_modmenu_slide
frame:
xsize 420
yfill True
background "gui/overlay/main_menu.png"
default mod_metadata = {}
default reload_game = False
@ -675,28 +692,24 @@ screen mod_menu():
default mod_icon = None
default mod_thumbnail = None
button at tf_modmenu_slide:
xpos 1455
ypos 150
xmaximum 300
ymaximum 129
# For some reason, Function() will instantly reload the game upon entering the mod menu, and put it in an infinite loop, so it's using a workaround
# with this variable.
action SetScreenVariable("reload_game", True)
activate_sound "audio/ui/snd_ui_click.wav"
add "gui/button/menubuttons/menu_button.png" xalign 0.5 yalign 0.5
text _("Reload Mods") xalign 0.5 yalign 0.5 size 34
# The top 2 buttons
hbox:
xpos 1272
ypos 50
if reload_game:
python:
reload_game = False
renpy.reload_script()
spacing 8
use mod_menu_top_buttons(_("Reload Mods"), SetScreenVariable("reload_game", True)):
if reload_game:
python:
reload_game = False
renpy.reload_script()
use mod_menu_top_buttons(_("Return"), ShowMenu("extras"))
viewport at tf_modmenu_slide:
viewport:
xpos 1338
ypos 279
ypos 179
xmaximum 540
ymaximum 790
@ -791,10 +804,10 @@ screen mod_menu():
xsize 350
ymaximum 2000
if mod_button_enabled:
background Frame("gui/button/menubuttons/title_button.png", 12, 12)
hover_background Transform(Frame("gui/button/menubuttons/title_button.png", 12, 12), matrixcolor = BrightnessMatrix(0.1))
background Frame("gui/button/menubuttons/template_idle.png", 12, 12)
hover_background Transform(Frame("gui/button/menubuttons/template_idle.png", 12, 12), matrixcolor = BrightnessMatrix(0.1))
else:
background Transform(Frame("gui/button/menubuttons/title_button.png", 12, 12),matrixcolor=SaturationMatrix(0.5))
background Transform(Frame("gui/button/menubuttons/template_idle.png", 12, 12),matrixcolor=SaturationMatrix(0.5))
padding (5, 5)
@ -828,29 +841,21 @@ screen mod_menu():
frame:
xsize 350
ymaximum 2000
background Frame("gui/button/menubuttons/title_button.png", 12, 12)
background Frame("gui/button/menubuttons/template_idle.png", 12, 12)
padding (5, 5)
hover_background Transform(Frame("gui/button/menubuttons/title_button.png", 12, 12), matrixcolor = BrightnessMatrix(0.1))
hover_background Transform(Frame("gui/button/menubuttons/template_idle.png", 12, 12), matrixcolor = BrightnessMatrix(0.1))
text x["Name"] xalign 0.5 yalign 0.5 size 34 textalign 0.5
else:
fixed:
ymaximum 600 # This is the stupidest fucking hack fix
if achievement.steamapi:
text _("You have no mods! \nInstall some in:\n\"{color=#abd7ff}[mod_menu_moddir]{/color}\"\nOr download some from the Steam Workshop!"):
style_prefix "navigation"
size 45
text_align 0.5
xalign 0.5 yalign 0.5
outlines [(3, "#342F6C", absolute(0), absolute(0))]
else:
text _("You have no mods! \nInstall some in:\n\"{color=#abd7ff}[mod_menu_moddir]{/color}\""):
style_prefix "navigation"
size 45
text_align 0.5
xalign 0.5 yalign 0.5
outlines [(3, "#342F6C", absolute(0), absolute(0))]
text _("You have no mods! \nInstall some in:\n\"{color=#abd7ff}[mod_menu_moddir]{/color}\""):
style_prefix "navigation"
size 45
text_align 0.5
xalign 0.5 yalign 0.5
outlines [(3, "#342F6C", absolute(0), absolute(0))]
# Displays the mod metadata on the left side
# This has two seperate viewports for error display because renpy is retarded
@ -885,7 +890,7 @@ screen mod_menu():
add mod_thumbnail fit 'scale-down'
# Mod details
# Omit checking for mod name, since we'll always have some kind of mod name.
# Omits checking for mod name, since we'll always have some kind of mod name.
# This will also not show anything if there's only a mod name, since we already show one in the mod button.
if return_translated_metadata(mod_metadata, "Version") != None or return_translated_metadata(mod_metadata, "Authors") != None or return_translated_metadata(mod_metadata, "Links") != None:
frame:
@ -987,8 +992,6 @@ screen mod_menu():
for t in mod_menu_errorcodes:
text convert_errorcode_to_errorstring(t[0], t[1])
use extrasnavigation
if not persistent.seenModWarning:
$ persistent.seenModWarning = True
use OkPrompt(_("Installing mods is dangerous since you are running unknown code in your computer. Only install mods from sources that you trust.\n\nIf you have problems with installed mods, check the README.md in the root of the mods folder."), False)
@ -1005,4 +1008,20 @@ screen mod_screenshot_preview(img):
align (0.5, 0.5)
fit "scale-down"
key ["mouseup_1", "mouseup_3"] action Hide("mod_screenshot_preview", dissolve)
key ["mouseup_1", "mouseup_3"] action Hide("mod_screenshot_preview", dissolve)
screen mod_menu_top_buttons(text, action):
button:
frame:
xmaximum 300
ymaximum 129
background Frame("gui/button/menubuttons/template_idle.png", 12, 12)
text text xalign 0.5 yalign 0.5 size 34
# For some reason, Function() will instantly reload the game upon entering the mod menu, and put it in an infinite loop, so it's using a workaround
# with this variable.
action action
activate_sound "audio/ui/snd_ui_click.wav"
transclude

View File

@ -0,0 +1,91 @@
# RoundedCorners() rounds the corners of a displayable you give it
python early:
import collections, pygame_sdl2 as pygame
def normalize_color(col):
a = col[3] / 255.0
r = a * col[0] / 255.0
g = a * col[1] / 255.0
b = a * col[2] / 255.0
return (r, g, b, a)
_rounded_corners_relative = {
None: 0.0,
"min": 1.0,
"max": 2.0,
"width": 3.0,
"height": 4.0,
}
def RoundedCorners(child, radius, relative=None, outline_width=0.0, outline_color="#fff", **kwargs):
if not isinstance(radius, tuple): radius = (radius,) * 4
relative = _rounded_corners_relative[relative]
outline_color = normalize_color(Color(outline_color))
return Transform(child, mesh=True, shader="shader.rounded_corners", u_radius=radius, u_relative=relative, u_outline_color=outline_color, u_outline_width=outline_width, **kwargs)
CurriedRoundedCorners = renpy.curry(RoundedCorners)
renpy.register_shader("shader.rounded_corners", variables="""
uniform vec4 u_radius;
uniform float u_outline_width;
uniform vec4 u_outline_color;
uniform float u_relative;
uniform sampler2D tex0;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
uniform vec2 u_model_size;
""", vertex_200="""
v_tex_coord = a_tex_coord;
""", fragment_functions="""
float rounded_rectangle(vec2 p, vec2 b, float r) {
return length(max(abs(p) - b + r, 0.0)) - r;
}
float get_radius(vec2 uv_minus_center, vec4 radius) {
vec2 xy = (uv_minus_center.x > 0.0) ? radius.xy : radius.zw;
float r = (uv_minus_center.y > 0.0) ? xy.x : xy.y;
return r;
}
""", fragment_200="""
vec2 center = u_model_size.xy / 2.0;
vec2 uv = (v_tex_coord.xy * u_model_size.xy);
vec2 uv_minus_center = uv - center;
float radius = get_radius(uv_minus_center, u_radius);
vec4 color = texture2D(tex0, v_tex_coord);
if (u_relative != 0.0) {
float side_size;
if (u_relative == 1.0) {
side_size = u_model_size.x;
} else if (u_relative == 2.0) {
side_size = u_model_size.y;
} else if (u_relative == 3.0) {
side_size = min(u_model_size.x, u_model_size.y);
} else {
side_size = max(u_model_size.x, u_model_size.y);
}
radius *= side_size;
}
if (u_outline_width > 0.0) {
vec2 center_outline = center - u_outline_width;
float crop1 = rounded_rectangle(uv - center, center, radius);
float crop2 = rounded_rectangle(uv - center, center_outline, radius - u_outline_width);
float coeff1 = smoothstep(1.0, -1.0, crop1);
float coeff2 = smoothstep(1.0, -1.0, crop2);
float outline_coeff = (coeff1 - coeff2);
gl_FragColor = mix(vec4(0.0), mix(color, u_outline_color, outline_coeff), coeff1);
}
else {
float crop = rounded_rectangle(uv_minus_center, center, radius);
gl_FragColor = mix(color, vec4(0.0), smoothstep(0.0, 1.0, crop));
}
""")