class ChoiceDialogButton(DialogButton): ChoiceButton = Language.String(This.Mod.Namespace + ".Setting_Dialogs.Choice_Button", fallbackText = "Choice_Button") # type: Language.String def __init__ (self, selected: bool = False, *args, **kwargs): """ :param selected: Whether or not the button's text will have a selected look. :type selected: bool """ super().__init__(*args, **kwargs) self.Selected = selected # type: bool def GenerateDialogResponse (self) -> ui_dialog.UiDialogResponse: if self.Selected: valueButtonStringTokens = ("> ", self.Text, " <") else: valueButtonStringTokens = ("", self.Text, "") if self.ChoiceButton.IdentifierIsRegistered(): buttonTextString = self.ChoiceButton.GetCallableLocalizationString(*valueButtonStringTokens) else: buttonTextString = self.Text responseArguments = { "dialog_response_id": self.ResponseID, "sort_order": self.SortOrder, "text": buttonTextString } if self.SubText is not None: buttonSubTextString = lambda *args, **kwargs: self.SubText responseArguments["subtext"] = buttonSubTextString response = ui_dialog.UiDialogResponse(**responseArguments) return response
from __future__ import annotations import typing import webbrowser from NeonOcean.S4.Order import Debug, Language, Mods, This from NeonOcean.S4.Order.Tools import Exceptions from NeonOcean.S4.Order.UI import Dialogs from sims4 import localization from ui import ui_dialog OpenBrowserDialogText = Language.String( This.Mod.Namespace + ".Generic_Dialogs.Open_Browser_Dialog.Text") # type: Language.String OpenBrowserDialogYesButton = Language.String( This.Mod.Namespace + ".Generic_Dialogs.Open_Browser_Dialog.Yes_Button", fallbackText="Yes_Button") # type: Language.String OpenBrowserDialogNoButton = Language.String( This.Mod.Namespace + ".Generic_Dialogs.Open_Browser_Dialog.No_Button", fallbackText="No_Button") # type: Language.String AboutModDialogTitle = Language.String( This.Mod.Namespace + ".Generic_Dialogs.About_Mod_Dialog.Title") # type: Language.String AboutModDialogText = Language.String( This.Mod.Namespace + ".Generic_Dialogs.About_Mod_Dialog.Text") # type: Language.String AboutModDialogOkButton = Language.String( This.Mod.Namespace + ".Generic_Dialogs.About_Mod_Dialog.Ok_Button", fallbackText="Ok_Button") # type: Language.String AboutModDialogUnknown = Language.String(
from __future__ import annotations import inspect import sys import types import typing from NeonOcean.S4.Order import Debug, Language, Mods, This from NeonOcean.S4.Order.UI import Dialogs from ui import ui_dialog DialogTitle = Language.String(This.Mod.Namespace + ".Reset.Dialog.Title") # type: Language.String DialogText = Language.String(This.Mod.Namespace + ".Reset.Dialog.Text") # type: Language.String DialogEverythingButton = Language.String(This.Mod.Namespace + ".Reset.Dialog.Everything_Button", fallbackText = "Everything_Button") # type: Language.String DialogSettingsButton = Language.String(This.Mod.Namespace + ".Reset.Dialog.Settings_Button", fallbackText = "Settings_Button") # type: Language.String DialogCancelButton = Language.String(This.Mod.Namespace + ".Reset.Dialog.Cancel_Button", fallbackText = "Cancel_Button") # type: Language.String ConfirmDialogTitle = Language.String(This.Mod.Namespace + ".Reset.Confirm_Dialog.Title") # type: Language.String ConfirmDialogEverythingText = Language.String(This.Mod.Namespace + ".Reset.Confirm_Dialog.Everything_Text") # type: Language.String ConfirmDialogSettingsText = Language.String(This.Mod.Namespace + ".Reset.Confirm_Dialog.Settings_Text") # type: Language.String ConfirmDialogYesButton = Language.String(This.Mod.Namespace + ".Reset.Confirm_Dialog.Yes_Button", fallbackText = "Yes_Button") # type: Language.String ConfirmDialogNoButton = Language.String(This.Mod.Namespace + ".Reset.Confirm_Dialog.No_Button", fallbackText = "No_Button") # type: Language.String def ResetEverything (mod: Mods.Mod) -> bool: """ Resets everything that can be reset in the target mod. :return: Returns true if successful or false if not. :rtype: bool """
from __future__ import annotations import abc import traceback import typing import services from NeonOcean.S4.Order import Debug, Language, This from NeonOcean.S4.Order.Tools import Exceptions, TextBuilder from NeonOcean.S4.Order.UI import SettingsShared as UISettingsShared from sims4 import localization from ui import ui_dialog, ui_dialog_picker InvalidInputNotificationTitle = Language.String(This.Mod.Namespace + ".Setting_Dialogs.Invalid_Input_Notification.Title") # type: Language.String InvalidInputNotificationText = Language.String(This.Mod.Namespace + ".Setting_Dialogs.Invalid_Input_Notification.Text") # type: Language.String PresetConfirmDialogTitle = Language.String(This.Mod.Namespace + ".Setting_Dialogs.Preset_Confirm_Dialog.Title") # type: Language.String PresetConfirmDialogText = Language.String(This.Mod.Namespace + ".Setting_Dialogs.Preset_Confirm_Dialog.Text") # type: Language.String PresetConfirmDialogYesButton = Language.String(This.Mod.Namespace + ".Setting_Dialogs.Preset_Confirm_Dialog.Yes_Button", fallbackText = "Preset_Confirm_Dialog.Yes_Button") # type: Language.String PresetConfirmDialogNoButton = Language.String(This.Mod.Namespace + ".Setting_Dialogs.Preset_Confirm_Dialog.No_Button", fallbackText = "Preset_Confirm_Dialog.No_Button") # type: Language.String class DialogButton: def __init__ (self, responseID: int, sortOrder: int, callback: typing.Callable[[ui_dialog.UiDialog], None], text: localization.LocalizedString, subText: localization.LocalizedString = None): """ :param responseID: The identifier used to determine which response the dialog was given. :type responseID: int
from __future__ import annotations import interactions from NeonOcean.S4.Order import Language, Mods, This from event_testing import results, test_base from sims4.tuning import tunable DisabledTooltip = Language.String( This.Mod.Namespace + ".Interactions.Support.Dependent.Disabled_Tooltip", fallbackText="Disabled_Tooltip") class DependentTest(test_base.BaseTest): # noinspection SpellCheckingInspection def __call__(self, affordance): if affordance is None: return results.TestResult(False) if not issubclass(affordance, DependentExtension): return results.TestResult(True) if affordance.DependentOnMod: if affordance.DependentMod is not None: if not affordance.DependentMod.IsLoaded(): return results.TestResult( False, tooltip=DisabledTooltip.GetCallableLocalizationString( affordance.DependentMod.Namespace)) return results.TestResult(True)
import json import os import sys import types import typing import time import zipfile import zone from NeonOcean.S4.Order import Debug, Language, LoadingEvents, LoadingShared, Mods, Paths, This from NeonOcean.S4.Order.Tools import Exceptions, Parse, Version from NeonOcean.S4.Order.UI import Notifications from sims4.importer import custom_import from ui import ui_dialog_notification LoadingFailureNotificationTitle = Language.String(This.Mod.Namespace + ".Mod_Loading.Failure_Notification.Title") # type: Language.String LoadingFailureNotificationText = Language.String(This.Mod.Namespace + ".Mod_Loading.Failure_Notification.Text") # type: Language.String InvalidSetupNotificationTitle = Language.String(This.Mod.Namespace + ".Mod_Loading.Invalid_Setup_Notification.Title") # type: Language.String InvalidSetupNotificationText = Language.String(This.Mod.Namespace + ".Mod_Loading.Invalid_Setup_Notification.Text") # type: Language.String CascadeFailureNotificationTitle = Language.String(This.Mod.Namespace + ".Mod_Loading.Cascade_Failure_Notification.Title") # type: Language.String CascadeFailureNotificationText = Language.String(This.Mod.Namespace + ".Mod_Loading.Cascade_Failure_Notification.Text") # type: Language.String NotLoadedFailureNotificationTitle = Language.String(This.Mod.Namespace + ".Mod_Loading.Not_Loaded_Notification.Title") # type: Language.String NotLoadedFailureNotificationText = Language.String(This.Mod.Namespace + ".Mod_Loading.Not_Loaded_Notification.Text") # type: Language.String _allLoaders = list() # type: typing.List[_Loader] _autoLoad = True # type: bool
import json import os import random import threading import typing from http import client from urllib import request import zone from NeonOcean.S4.Order import Debug, Director, Information, Language, Mods, Paths, Settings, This, Websites from NeonOcean.S4.Order.Tools import Exceptions, Parse, Timer, Version from NeonOcean.S4.Order.UI import Notifications from sims4 import collections from ui import ui_dialog UpdateNotificationTitle = Language.String( This.Mod.Namespace + ".Distribution.Update_Notification.Title") UpdateNotificationReleaseText = Language.String( This.Mod.Namespace + ".Distribution.Update_Notification.Release_Text") UpdateNotificationPreviewText = Language.String( This.Mod.Namespace + ".Distribution.Update_Notification.Preview_Text") UpdateNotificationButton = Language.String( This.Mod.Namespace + ".Distribution.Update_Notification.Button") PromotionDefaultTitle = Language.String( This.Mod.Namespace + ".Distribution.Promotions.Default.Title") PromotionDefaultButton = Language.String( This.Mod.Namespace + ".Distribution.Promotions.Default.Button") _distributionURL = "http://dist.mods.neonoceancreations.com" # type: str _updatesTicker = None # type: typing.Optional[Timer.Timer]
class Logger: WriteFailureNotificationTitle = Language.String( This.Mod.Namespace + ".Write_Failure_Notification.Title") WriteFailureNotificationText = Language.String( This.Mod.Namespace + ".Write_Failure_Notification.Text") _globalSessionID = "SessionID" # type: str _globalSessionStartTime = "SessionStartTime" # type: str _globalShownWriteFailureNotification = "ShownWriteFailureNotification" # type: str def __init__(self, loggingRootPath: str, hostNamespace: str = This.Mod.Namespace): """ An object for logging debug information. Logs will be written to a folder named either by the global NeonOcean debugging start time, or the time ChangeLogFile() was last called for this object. :param loggingRootPath: The root path all reports sent to this logger object will be written. :type loggingRootPath: str :param hostNamespace: Errors made by this logger object will show up under this namespace. :type hostNamespace: str """ if not isinstance(loggingRootPath, str): raise Exceptions.IncorrectTypeException(loggingRootPath, "loggingRootPath", (str, )) if not isinstance(hostNamespace, str): raise Exceptions.IncorrectTypeException(hostNamespace, "hostNamespace", (str, )) self.DebugGlobal = Global.GetModule("Debug") if not hasattr(self.DebugGlobal, self._globalSessionID) or not isinstance( getattr(self.DebugGlobal, self._globalSessionID), uuid.UUID): setattr(self.DebugGlobal, self._globalSessionID, uuid.uuid4()) if not hasattr(self.DebugGlobal, self._globalSessionStartTime) or not isinstance( getattr(self.DebugGlobal, self._globalSessionStartTime), datetime.datetime): setattr(self.DebugGlobal, self._globalSessionStartTime, datetime.datetime.now()) if not hasattr(self.DebugGlobal, self._globalShownWriteFailureNotification): setattr(self.DebugGlobal, self._globalShownWriteFailureNotification, False) self.HostNamespace = hostNamespace # type: str self._reportStorage = list() # type: typing.List[Report] self._flushThread = None # type: typing.Optional[threading.Thread] self._loggingRootPath = loggingRootPath # type: str self._loggingDirectoryName = GetDateTimePathString( getattr(self.DebugGlobal, self._globalSessionStartTime)) # type: str self._writeFailureCount = 0 # type: int self._writeFailureLimit = 2 # type: int self._isContinuation = False # type: bool self._sessionInformation = self._CreateSessionInformation( ) # type: str self._modsDirectoryInformation = self._CreateModsDirectoryInformation( ) # type: str def Log(self, *args, **kwargs) -> None: raise NotImplementedError() def GetLoggingRootPath(self) -> str: return self._loggingRootPath def GetLoggingDirectoryName(self) -> str: return self._loggingDirectoryName def IsContinuation(self) -> bool: return self._isContinuation def GetSessionID(self) -> uuid.UUID: return getattr(self.DebugGlobal, self._globalSessionID) def GetSessionStartTime(self) -> datetime.datetime: return getattr(self.DebugGlobal, self._globalSessionStartTime) def GetLogStartBytes(self) -> bytes: logStartString = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + os.linesep + \ "<LogFile SessionID=\"%s\" SessionStartTime=\"%s\">" % (str(self.GetSessionID()), self.GetSessionStartTime().isoformat()) + os.linesep return logStartString.encode("utf-8") # type: bytes def GetLogEndBytes(self) -> bytes: return (os.linesep + "</LogFile>").encode("utf-8") # type: bytes def ChangeLogFile(self) -> None: """ Change the current directory name for a new one. The new directory name will be the time this method was called. :rtype: None """ self._loggingDirectoryName = GetDateTimePathString( datetime.datetime.now()) self._isContinuation = True self._sessionInformation = self._CreateSessionInformation() self._modsDirectoryInformation = self._CreateModsDirectoryInformation() def Flush(self) -> None: mainThread = threading.main_thread() # type: threading.Thread mainThreadAlive = mainThread.is_alive() # type: bool currentThreadIsMain = mainThread == threading.current_thread( ) # type: bool if not mainThreadAlive and not currentThreadIsMain: # Unfortunately any daemonic thread's reports will not be written unless they are logged before the last reports from the main thread are written. # At this point, any flush thread started will not finish in time before it all comes to an end. Though the flush thread is not daemonic, Python will # not stop for it while a shutdown is already in progress. The best way of getting any more reports out during this time would be to co-opt the # main thread and make it wait while we write the stored reports. return if self._flushThread is not None: if not mainThreadAlive: if self._flushThread.is_alive(): self._flushThread.join(1) if self._flushThread.is_alive(): return else: self._flushThread = None else: return if not mainThreadAlive: flushThread = self._CreateFlushThread() # type: threading.Thread self._SetFlushThreadUnsafe(flushThread) flushThread.start() flushThread.join(1) else: flushThread = self._CreateFlushThread() # type: threading.Thread try: self._SetFlushThread(flushThread) except ToolsThreading.SimultaneousCallException: return mainThreadStateChanged = not mainThread.is_alive() # type: bool if mainThreadStateChanged: self.Flush() return flushThread.start() def _CreateFlushThread(self) -> threading.Thread: mainThread = threading.main_thread() # type: threading.Thread mainThreadAlive = mainThread.is_alive() # type: bool def _FlushThread() -> None: nonlocal mainThreadAlive mainThreadAlive = mainThread.is_alive() try: while len(self._reportStorage) != 0: reportStorageLength = len(self._reportStorage) # type: int targetReports = self._reportStorage[:reportStorageLength] self._reportStorage = self._reportStorage[ reportStorageLength:] filteredReports = self._FilterReports(targetReports) self._LogAllReports(filteredReports) mainThreadAlive = mainThread.is_alive() if not mainThreadAlive: break finally: self._flushThread = None if mainThreadAlive: if len(self._reportStorage) != 0: self.Flush() return threading.Thread(target=_FlushThread, daemon=False) @ToolsThreading.NotThreadSafe(raiseException=True) def _SetFlushThread(self, flushThread: threading.Thread) -> None: self._SetFlushThreadUnsafe(flushThread) def _SetFlushThreadUnsafe(self, flushThread: threading.Thread) -> None: self._flushThread = flushThread def _FilterReports(self, reports: typing.List[Report]) -> typing.List[Report]: return list(reports) def _LogAllReports(self, reports: typing.List[Report]) -> None: raise NotImplementedError() def _CreateSessionInformation(self) -> str: try: installedPacks = list() # type: typing.List[str] for packTuple in common.Pack.items( ): # type: typing.Tuple[str, common.Pack] if packTuple[1] == common.Pack.BASE_GAME: continue packAvailable = common.is_available_pack(packTuple[1]) if packAvailable: prefixExpansionPairs = { "EP": "Expansion Pack ", "GP": "Game Pack ", "SP": "Stuff Pack " } # type: typing.Dict[str, str] packText = packTuple[0] # type: str for prefix, prefixExpansion in prefixExpansionPairs.items( ): # type: str if packText.startswith(prefix): packText = packText.replace( prefix, prefixExpansion, 1) break installedPacks.append(packText) sessionDictionary = { "SessionID": str(self.GetSessionID()), "SessionStartTime": self.GetSessionStartTime().isoformat(), "IsContinuation": self.IsContinuation(), "OS": platform.system(), "OSVersion": platform.version(), "InstalledPacks": installedPacks } return json.JSONEncoder(indent="\t").encode(sessionDictionary) except Exception as e: return "Failed to get session information\n" + FormatException(e) def _CreateModsDirectoryInformation(self) -> str: try: modFolderString = os.path.split( Paths.ModsPath)[1] + " {" + os.path.split( Paths.ModsPath)[1] + "}" # type: str for directoryRoot, directoryNames, fileNames in os.walk( Paths.ModsPath): # type: str, list, list depth = 1 if directoryRoot != Paths.ModsPath: depth = len( directoryRoot.replace( Paths.ModsPath + os.path.sep, "").split( os.path.sep)) + 1 # type: int indention = "\t" * depth # type: str newString = "" # type: str for directory in directoryNames: newString += "\n" + indention + directory + " {" + directory + "}" for file in fileNames: newString += "\n" + indention + file + " (" + str( os.path.getsize(os.path.join(directoryRoot, file))) + " B)" if len(newString) == 0: newString = "\n" newString += "\n" modFolderString = modFolderString.replace( "{" + os.path.split(directoryRoot)[1] + "}", "{" + newString + "\t" * (depth - 1) + "}", 1) return modFolderString except Exception as e: return "Failed to get mod information\n" + FormatException(e) def _VerifyLogFile(self, logFilePath: str) -> None: logEndBytes = self.GetLogEndBytes() # type: bytes with open(logFilePath, "rb") as logFile: logFile.seek(-len(logEndBytes), os.SEEK_END) if logEndBytes != logFile.read(): raise Exception( "The end of the log file doesn't match what was expected.") def _ShowWriteFailureDialog(self, exception: Exception) -> None: Notifications.ShowNotification( queue=True, title=self.WriteFailureNotificationTitle. GetCallableLocalizationString(), text=self.WriteFailureNotificationText. GetCallableLocalizationString(FormatException(exception)), expand_behavior=ui_dialog_notification.UiDialogNotification. UiDialogNotificationExpandBehavior.FORCE_EXPAND, urgency=ui_dialog_notification.UiDialogNotification. UiDialogNotificationUrgency.URGENT)