|
| 1 | +import bl2sdk |
| 2 | +import sys |
| 3 | +import os |
| 4 | +import math |
| 5 | +import json |
| 6 | +from fractions import Fraction |
| 7 | + |
| 8 | + |
| 9 | +class Commander(bl2sdk.BL2MOD): |
| 10 | + |
| 11 | + Name = "Commander" |
| 12 | + Description = "By mopioid.\n\nPerform various changes to the game using keybindings." |
| 13 | + |
| 14 | + SettingsPath = os.path.join(os.path.dirname(sys.executable), "Plugins\\Python\\commander.json") |
| 15 | + """The path to the file in which we store our settings.""" |
| 16 | + |
| 17 | + def __init__(self): |
| 18 | + """Attempts to load the user's settings from their settings file.""" |
| 19 | + try: |
| 20 | + with open(self.SettingsPath) as settingsFile: |
| 21 | + # Decode the JSON data contained in the settings file. |
| 22 | + settings = json.load(settingsFile) |
| 23 | + # Convert the settings file's list of ignored files to a set for |
| 24 | + # our own usage. |
| 25 | + self.Bindings = settings.get('bindings', dict()) |
| 26 | + if type(self.Bindings) is not dict: |
| 27 | + self.Bindings = dict() |
| 28 | + |
| 29 | + self.Positions = settings.get('positions', dict()) |
| 30 | + if type(self.Positions) is not dict: |
| 31 | + self.Positions = dict() |
| 32 | + # If this fails, set up empty values for our settings. |
| 33 | + except: |
| 34 | + self.Bindings = dict() |
| 35 | + self.Positions = dict() |
| 36 | + |
| 37 | + self.GameInputs = { |
| 38 | + "Halve Game Speed": ( 'LeftBracket', self.HalveGameSpeed ), |
| 39 | + "Double Game Speed": ( 'RightBracket', self.DoubleGameSpeed ), |
| 40 | + "Reset Game Speed": ( 'Backslash', self.ResetGameSpeed ), |
| 41 | + "Save Position": ( 'Period', self.SavePosition ), |
| 42 | + "Restore Position": ( 'Comma', self.RestorePosition ), |
| 43 | + "Teleport Forward": ( 'Up', self.MoveForward ), |
| 44 | + "Toggle World Freeze": ( 'Slash', self.TogglePlayersOnly ), |
| 45 | + "Toggle HUD": ( 'Semicolon', self.ToggleHUD ), |
| 46 | + "Toggle Damage Numbers": ( 'Quote', self.ToggleDamageNumbers ), |
| 47 | + "Toggle Third Person": ( 'Equals', self.ToggleThirdPerson ), |
| 48 | + "Quit Without Saving": ( 'End', self.QuitWithoutSaving ) |
| 49 | + } |
| 50 | + |
| 51 | + def SaveSettings(self): |
| 52 | + """Saves the current settings in JSON format to our settings file.""" |
| 53 | + |
| 54 | + # Create a dictionary with our settings. |
| 55 | + settings = { 'bindings': self.Bindings, 'positions': self.Positions } |
| 56 | + # Save the settings dictionary to our settings file in JSON format. |
| 57 | + with open(self.SettingsPath, 'w') as settingsFile: |
| 58 | + json.dump(settings, settingsFile, indent=4) |
| 59 | + |
| 60 | + |
| 61 | + def Enable(self): |
| 62 | + for name, (key, _) in self.GameInputs.items(): |
| 63 | + key = self.Bindings.get(name, key) |
| 64 | + self.RegisterGameInput(name, key) |
| 65 | + |
| 66 | + def Disable(self): |
| 67 | + for name in self.GameInputs: |
| 68 | + self.UnregisterGameInput(name) |
| 69 | + |
| 70 | + def GameInputPressed(self, name): |
| 71 | + self.GameInputs[name][1]() |
| 72 | + |
| 73 | + def GameInputRebound(self, name, key): |
| 74 | + self.Bindings[name] = key |
| 75 | + self.SaveSettings() |
| 76 | + |
| 77 | + |
| 78 | + def GetPlayerController(self): |
| 79 | + """Return the current WillowPlayerController object for the local player.""" |
| 80 | + return bl2sdk.GetEngine().GamePlayers[0].Actor |
| 81 | + |
| 82 | + |
| 83 | + DefaultGameInfo = bl2sdk.FindObject("WillowCoopGameInfo", "WillowGame.Default__WillowCoopGameInfo") |
| 84 | + """A reference to the WillowCoopGameInfo template object.""" |
| 85 | + # We use this for managing game speed, as transient WorldInfo objects pull |
| 86 | + # their TimeDilation from it. |
| 87 | + |
| 88 | + |
| 89 | + def Feedback(self, feedback): |
| 90 | + """Presents a "training" message to the user with the given string.""" |
| 91 | + |
| 92 | + # Get the current player controller and the graphics object for its HUD. |
| 93 | + playerController = self.GetPlayerController() |
| 94 | + HUDMovie = playerController.GetHUDMovie() |
| 95 | + |
| 96 | + # If there is no graphics object, we cannot display feedback. |
| 97 | + if HUDMovie is None: |
| 98 | + return |
| 99 | + |
| 100 | + # We will be displaying the message for two *real time* seconds. |
| 101 | + duration = 2.0 * self.DefaultGameInfo.GameSpeed |
| 102 | + # Clear any previous message that may be displayed. |
| 103 | + HUDMovie.ClearTrainingText() |
| 104 | + # Present the training message as per the function's signature: |
| 105 | + # AddTrainingText(string MessageString, string TitleString, float Duration, Color DrawColor, string HUDInitializationFrame, bool PausesGame, float PauseContinueDelay, PlayerReplicationInfo Related_PRI1, optional bool bIsntActuallyATrainingMessage, optional WillowPlayerController.EBackButtonScreen StatusMenuTab, optional bool bMandatory) |
| 106 | + HUDMovie.AddTrainingText(feedback, "Commander", duration, (), "", False, 0, playerController.PlayerReplicationInfo, True) |
| 107 | + |
| 108 | + |
| 109 | + def ConsoleCommand(self, command): |
| 110 | + """Performs the given string as a console command.""" |
| 111 | + playerController = self.GetPlayerController() |
| 112 | + try: |
| 113 | + playerController.ConsoleCommand(command, 0) |
| 114 | + except: |
| 115 | + pass |
| 116 | + |
| 117 | + |
| 118 | + def ToggleThirdPerson(self): |
| 119 | + # Assume our local player controller is the first in the engine's list. |
| 120 | + playerController = self.GetPlayerController() |
| 121 | + # Check the state of the current player controller's camera. If it is |
| 122 | + # in third person, we will be switching to first, and vice versa. |
| 123 | + camera = "3rd" if playerController.UsingFirstPersonCamera() else "1st" |
| 124 | + # Perform the "camera" console command using the player controller, with |
| 125 | + # the argument as determined above. |
| 126 | + self.ConsoleCommand("camera " + camera) |
| 127 | + |
| 128 | + |
| 129 | + def HalveGameSpeed(self): |
| 130 | + speed = self.DefaultGameInfo.GameSpeed |
| 131 | + if speed > 0.0625: |
| 132 | + speed /= 2 |
| 133 | + worldInfo = bl2sdk.GetEngine().GetCurrentWorldInfo() |
| 134 | + worldInfo.TimeDilation = speed |
| 135 | + self.DefaultGameInfo.GameSpeed = speed |
| 136 | + self.Feedback("Game Speed: " + str(Fraction(speed))) |
| 137 | + |
| 138 | + def DoubleGameSpeed(self): |
| 139 | + speed = self.DefaultGameInfo.GameSpeed |
| 140 | + if speed < 32: |
| 141 | + speed *= 2 |
| 142 | + worldInfo = bl2sdk.GetEngine().GetCurrentWorldInfo() |
| 143 | + worldInfo.TimeDilation = speed |
| 144 | + self.DefaultGameInfo.GameSpeed = speed |
| 145 | + self.Feedback("Game Speed: " + str(Fraction(speed))) |
| 146 | + |
| 147 | + def ResetGameSpeed(self): |
| 148 | + worldInfo = bl2sdk.GetEngine().GetCurrentWorldInfo() |
| 149 | + worldInfo.TimeDilation = 1.0 |
| 150 | + self.DefaultGameInfo.GameSpeed = 1.0 |
| 151 | + self.Feedback("Game Speed: 1") |
| 152 | + |
| 153 | + |
| 154 | + def ToggleHUD(self): |
| 155 | + self.ConsoleCommand("ToggleHUD") |
| 156 | + |
| 157 | + |
| 158 | + DamageNumberEmitterObject = bl2sdk.FindObject("ParticleSystem", "FX_CHAR_Damage_Matrix.Particles.Part_Dynamic_Number") |
| 159 | + DamageNumberEmitters = list(DamageNumberEmitterObject.Emitters) |
| 160 | + NoDamageNumberEmitters = [None, None, DamageNumberEmitters[2], DamageNumberEmitters[3], DamageNumberEmitters[4], DamageNumberEmitters[5], DamageNumberEmitters[6], DamageNumberEmitters[7], DamageNumberEmitters[8], DamageNumberEmitters[9], DamageNumberEmitters[10], DamageNumberEmitters[11], DamageNumberEmitters[12], DamageNumberEmitters[13], DamageNumberEmitters[14], DamageNumberEmitters[15], DamageNumberEmitters[16]] |
| 161 | + |
| 162 | + def ToggleDamageNumbers(self): |
| 163 | + if self.DamageNumberEmitterObject.Emitters[0] is None: |
| 164 | + self.DamageNumberEmitterObject.Emitters = self.DamageNumberEmitters |
| 165 | + self.Feedback("Damage Numbers: On") |
| 166 | + else: |
| 167 | + self.DamageNumberEmitterObject.Emitters = self.NoDamageNumberEmitters |
| 168 | + self.Feedback("Damage Numbers: Off") |
| 169 | + |
| 170 | + |
| 171 | + def GetMapName(self): |
| 172 | + return bl2sdk.GetEngine().GetCurrentWorldInfo().GetMapName(True) |
| 173 | + |
| 174 | + def GetRotationAndLocation(self): |
| 175 | + # Assume our local player controller is the first in the engine's list. |
| 176 | + playerController = self.GetPlayerController() |
| 177 | + # Our rotation struct is stored in the player controller, while our |
| 178 | + # location struct is stored in its associated pawn object. |
| 179 | + return playerController.Rotation, playerController.Pawn.Location |
| 180 | + |
| 181 | + |
| 182 | + def SavePosition(self): |
| 183 | + rotation, location = self.GetRotationAndLocation() |
| 184 | + position = { 'X': location.X, 'Y': location.Y, 'Z': location.Z, 'Pitch': rotation.Pitch, 'Yaw': rotation.Yaw } |
| 185 | + self.Positions[self.GetMapName()] = position |
| 186 | + self.SaveSettings() |
| 187 | + self.Feedback("Saved Position") |
| 188 | + |
| 189 | + def RestorePosition(self): |
| 190 | + mapName = self.GetMapName() |
| 191 | + if mapName in self.Positions: |
| 192 | + position = self.Positions[mapName] |
| 193 | + |
| 194 | + rotation, location = self.GetRotationAndLocation() |
| 195 | + |
| 196 | + location.X = position['X'] |
| 197 | + location.Y = position['Y'] |
| 198 | + location.Z = position['Z'] |
| 199 | + rotation.Pitch = position['Pitch'] |
| 200 | + rotation.Yaw = position['Yaw'] |
| 201 | + |
| 202 | + self.Feedback("Restored Position") |
| 203 | + else: |
| 204 | + self.Feedback("No Position Saved") |
| 205 | + |
| 206 | + RadiansConversion = 65535.0 / math.pi / 2.0 |
| 207 | + |
| 208 | + def MoveForward(self): |
| 209 | + rotation, location = self.GetRotationAndLocation() |
| 210 | + |
| 211 | + pitch = rotation.Pitch / Commander.RadiansConversion |
| 212 | + yaw = rotation.Yaw / Commander.RadiansConversion |
| 213 | + |
| 214 | + location.Z += math.sin(pitch) * 250 |
| 215 | + location.X += math.cos(yaw) * math.cos(pitch) * 250 |
| 216 | + location.Y += math.sin(yaw) * math.cos(pitch) * 250 |
| 217 | + |
| 218 | + |
| 219 | + def TogglePlayersOnly(self): |
| 220 | + # Get the current WorldInfo object from the engine. |
| 221 | + worldInfo = bl2sdk.GetEngine().GetCurrentWorldInfo() |
| 222 | + # Get the WorldInfo's current players only state. |
| 223 | + playersOnly = worldInfo.bPlayersOnly |
| 224 | + |
| 225 | + # Display the state we will be switching to to the user. |
| 226 | + self.Feedback("Players Only: " + ("Off" if playersOnly else "On")) |
| 227 | + # Apply the change. |
| 228 | + worldInfo.bPlayersOnly = not playersOnly |
| 229 | + |
| 230 | + |
| 231 | + def QuitWithoutSaving(self): |
| 232 | + self.ConsoleCommand("disconnect") |
| 233 | + |
| 234 | + |
| 235 | +bl2sdk.Mods.append(Commander()) |
0 commit comments