Bombsquad-Ballistica-Modded.../dist/ba_data/python/babase/modutils.py

193 lines
6.8 KiB
Python
Raw Normal View History

2021-03-29 03:24:13 +05:30
# Released under the MIT License. See LICENSE for details.
#
"""Functionality related to modding."""
from __future__ import annotations
from typing import TYPE_CHECKING
import os
2023-08-13 17:21:49 +05:30
import _babase
2021-03-29 03:24:13 +05:30
if TYPE_CHECKING:
2022-06-30 00:31:52 +05:30
from typing import Sequence
2021-03-29 03:24:13 +05:30
def get_human_readable_user_scripts_path() -> str:
"""Return a human readable location of user-scripts.
This is NOT a valid filesystem path; may be something like "(SD Card)".
"""
2023-08-13 17:21:49 +05:30
app = _babase.app
2023-09-30 17:21:33 +05:30
path: str | None = app.env.python_directory_user
2021-03-29 03:24:13 +05:30
if path is None:
return '<Not Available>'
2022-07-16 17:59:14 +05:30
# These days, on Android, we use getExternalFilesDir() as the base of our
# app's user-scripts dir, which gives us paths like:
# /storage/emulated/0/Android/data/net.froemling.bombsquad/files
# Userspace apps tend to show that as:
# Android/data/net.froemling.bombsquad/files
# We'd like to display it that way, but I'm not sure if there's a clean
# way to get the root of the external storage area (/storage/emulated/0)
# so that we could strip it off. There is
# Environment.getExternalStorageDirectory() but that is deprecated.
# So for now let's just be conservative and trim off recognized prefixes
# and show the whole ugly path as a fallback.
# Note that we used to use externalStorageText resource but gonna try
# without it for now. (simply 'foo' instead of <External Storage>/foo).
2023-08-13 17:21:49 +05:30
if app.classic is not None and app.classic.platform == 'android':
2022-07-16 17:59:14 +05:30
for pre in ['/storage/emulated/0/']:
if path.startswith(pre):
path = path.removeprefix(pre)
break
2021-03-29 03:24:13 +05:30
return path
def _request_storage_permission() -> bool:
"""If needed, requests storage permission from the user (& return true)."""
2023-08-13 17:21:49 +05:30
from babase._language import Lstr
2023-08-13 17:21:49 +05:30
# noinspection PyProtectedMember
# (PyCharm inspection bug?)
from babase._mgen.enums import Permission
if not _babase.have_permission(Permission.STORAGE):
_babase.getsimplesound('error').play()
_babase.screenmessage(
Lstr(resource='storagePermissionAccessText'), color=(1, 0, 0)
)
2023-08-13 17:21:49 +05:30
_babase.apptimer(
1.0, lambda: _babase.request_permission(Permission.STORAGE)
)
2021-03-29 03:24:13 +05:30
return True
return False
def show_user_scripts() -> None:
"""Open or nicely print the location of the user-scripts directory."""
2023-08-13 17:21:49 +05:30
app = _babase.app
2023-09-30 17:21:33 +05:30
env = app.env
2021-03-29 03:24:13 +05:30
# First off, if we need permission for this, ask for it.
if _request_storage_permission():
return
2023-08-13 17:21:49 +05:30
# If we're running in a nonstandard environment its possible this is unset.
2023-09-30 17:21:33 +05:30
if env.python_directory_user is None:
2023-08-13 17:21:49 +05:30
_babase.screenmessage('<unset>')
return
2021-03-29 03:24:13 +05:30
# Secondly, if the dir doesn't exist, attempt to make it.
2023-09-30 17:21:33 +05:30
if not os.path.exists(env.python_directory_user):
os.makedirs(env.python_directory_user)
2021-03-29 03:24:13 +05:30
# On android, attempt to write a file in their user-scripts dir telling
# them about modding. This also has the side-effect of allowing us to
# media-scan that dir so it shows up in android-file-transfer, since it
# doesn't seem like there's a way to inform the media scanner of an empty
# directory, which means they would have to reboot their device before
# they can see it.
2023-08-13 17:21:49 +05:30
if app.classic is not None and app.classic.platform == 'android':
2021-03-29 03:24:13 +05:30
try:
2023-09-30 17:21:33 +05:30
usd: str | None = env.python_directory_user
2021-03-29 03:24:13 +05:30
if usd is not None and os.path.isdir(usd):
file_name = usd + '/about_this_folder.txt'
2021-10-26 23:24:50 +05:30
with open(file_name, 'w', encoding='utf-8') as outfile:
outfile.write(
'You can drop files in here to mod the game.'
' See settings/advanced'
' in the game for more info.'
)
2022-12-25 00:39:49 +05:30
2021-03-29 03:24:13 +05:30
except Exception:
2023-08-13 17:21:49 +05:30
from babase import _error
2021-03-29 03:24:13 +05:30
_error.print_exception('error writing about_this_folder stuff')
# On a few platforms we try to open the dir in the UI.
2023-08-13 17:21:49 +05:30
if app.classic is not None and app.classic.platform in ['mac', 'windows']:
2023-09-30 17:21:33 +05:30
_babase.open_dir_externally(env.python_directory_user)
2021-03-29 03:24:13 +05:30
# Otherwise we just print a pretty version of it.
else:
2023-08-13 17:21:49 +05:30
_babase.screenmessage(get_human_readable_user_scripts_path())
2021-03-29 03:24:13 +05:30
def create_user_system_scripts() -> None:
2023-08-13 17:21:49 +05:30
"""Set up a copy of Ballistica app scripts under user scripts dir.
2021-03-29 03:24:13 +05:30
2023-08-13 17:21:49 +05:30
(for editing and experimenting)
2021-03-29 03:24:13 +05:30
"""
import shutil
2023-08-13 17:21:49 +05:30
app = _babase.app
2023-09-30 17:21:33 +05:30
env = app.env
2021-03-29 03:24:13 +05:30
# First off, if we need permission for this, ask for it.
if _request_storage_permission():
return
2023-08-13 17:21:49 +05:30
# Its possible these are unset in non-standard environments.
2023-09-30 17:21:33 +05:30
if env.python_directory_user is None:
2023-08-13 17:21:49 +05:30
raise RuntimeError('user python dir unset')
2023-09-30 17:21:33 +05:30
if env.python_directory_app is None:
2023-08-13 17:21:49 +05:30
raise RuntimeError('app python dir unset')
2023-09-30 17:21:33 +05:30
path = f'{env.python_directory_user}/sys/{env.version}'
2021-03-29 03:24:13 +05:30
pathtmp = path + '_tmp'
if os.path.exists(path):
shutil.rmtree(path)
if os.path.exists(pathtmp):
shutil.rmtree(pathtmp)
def _ignore_filter(src: str, names: Sequence[str]) -> Sequence[str]:
del src, names # Unused
# We simply skip all __pycache__ directories. (the user would have
# to blow them away anyway to make changes;
# See https://github.com/efroemling/ballistica/wiki
# /Knowledge-Nuggets#python-cache-files-gotcha
return ('__pycache__',)
2021-03-29 03:24:13 +05:30
2023-09-30 17:21:33 +05:30
print(f'COPYING "{env.python_directory_app}" -> "{pathtmp}".')
shutil.copytree(env.python_directory_app, pathtmp, ignore=_ignore_filter)
2021-03-29 03:24:13 +05:30
print(f'MOVING "{pathtmp}" -> "{path}".')
shutil.move(pathtmp, path)
print(
f"Created system scripts at :'{path}"
2023-08-13 17:21:49 +05:30
f"'\nRestart {_babase.appname()} to use them."
f' (use babase.quit() to exit the game)'
)
2023-08-13 17:21:49 +05:30
if app.classic is not None and app.classic.platform == 'android':
print(
'Note: the new files may not be visible via '
'android-file-transfer until you restart your device.'
)
2021-03-29 03:24:13 +05:30
def delete_user_system_scripts() -> None:
"""Clean out the scripts created by create_user_system_scripts()."""
import shutil
2023-09-30 17:21:33 +05:30
env = _babase.app.env
2023-08-13 17:21:49 +05:30
2023-09-30 17:21:33 +05:30
if env.python_directory_user is None:
2023-08-13 17:21:49 +05:30
raise RuntimeError('user python dir unset')
2023-09-30 17:21:33 +05:30
path = f'{env.python_directory_user}/sys/{env.version}'
2021-03-29 03:24:13 +05:30
if os.path.exists(path):
shutil.rmtree(path)
print(
f'User system scripts deleted.\n'
2023-08-13 17:21:49 +05:30
f'Restart {_babase.appname()} to use internal'
f' scripts. (use babase.quit() to exit the game)'
)
2021-03-29 03:24:13 +05:30
else:
2023-08-13 17:21:49 +05:30
print(f"User system scripts not found at '{path}'.")
2021-03-29 03:24:13 +05:30
# If the sys path is empty, kill it.
2023-09-30 17:21:33 +05:30
dpath = env.python_directory_user + '/sys'
2021-03-29 03:24:13 +05:30
if os.path.isdir(dpath) and not os.listdir(dpath):
os.rmdir(dpath)