def method_sender(self: ModObjects.SDKMod, *args: Any, **kwargs: Any) -> None: # Get the current world info, and from that, the current list of replicated players. world_info = unrealsdk.GetEngine().GetCurrentWorldInfo() PRIs = list(world_info.GRI.PRIArray) # Determine whether this message should be sent to a server and whether it should be sent to # client(s). If neither, we have nothing to send. # ENetMode.NM_Client == 3 send_server = (method_sender._is_server and world_info.NetMode == 3) # type: ignore send_client = ( method_sender._is_client and world_info.NetMode != 3 and len(PRIs) > 1 # type: ignore ) if not (send_server or send_client): return # Use the inspect module to correctly map the received arguments to their parameters. bindings = signature.bind(*args, **kwargs) # If the arguments include one specifying a player controller we will be messaging, retrieve # which one, and purge it. remote_pc = bindings.arguments.get("PC", None) if specifies_pc: bindings.arguments["PC"] = None # Serialize the arguments we were called with using the class's serializer function. arguments = type(self).NetworkSerialize({ "args": bindings.args, "kwargs": bindings.kwargs }) # Retrieve our own player controller. local_pc = unrealsdk.GetEngine().GamePlayers[0].Actor # If we're sending a message to the server, send it using our own player controller. if send_server: _enqueue_message(_Message(local_pc, message_type, arguments, True)) # If we're sending to a client, and the mapped arguments do specify a specific player # controller to message, we will spend this message to it. elif send_client and remote_pc is not None: if type( remote_pc ) is unrealsdk.UObject and remote_pc.Class.Name == "WillowPlayerController": _enqueue_message( _Message(remote_pc, message_type, arguments, False)) else: raise TypeError( f"Invalid player controller specified for {message_type}. Expected" f" unrealsdk.UObject of UClass WillowPlayerController, received {remote_pc}." ) # If no player controller was specified, send a message to each replicated player that has a # player controller that is not our own. elif send_client: for PRI in PRIs: if PRI.Owner is not None and PRI.Owner is not local_pc: _enqueue_message( _Message(PRI.Owner, message_type, arguments, False))
def OnStart(self, msg: JSON) -> None: self.ShowRedemption(msg) PC = unrealsdk.GetEngine().GamePlayers[0].Actor PC.ServerRCon( f"set {PC.PathName(PC.Pawn)} GroundSpeedBaseValue {self.NewSpeed}") PC.ServerRCon(f"set {PC.PathName(PC.Pawn)} AccelRate {self.NewSpeed}")
def BringWeaponsUpAfterPutDown(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: if caller.MyWillowPC != unrealsdk.GetEngine().GamePlayers[0].Actor: return True weapon = caller.MyWillowPawn.InvManager.InventoryChain while weapon is not None: if weapon.QuickSelectSlot == params.MainHandWeaponSlot: break weapon = weapon.Inventory # If you dropped the equiped slot default to the start of the list - not sure if other # behaviour might be better? if weapon is None: weapon = caller.MyWillowPawn.InvManager.InventoryChain # If you dropped all weapons just let the game handle it if weapon is None: return True caller.ForceRefreshSkills() caller.ClientBringWeaponsUpAfterPutDown(weapon, self.DupeWeapon(weapon)) return False
def escape_to_main_menu(self, junk): """ Escape out to the main menu """ pc = unrealsdk.GetEngine().GamePlayers[0].Actor pc.ReturnToTitleScreen(False, False) self.setNextDelay(self.mainmenu_delay)
def _GameLoad(self, caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: PC = unrealsdk.GetEngine().GamePlayers[0].Actor if PC and PC.Pawn: PC.Pawn.InvManager.InventorySlotMax_Misc = self.BackpackSize.CurrentValue return True
def Show(self) -> None: """ Displays the training box. """ self._TrainingBox = unrealsdk.GetEngine().GamePlayers[0].Actor.GFxUIManager.ShowTrainingDialog( self.Message, self.Title, self.MinDuration, self.MenuHint, not self.PausesGame ) self._TrainingBox.SetPriority(self.Priority) self._TrainingBox.ApplyLayout() def HandleInputKey(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: if caller == self._TrainingBox: self.OnInput(params.ukey, params.uevent) # We don't have a good function to hook for when this exits so we have to decode it # from the key presses if self._TrainingBox is not None and self._TrainingBox.DelayUntilShowOk < 0 and params.uevent == 1: use_key = "FAKE" if caller.GetPC().PlayerInput is not None: use_key = caller.GetPC().PlayerInput.GetKeyForAction("Use", True) if params.ukey in self._ExitKeys or params.ukey == use_key: unrealsdk.RemoveHook("WillowGame.WillowGFxTrainingDialogBox.HandleInputKey", "CustomTrainingBox") self._TrainingBox = None self.OnExit() return True unrealsdk.RegisterHook("WillowGame.WillowGFxTrainingDialogBox.HandleInputKey", "CustomTrainingBox", HandleInputKey)
def __init__(self) -> None: # Hopefully I can remove this in a future SDK update self.Author += "\nVersion: " + str(self.Version) # type: ignore if unrealsdk.GetEngine().GetEngineVersion() == 8639: self.FORCE_LOAD = self._FORCE_LOAD_BL2 else: self.FORCE_LOAD = self._FORCE_LOAD_TPS # All classes we will be searching through and their handlers # Do this in here so we can actually get a proper reference to the functions self.CLASS_HANDLER_MAP = { "InventoryBalanceDefinition": self.HandleInvBalance, "ItemBalanceDefinition": self.HandleInvBalance, "ClassModBalanceDefinition": self.HandleInvBalance, "WeaponBalanceDefinition": self.HandleInvBalance, "MissionWeaponBalanceDefinition": self.HandleInvBalance, "ItemPartListCollectionDefinition": self.HandlePartListCollection, "WeaponPartListCollectionDefinition": self.HandlePartListCollection, "ItemPartListDefinition": self.HandleRawPartList, "WeaponPartListDefinition": self.HandleRawPartList, "ItemNamePartDefinition": self.HandleNamePart, "WeaponNamePartDefinition": self.HandleNamePart, "InteractiveObjectBalanceDefinition": self.HandleGradedObject, "VehicleBalanceDefinition": self.HandleGradedObject }
def OnPress(self) -> None: pawn = unrealsdk.GetEngine().GamePlayers[0].Actor.Pawn # Only activate if in FFYL if pawn.bIsInjured and not pawn.bIsDead: pawn.GoFromInjuredToHealthy() pawn.ClientOnRevived()
def _OnDownloadableContentListRead(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: """ This function is called to fill in the dlc menu, we overwrite it with our mods instead. """ global _current_mod_list _load_favourite_mods() caller.ClearFilters() caller.SetFilterFromString("compatibility", "Utility Mods", "isCompatibility:1") caller.SetFilterFromString("addon", "Content Mods", "isAddOn:1") caller.SetFilterFromString("seasonpass", "Gameplay Mods", "isSeasonPass:1") caller.SetFilterFromString("misc", "Libraries", "isMisc:1") caller.SetFilterFromStringAndSortNew("all", "All Mods", "") caller.SetStoreHeader("Mods", False, "By Abahbob", "Mod Manager") _current_mod_list = [_general_instance] + GetOrderedModList() # type: ignore translation_context = unrealsdk.GetEngine().GamePlayers[0].GetTranslationContext() for idx, mod in enumerate(_current_mod_list): # This is weird and crashes if you don't have the second arg, but also crashes most the time # when you try to access something on it - seems like a garbage pointer item, _ = caller.CreateMarketplaceItem() item.SetString(caller.Prop_offeringId, str(idx), translation_context) _update_mod_list_item(mod, item, caller, translation_context) caller.AddContentData(item) caller.PostContentLoaded(True) return False
def Condition(self) -> bool: if not IsInGame(): return False PC = unrealsdk.GetEngine().GamePlayers[0].Actor if PC is None or PC.Pawn is None: return False return PC.Pawn.bIsInjured and not PC.Pawn.bIsDead
def GenerateInventory(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: # Whenever a vendor inventory is generated, update our cached costs # Unfortuantly ShopInventory is a fixed array, which we can't iterate though, so we have # to do a findall to find the items unrealsdk.DoInjectedCallNext() caller.GenerateInventory() PC = unrealsdk.GetEngine().GamePlayers[0].Actor if caller.ShopType == 1: self.AmmoCosts[caller] = {} for item in unrealsdk.FindAll("WillowUsableItem"): if item.Owner != caller: continue if item.DefinitionData is None or item.DefinitionData.ItemDefinition is None: continue if caller.ShopType == 2: if item.DefinitionData.ItemDefinition.Name == "BuffDrink_HealingInstant": self.VialCosts[caller] = caller.GetSellingPriceForInventory(item, PC, 1) break elif caller.ShopType == 1: name = item.DefinitionData.ItemDefinition.Name if name not in self.AMMO_COUNTS: continue info = self.AMMO_COUNTS[name] price = caller.GetSellingPriceForInventory(item, PC, 1) / info.BulletsPerItem self.AmmoCosts[caller][info.ResourcePoolName] = price return False
def GetItemStatus(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: if params.WPC != unrealsdk.GetEngine().GamePlayers[0].Actor: return True if not self.IsOn: return True unrealsdk.DoInjectedCallNext() status = caller.GetItemStatus(params.Item, params.WPC, params.ItemPrice) # If we get back SIS_ItemCanBePurchased (0) we don't have to do anything if status == 0: return True # Otherwise temporarily give all the money you'd need to purcahse it and check again PRI = params.WPC.PlayerReplicationInfo currency = caller.GetCurrencyTypeInventoryIsSoldIn(params.Item) wallet = PRI.GetCurrencyOnHand(currency) PRI.SetCurrencyOnHand(currency, params.ItemPrice) unrealsdk.DoInjectedCallNext() status = caller.GetItemStatus(params.Item, params.WPC, params.ItemPrice) # Revert your money back PRI.SetCurrencyOnHand(currency, wallet) # If the status now is SIS_ItemCanBePurchased (0) then we were just missing money, and # we want the actual status to be ignore that # We can't directly change the return value of the function, only if it executes # However, if the function doesn't execute then it's return value is None # As luck would have it this ends up casting to 0 - the exact value we want to set return bool(status != 0)
def EquipInitialWeapons(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: PC = unrealsdk.GetEngine().GamePlayers[0].Actor if PC.Pawn is None: return False weap = PC.Pawn.Weapon if weap is None: return False # This is the only real bit of the function we overwrite weapAlt = self.DupeWeapon(weap) PC.Pawn.OffHandWeapon = weapAlt PC.Pawn.InvManager.SetCurrentWeapon(weap, False) PC.Pawn.InvManager.SetCurrentWeapon(weapAlt, True) weap.RefillClip() weapAlt.RefillClip() if PC.bInSprintState: caller.SetTimer( min(weap.GetEquipTime(), weapAlt.GetEquipTime()), False, "SprintTransition") caller.SetLeftSideControl() return False
def TakeDamage(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: PC = unrealsdk.GetEngine().GamePlayers[0].Actor if params.InstigatedBy != PC: return True if not self.IsOn: return True game = unrealsdk.FindAll("WillowCoopGameInfo")[-1] if game.IsFriendlyFire(caller, params.InstigatedBy.Pawn): return True caller.SetShieldStrength(0) # Try set the health to 1 so that your shot kills them, giving xp # Only do it if they have more than 1 health though, so that you don't get stuck in a # loop if you somehow deal less than 1 damage if caller.GetHealth() > 1: caller.SetHealth(1) else: caller.SetHealth(0) return True
def HookMainMenuInput(caller: UObject, function: UFunction, params: FStruct) -> bool: if params.ukey == "M" and params.uevent == 1: gfxmovie = caller if(gfxmovie.IsOverlayMenuOpen() == False): WPCOwner = unrealsdk.GetEngine().GamePlayers[0].Actor gfxmovie.CheckDownloadableContentListCompleted(WPCOwner.GetMyControllerId(), True) return True
def ShowHUDMessage(Title: str, Message: str, Duration: float = 2, MenuHint: int = 0) -> None: """ Displays a small training message on the left side of the screen, like those used for the respawn cost messages. Note that this should not be used for critical messages. It only works while the main game HUD is shown, so it will silently fail if the user is in any menu. Additionally, if another message is shown (including ones the game creates) it will immediately be overwritten. Args: Title: The title of the training message. Message: The message to be shown in the main body of the training message. Duration: How long the training message should be shown for (in seconds). Defaults to 2. MenuHint: If to display a hint to open your menu, and what menu should be opened when you do. Defaults to 0, no hint. 1-5 represent the different the different menu tabs, in the same order as the game: Missions; Map; Inventory; Skills; BAR. Defaults to 0. """ PC = unrealsdk.GetEngine().GamePlayers[0].Actor HUDMovie = PC.GetHUDMovie() if HUDMovie is None: return HUDMovie.ClearTrainingText() HUDMovie.AddTrainingText(Message, Title, Duration, (), "", False, 0, PC.PlayerReplicationInfo, True, MenuHint)
def Condition(self) -> bool: if not IsInGame(): return False PC = unrealsdk.GetEngine().GamePlayers[0].Actor if PC is None: return False return cast(bool, PC.IsActionSkillOnCooldown())
def DisplayRecentDamageForPlayer(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: PC = unrealsdk.GetEngine().GamePlayers[0].Actor if params.PC != PC: return True damage = params.DamageEventData.TotalDamageForDamageType if damage < 10**self.MinDamageSlider.CurrentValue: return True actor = params.DamageEventData.DamagedActor name = actor.PathName(actor) if actor.AIClass is not None and actor.AIClass.DefaultDisplayName is not None: name = actor.AIClass.DefaultDisplayName if actor.BalanceDefinitionState.BalanceDefinition is not None: ptNum = PC.GetCurrentPlaythrough() + 1 for pt in actor.BalanceDefinitionState.BalanceDefinition.PlayThroughs: if pt.PlayThrough > ptNum: continue if pt.DisplayName is None or len(pt.DisplayName) == 0: continue name = pt.DisplayName unrealsdk.Log( f"Dealt {damage} damage to level {actor.GetExpLevel()} {name}") return True
def OnCycle(self) -> None: # If you switch levels while in ghost it gets turned off automatically, so don't do # anything if you toggle it off after changing map engine = unrealsdk.GetEngine() level = engine.GetCurrentWorldInfo().GetStreamingPersistentMapName( ) if level != self.LastLevel and self == self.OFF: self.LastLevel = level return self.LastLevel = level # If you somehow triggered this on the menu don't do anything if level == "menumap": return PC = engine.GamePlayers[0].Actor if self == self.ON: # We need to save the pawn so that we can possess the right one again later self.Pawn = PC.Pawn PC.ServerSpectate() PC.bCollideWorld = False PC.SpectatorCameraSpeed = self.DefaultSpeed self.LastOff = False else: # So that turning this off with a preset doesn't re-tp you if not self.LastOff: self.Pawn.Location = (PC.Location.X, PC.Location.Y, PC.Location.Z) PC.Possess(self.Pawn, True) self.LastOff = True
def TakeDamage(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: if caller != unrealsdk.GetEngine().GamePlayers[0].Actor.Pawn: return True return self != self.FULL
class ScalingAdjuster(unrealsdk.BL2MOD): Name: ClassVar[str] = "Scaling Adjuster" Author: ClassVar[str] = "apple1417" Description: ClassVar[str] = ( "Adds an option to let you easily adjust the game's base scaling value.\n" "Note that you may have to save quit to get values to update.") Types: ClassVar[List[unrealsdk.ModTypes]] = [unrealsdk.ModTypes.Utility] Version: ClassVar[str] = "1.4" Options: List[OptionsWrapper.Base] IS_BL2: ClassVar[bool] = unrealsdk.GetEngine().GetEngineVersion() == 8639 ScalingObject: unrealsdk.UObject ScalingSlider: OptionsWrapper.Slider def __init__(self) -> None: # Hopefully I can remove this in a future SDK update self.Author += "\nVersion: " + str(self.Version) # type: ignore self.ScalingObject = unrealsdk.FindObject( "ConstantAttributeValueResolver", "GD_Balance_HealthAndDamage.HealthAndDamage.Att_UniversalBalanceScaler:ConstantAttributeValueResolver_0" ) self.Options = [ OptionsWrapper.Slider( Caption="BL2 Scaling", Description= "The game's base scaling value (multiplied by 100). 113 means every level the numbers get 13% higher.", StartingValue=113, MinValue=0, MaxValue=500, Increment=1, IsHidden=not self.IS_BL2), OptionsWrapper.Slider( Caption="TPS Scaling", Description= "The game's base scaling value (multiplied by 100). 113 means every level the numbers get 13% higher.", StartingValue=111, MinValue=0, MaxValue=500, Increment=1, IsHidden=self.IS_BL2) ] self.ScalingSlider = cast(OptionsWrapper.Slider, self.Options[0 if self.IS_BL2 else 1]) def Enable(self) -> None: self.ScalingObject.ConstantValue = self.ScalingSlider.CurrentValue def Disable(self) -> None: self.ScalingObject.ConstantValue = self.ScalingSlider.StartingValue def ModOptionChanged(self, option: OptionsWrapper.Base, newValue: Any) -> None: if option == self.ScalingSlider: self.ScalingObject.ConstantValue = cast(int, newValue) / 100
def OnActionSkillEnded(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: if caller.MyWillowPC != unrealsdk.GetEngine().GamePlayers[0].Actor: return True self.WeaponMap = {} return True
def ModOptionChanged(self, option: Options.Base, new_value: Any) -> None: pawn = unrealsdk.GetEngine().GamePlayers[0].Actor.Pawn if pawn is None: return for item in self.GetEquippedItems(): if not item.CanBeUsedBy(pawn): pawn.InvManager.InventoryUnreadied(item, True)
def HandleKill(self, caller, function, params): if caller == self.boss_pawn: self.boss_pawn = None gri = unrealsdk.GetEngine().GetCurrentWorldInfo().GRI gri.UpdateBossBarInfo() gri.InitBossBar(False, self.boss_pawn) self.bar_active = False return True
def ConsumeAmmo(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: pc = unrealsdk.GetEngine().GamePlayers[0].Actor if pc is None or pc.Pawn is None or caller != pc.Pawn.Weapon: return True if self.CurrentValue == InfiniteAmmo.FULL: caller.RefillClip() return self.CurrentValue == InfiniteAmmo.OFF
def HandleVerificationReceived(caller: unrealsdk.UObject, function: unrealsdk.UFunction, params: unrealsdk.FStruct) -> bool: PC = unrealsdk.GetEngine().GamePlayers[0].Actor for i in range(self.DUMMY_AMOUNT): obj = unrealsdk.ConstructObject(unrealsdk.FindClass("SparkServiceConfiguration")) PC.ServerRCon(f"set {PC.PathName(obj)} ServiceName Dummy_{i}") unrealsdk.RemoveHook("GearboxFramework.SparkInitializationProcess.HandleVerificationReceived", "FixHotfixes") return True
def OnEnd(self) -> None: ShowHUDMessage("Crowd Control", f"{self.Name} wore off.") PC = unrealsdk.GetEngine().GamePlayers[0].Actor PC.ServerRCon( f"set {PC.PathName(PC.Pawn)} GroundSpeedBaseValue {self.GroundSpeedBaseValue}" ) PC.ServerRCon(f"set {PC.PathName(PC.Pawn)} AccelRate {self.AccelRate}")
def PlayEmote(self): # up to and including index 16 are animations only, after that come ParticleSystems if self._animation < 17: # We are going to change the animations that are played on melee to play our emotes instead SpecialMoves = [ "GD_Assassin_Streaming.Anims.WeaponAnim_Melee", "GD_Lilac_Psycho_Streaming.Anims.WeaponAnim_Melee", "GD_Mercenary_Streaming.Anims.WeaponAnim_Melee", "GD_PlayerShared.Anims.WeaponAnim_Melee_WeaponBlade", "GD_Siren_Streaming.Anims.WeaponAnim_Melee", "GD_Soldier_Streaming.Anims.WeaponAnim_Melee", "GD_Tulip_Mechro_Streaming.Anims.WeaponAnim_Melee" ] PC = self.GetPlayerController() for Move in SpecialMoves: if unrealsdk.FindObject("SpecialMove_WeaponAction", Move) != None: PC.ConsoleCommand( "set " + Move + " AnimName " + self.ChooseAnimation(), 0) PC.ConsoleCommand( "set " + Move + " EndingCondition EC_Loop", 0) # The first 2 animations are Moxxie only if self._animation in (0, 1): PC.ConsoleCommand( "set " + Move + " AnimSet AnimSet'Anim_Moxxi.Anim_Moxxi'", 0) # Index 2 is Tannis only elif self._animation == 2: PC.ConsoleCommand( "set " + Move + " AnimSet AnimSet'Anim_Tannis.Anim_Tannis'", 0) # Index 3, 4 is Brick only elif self._animation in (3, 4): PC.ConsoleCommand( "set " + Move + " AnimSet AnimSet'Anim_Brick.Anim_Brick'", 0) elif self._animation == 16: PC.ConsoleCommand( "set " + Move + " AnimSet AnimSet'Anim_Assassin.Base_Assassin'", 0) else: PC.ConsoleCommand( "set " + Move + " AnimSet AnimSet'Anim_Generic_NPC.Anim_Generic_NPC'", 0) PC.ConsoleCommand("camera 3rd", 0) PC.Behavior_Melee() else: PlayerMesh = self.GetPlayerController().Pawn.Mesh Particle = unrealsdk.FindObject("ParticleSystem", self.ChooseAnimation()) self.GetPlayerController().ConsoleCommand("camera 3rd", 0) unrealsdk.GetEngine().GetCurrentWorldInfo( ).MyEmitterPool.SpawnEmitterMeshAttachment(Particle, PlayerMesh, "head")
def HookHandleClick(caller: UObject, function: UFunction, params: FStruct) -> bool: if params.EventID == 4: gfxmovie = params.TheList.MyOwnerMovie if(gfxmovie.IsOverlayMenuOpen() == False): WPCOwner = unrealsdk.GetEngine().GamePlayers[0].Actor gfxmovie.CheckDownloadableContentListCompleted(WPCOwner.GetMyControllerId(), True) return False else: return True
def OnEnd(self) -> None: ShowChatMessage("Crowd Control:", f"{self.Name} wore off.", ShowTimestamp=False) unrealsdk.RemoveHook("WillowGame.WillowHUDGFxMovie.Start", "CCHideHUD") # Seems the hook sticks around a tick or something, this doesn't work unless I delay it AsyncUtil.RunIn(0.05, unrealsdk.GetEngine().GamePlayers[0].Actor.DisplayHUD)