├── .gitattributes ├── .gitignore ├── LICENSE ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs └── Resources.resx ├── Resources └── TornadoScript.ini ├── ScriptCore ├── Game │ ├── IScriptComponent.cs │ ├── IScriptEntity.cs │ ├── IScriptEventHandler.cs │ ├── IScriptUpdatable.cs │ ├── IScriptVar.cs │ ├── ScriptComponent.cs │ ├── ScriptEntity.cs │ ├── ScriptEntityEventArgs.cs │ ├── ScriptEventArgs.cs │ ├── ScriptExtension.cs │ ├── ScriptExtensionEventArgs.cs │ ├── ScriptExtensionEventPool.cs │ ├── ScriptExtensionPool.cs │ ├── ScriptPed.cs │ ├── ScriptPlane.cs │ ├── ScriptProp.cs │ ├── ScriptThread.cs │ ├── ScriptVar.cs │ └── ScriptVarCollection.cs ├── IO │ ├── EncryptedFileStream.cs │ ├── IXMLSimpleMetadata.cs │ ├── XMLAttributesCollection.cs │ ├── XMLSimpleMetadata.cs │ └── XMLSimpleParser.cs └── Logger.cs ├── ScriptMain ├── Commands │ ├── CommandManager.cs │ └── Commands.cs ├── Config │ ├── IniFile.cs │ └── IniHelper.cs ├── Frontend │ ├── FrontendInput.cs │ ├── FrontendManager.cs │ └── FrontendOutput.cs ├── Memory │ ├── MemoryAccess.cs │ ├── NativeTypes.cs │ └── Pattern.cs ├── Script │ ├── TDebris.cs │ ├── TEntity.cs │ ├── TFactory.cs │ ├── TParticle.cs │ ├── TScript.cs │ └── TVortex.cs ├── Utility │ ├── Audio │ │ ├── LoopStream.cs │ │ ├── SoundManager.cs │ │ └── WavePlayer.cs │ ├── GameSound.cs │ ├── Helpers.cs │ ├── LoopedParticle.cs │ ├── MathEx.cs │ ├── Probability.cs │ ├── ShapeTestEx.cs │ ├── StrongRandom.cs │ └── Win32Native.cs └── WinHelper.cs ├── TornadoScript.csproj └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Cameron Berry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TornadoScript")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TornadoScript")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("61ed2e12-e7e2-42ed-8ccb-8d1dfccc5fd9")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | <<<<<<< HEAD 36 | [assembly: AssemblyVersion("1.3.1.0")] 37 | [assembly: AssemblyFileVersion("1.3.1.0")] 38 | ======= 39 | [assembly: AssemblyVersion("1.2.2.0")] 40 | [assembly: AssemblyFileVersion("1.2.2.0")] 41 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 42 | -------------------------------------------------------------------------------- /Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace TornadoScript.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TornadoScript.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to [KeyBinds] 65 | ///; Binds for the keyboard --> 66 | /// 67 | ///KeybindsEnabled = true 68 | ///; Enable keybinds. 69 | /// 70 | ///ToggleScript = F6 71 | ///; Tornado activation key. 72 | /// 73 | ///;-- You can use any keys from the Windows Forms key enumeration found here: 74 | ///;https://msdn.microsoft.com/en-us/library/system.windows.forms.keys%28v=vs.110%29.aspx 75 | /// 76 | /// 77 | ///[Vortex] 78 | ///; Settings for the tornado vortex --> 79 | /// 80 | ///MovementEnabled = true 81 | ///; Enable/ disable tornado movement 82 | /// 83 | ///MoveSpeedScale = 1.0 84 | ///; Speed at which the tornado traverses the world. 85 | /// 86 | ///MaxEntitySpe [rest of string was truncated]";. 87 | /// 88 | internal static string TornadoScript { 89 | get { 90 | return ResourceManager.GetString("TornadoScript", resourceCulture); 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\TornadoScript.ini;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 123 | 124 | -------------------------------------------------------------------------------- /Resources/TornadoScript.ini: -------------------------------------------------------------------------------- 1 | [KeyBinds] 2 | ; Binds for the keyboard --> 3 | 4 | KeybindsEnabled = true 5 | ; Enable keybinds. 6 | 7 | ToggleScript = F6 8 | ; Tornado activation key. 9 | 10 | ;-- You can use any keys from the Windows Forms key enumeration found here: 11 | ;https://msdn.microsoft.com/en-us/library/system.windows.forms.keys%28v=vs.110%29.aspx 12 | 13 | 14 | [Vortex] 15 | ; Settings for the tornado vortex --> 16 | 17 | MovementEnabled = true 18 | ; Enable/ disable tornado movement 19 | 20 | MoveSpeedScale = 1.0 21 | ; Speed at which the tornado traverses the world. 22 | 23 | MaxEntitySpeed = 45.0 24 | ; Maximum speed for entities trapped in the vortex. 25 | 26 | MaxEntityDistance = 57.0 27 | ; Maximum distance entites must be from the vortex before we start using forces on them. 28 | 29 | HorizontalForceScale = 2.0 30 | ; Scale of horizontal forces to be applied to vehicles/ peds/ objects trapped in the tornado. 31 | ; If you change this value, also set the vertical force scale accordingly to avoid strange behaviour. 32 | 33 | VerticalForceScale = 1.6 34 | ; Scale of horizontal forces to be applied to vehicles/ peds/ objects trapped in the tornado. 35 | ; If you change this value, also set the horizontal force scale accordingly to avoid strange behaviour. 36 | 37 | VortexRadius = 9.4 38 | ; The initial radius (width) of the tornado for which particles will circulate around. 39 | ; This value gets extrapolated internally to add the characteristic 'cone-shape' of the tornado. 40 | 41 | RotationSpeed = 2.4 42 | ; The rotation speed for particles in the vortex. 43 | 44 | ReverseRotation = false 45 | ; Reverses vortex rotation to be clockwise 46 | 47 | 48 | [VortexAdvanced] 49 | ; Advanced settings for the tornado vortex --> 50 | 51 | MaxParticleLayers = 47 52 | ; Max vertical particle layers in the vortex (effectivley 'vortex height'). 53 | ; Adjusting this (lower) may provide performance improvements on lower- end machines. 54 | 55 | ParticlesPerLayer = 9 56 | ; Max particles that will be instantiated around the circumference of the vortex. 57 | ; The default value is recommended, but this may also be adjusted in the interest of improving performance. 58 | 59 | LayerSeperationAmount = 22.0 60 | ; The distance between individual layers in the vortex. 61 | ; Basically adjusts the amount of 'clipping' that occurs between particles vertically in the vortex. 62 | ; Particles that clip into eachother seem to affect performance, so adjusting this (larger) could potentially help. 63 | 64 | MultiVortexEnabled = false 65 | ; Allows spawning multiple tornadoes. 66 | 67 | <<<<<<< HEAD 68 | CloudTopEnabled = true 69 | ; Simulates a cloud type effect around the tornado 70 | 71 | CloudTopDebrisEnabled = true 72 | ; Adds a debris effect to the top portion of the tornado 73 | 74 | ======= 75 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 76 | ParticleMod = true 77 | ; Patches some stuff at runtime to change the particles to a more realistic color. Disable this if you experience 78 | ; compatibility issues with this mod and newer game updates. 79 | 80 | <<<<<<< HEAD 81 | SurfaceDetectionEnabled = true 82 | ; Enable dynamic detection of materials/ surfaces, blending the color of the tornado with the environment 83 | 84 | UseInternalPool = true 85 | ; Grabs entities from the games internal pool and slows down the "full" processing of entities to once every 6 sec v.s. the usual 2 sec 86 | ; Enabled by default to aid performance 87 | 88 | [Other] 89 | ; Other customizations --> 90 | 91 | Notifications = true 92 | ; Notify on spawning a tornado 93 | 94 | SpawnInStorm = true 95 | ======= 96 | [Other] 97 | ; Other customizations --> 98 | 99 | EnableConsole = true 100 | 101 | SpawnInStorm = false 102 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 103 | ; Spawn in a tornado when a thunderstorm is occuring 104 | 105 | ; Enable an in-game console to change settings in real- time 106 | EnableConsole = true 107 | 108 | ; Enable SFX for more immersion. NOTE: There are issues in .NET framework that cause significant lag when registering handlers for "window focus" events. 109 | ; The mod must use these APIs to ensure sound does not play when the GTA V window loses focus, meaning you may experience lag when loading or reloading scripts with this option enabled. 110 | EnableSound = true 111 | 112 | ; Plays a siren to warn of an imminent tornado when SpawnInStorm = true 113 | EnableSiren = false 114 | 115 | 116 | -------------------------------------------------------------------------------- /ScriptCore/Game/IScriptComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TornadoScript.ScriptCore.Game 4 | { 5 | /// 6 | /// Represents a component of a script. 7 | /// 8 | public interface IScriptComponent : IScriptUpdatable 9 | { 10 | /// 11 | /// Gets the name of this component. 12 | /// 13 | /// The name. 14 | string Name { get; } 15 | 16 | /// 17 | /// Unique Id. 18 | /// 19 | Guid Guid { get; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ScriptCore/Game/IScriptEntity.cs: -------------------------------------------------------------------------------- 1 | namespace TornadoScript.ScriptCore.Game 2 | { 3 | public interface IScriptEntity 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ScriptCore/Game/IScriptEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TornadoScript.ScriptCore.Game 2 | { 3 | /// 4 | /// Represents a handler of script events. 5 | /// 6 | public interface IScriptEventHandler 7 | { 8 | ScriptExtensionEventPool Events { get; } 9 | 10 | void NotifyEvent(string name); 11 | 12 | void NotifyEvent(string name, ScriptEventArgs args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ScriptCore/Game/IScriptUpdatable.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace TornadoScript.ScriptCore.Game 3 | { 4 | /// 5 | /// Represents an object that can be updated by a script. 6 | /// 7 | public interface IScriptUpdatable 8 | { 9 | /// 10 | /// Method to be fired each frame. 11 | /// 12 | void OnUpdate(int gameTime); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ScriptCore/Game/IScriptVar.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace TornadoScript.ScriptCore.Game 3 | { 4 | public interface IScriptVar 5 | { 6 | bool ReadOnly { get; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TornadoScript.ScriptCore.Game 4 | { 5 | /// 6 | /// Base class for all script components. 7 | /// 8 | public abstract class ScriptComponent : IScriptComponent, IScriptUpdatable 9 | { 10 | /// 11 | /// Name of the component. 12 | /// 13 | public string Name { get; protected set; } 14 | 15 | /// 16 | /// Unique id to identify this instance. 17 | /// 18 | public Guid Guid { get; private set; } 19 | 20 | 21 | public ScriptComponent() : this(string.Empty) 22 | { } 23 | 24 | public ScriptComponent(string name) 25 | { 26 | Name = name ?? GetType().Name; 27 | Guid = Guid.NewGuid(); 28 | } 29 | 30 | public override bool Equals(object obj) 31 | { 32 | var component = obj as ScriptComponent; 33 | 34 | if (component == null) 35 | { 36 | return false; 37 | } 38 | 39 | return Guid.GetHashCode() == component.Guid.GetHashCode(); 40 | } 41 | 42 | public override int GetHashCode() 43 | { 44 | return Guid.GetHashCode(); 45 | } 46 | 47 | public virtual void OnUpdate(int gameTime) 48 | { } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GTA; 3 | 4 | namespace TornadoScript.ScriptCore.Game 5 | { 6 | /// 7 | /// Represents a game entity. 8 | /// 9 | public abstract class ScriptEntity : ScriptExtension, IScriptEntity where T : Entity 10 | { 11 | /// 12 | /// Base game entity reference. 13 | /// 14 | public T Ref { get; } 15 | 16 | /// 17 | /// Total entity ticks. 18 | /// 19 | public int TotalTicks { get; private set; } 20 | 21 | /// 22 | /// Total time entity has been available to the script. 23 | /// 24 | public TimeSpan TotalTime { get; private set; } 25 | 26 | /// 27 | /// Time at which the entity was made avilable to the script. 28 | /// 29 | public int CreatedTime { get; } 30 | 31 | /// 32 | /// Initialize the class. 33 | /// 34 | /// 35 | protected ScriptEntity(T baseRef) 36 | { 37 | Ref = baseRef; 38 | CreatedTime = GTA.Game.GameTime; 39 | } 40 | 41 | /// 42 | /// Call this method each tick to update entity related information. 43 | /// 44 | public override void OnUpdate(int gameTime) 45 | { 46 | TotalTicks++; 47 | 48 | TotalTicks = TotalTicks % int.MaxValue; 49 | 50 | TotalTime = TimeSpan.FromMilliseconds(gameTime - CreatedTime); 51 | } 52 | 53 | public void Remove() 54 | { 55 | Ref.CurrentBlip?.Remove(); 56 | Ref.Delete(); 57 | } 58 | 59 | public override void Dispose() 60 | { 61 | Remove(); 62 | base.Dispose(); 63 | } 64 | 65 | public static implicit operator Entity(ScriptEntity e) 66 | { 67 | return e.Ref; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptEntityEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TornadoScript.ScriptCore.Game 4 | { 5 | public delegate void ScriptEntityEventHandler(IScriptEntity sender, ScriptEntityEventArgs args); 6 | 7 | /// 8 | /// Event args for a script entity event. 9 | /// 10 | public sealed class ScriptEntityEventArgs : EventArgs 11 | { 12 | public ScriptEntityEventArgs(int gameTime) 13 | { 14 | GameTime = gameTime; 15 | } 16 | 17 | /// 18 | /// The entity that fired the event 19 | /// 20 | public int GameTime { get; private set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TornadoScript.ScriptCore.Game 4 | { 5 | public class ScriptEventArgs : EventArgs 6 | { 7 | public object Data { get; private set; } 8 | 9 | public ScriptEventArgs() : this(null) 10 | { } 11 | 12 | public ScriptEventArgs(object data) 13 | { 14 | Data = data; 15 | } 16 | } 17 | 18 | public class ScriptEventArgs : EventArgs 19 | { 20 | public T Data { get; private set; } 21 | 22 | public ScriptEventArgs(T data) 23 | { 24 | Data = data; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptExtension.cs: -------------------------------------------------------------------------------- 1 | namespace TornadoScript.ScriptCore.Game 2 | { 3 | public abstract class ScriptExtension : ScriptComponent, IScriptEventHandler 4 | { 5 | public ScriptExtensionEventPool Events { get; } = 6 | new ScriptExtensionEventPool(); 7 | 8 | public ScriptExtension() 9 | { 10 | ScriptThread.Add(this); 11 | } 12 | 13 | /// 14 | /// Raise an event with the specified name. 15 | /// 16 | /// The name of the event. 17 | public void NotifyEvent(string name) 18 | { 19 | NotifyEvent(name, new ScriptEventArgs()); 20 | } 21 | 22 | /// 23 | /// Raise an event with the specified name and arguments. 24 | /// 25 | /// The name of the event. 26 | /// Event specific arguments. 27 | public void NotifyEvent(string name, ScriptEventArgs args) 28 | { 29 | Events[name]?.Invoke(this, args); 30 | } 31 | 32 | /// 33 | /// Register a script event for the underlying extension. 34 | /// 35 | /// 36 | public void RegisterEvent(string name) 37 | { 38 | Events.Add(name, default(ScriptExtensionEventHandler)); 39 | } 40 | 41 | internal virtual void OnThreadAttached() 42 | { } 43 | 44 | internal virtual void OnThreadDetached() 45 | { } 46 | 47 | public virtual void Dispose() 48 | { 49 | ScriptThread.Remove(this); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptExtensionEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TornadoScript.ScriptCore.Game 4 | { 5 | public delegate void ScriptComponentEventHandler(ScriptThread sender, ScriptComponentEventArgs args); 6 | 7 | /// 8 | /// Event args for a script extension event. 9 | /// 10 | public sealed class ScriptComponentEventArgs : EventArgs 11 | { 12 | public ScriptComponentEventArgs(ScriptComponent extension) 13 | { 14 | Extension = extension; 15 | } 16 | 17 | public ScriptComponent Extension { get; private set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptExtensionEventPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace TornadoScript.ScriptCore.Game 5 | { 6 | public delegate void ScriptExtensionEventHandler(ScriptExtension sender, ScriptEventArgs e); 7 | 8 | public class ScriptExtensionEventPool : Dictionary 9 | { 10 | public ScriptExtensionEventPool() : base(StringComparer.OrdinalIgnoreCase) 11 | { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptExtensionPool.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | <<<<<<< HEAD 3 | 4 | ======= 5 | using System; 6 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 7 | namespace TornadoScript.ScriptCore.Game 8 | { 9 | public class ScriptExtensionPool : List 10 | { 11 | /// 12 | /// Get an extension from the pool by its type. 13 | /// 14 | /// 15 | /// 16 | public T Get() where T : ScriptComponent 17 | { 18 | for (int i = 0; i < Count; i++) 19 | { 20 | if (this[i] is T item) 21 | { 22 | return item; 23 | } 24 | } 25 | 26 | return null; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptPed.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | 3 | namespace TornadoScript.ScriptCore.Game 4 | { 5 | /// 6 | /// Represents a ped. 7 | /// 8 | public class ScriptPed : ScriptEntity 9 | { 10 | /// 11 | /// Fired when the ped has entered a vehicle. 12 | /// 13 | public event ScriptEntityEventHandler EnterVehicle; 14 | 15 | /// 16 | /// Fired when the ped has exited a vehicle. 17 | /// 18 | public event ScriptEntityEventHandler ExitVehicle; 19 | 20 | 21 | private int vehicleTicks = 0; 22 | 23 | /// 24 | /// If the ped is a local player/ human. 25 | /// 26 | public bool IsHuman 27 | { 28 | get { return Ref == GTA.Game.Player.Character; } 29 | } 30 | 31 | public ScriptPed(Ped baseRef) : base(baseRef) 32 | { } 33 | 34 | protected virtual void OnEnterVehicle(ScriptEntityEventArgs e) 35 | { 36 | EnterVehicle?.Invoke(this, e); 37 | } 38 | 39 | protected virtual void OnExitVehicle(ScriptEntityEventArgs e) 40 | { 41 | ExitVehicle?.Invoke(this, e); 42 | } 43 | 44 | public override void OnUpdate(int gameTime) 45 | { 46 | if (Ref.IsInVehicle()) 47 | { 48 | if (vehicleTicks == 0) 49 | OnEnterVehicle(new ScriptEntityEventArgs(gameTime)); 50 | vehicleTicks++; 51 | } 52 | 53 | else 54 | { 55 | if (vehicleTicks > 0) 56 | { 57 | OnExitVehicle(new ScriptEntityEventArgs(gameTime)); 58 | vehicleTicks = 0; 59 | } 60 | } 61 | 62 | base.OnUpdate(gameTime); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptPlane.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | using GTA.Native; 3 | 4 | namespace TornadoScript.ScriptCore.Game 5 | { 6 | /// 7 | /// Represents a plane. 8 | /// 9 | public class ScriptPlane : ScriptEntity 10 | { 11 | /// 12 | /// Fired when the vehicle is no longer drivable. 13 | /// 14 | public event ScriptEntityEventHandler Undrivable; 15 | 16 | /// 17 | /// State of the vehicle landing gear. 18 | /// 19 | public LandingGearState LandingGearState 20 | { 21 | get { return (LandingGearState)Function.Call(Hash._GET_VEHICLE_LANDING_GEAR, Ref.Handle); } 22 | set { Function.Call(Hash._SET_VEHICLE_LANDING_GEAR, Ref.Handle, (int)value); } 23 | } 24 | 25 | private int undrivableTicks = 0; 26 | 27 | public ScriptPlane(Vehicle baseRef) : base(baseRef) 28 | { } 29 | 30 | protected virtual void OnUndrivable(ScriptEntityEventArgs e) 31 | { 32 | Undrivable?.Invoke(this, e); 33 | } 34 | 35 | public override void OnUpdate(int gameTime) 36 | { 37 | if (!Ref.IsDriveable) 38 | { 39 | if (undrivableTicks == 0) 40 | OnUndrivable(new ScriptEntityEventArgs(gameTime)); 41 | 42 | undrivableTicks++; 43 | } 44 | 45 | else 46 | { 47 | undrivableTicks = 0; 48 | } 49 | 50 | base.OnUpdate(gameTime); 51 | } 52 | } 53 | 54 | public enum LandingGearState 55 | { 56 | Deployed, 57 | Closing, 58 | Opening, 59 | Retracted 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptProp.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | 3 | namespace TornadoScript.ScriptCore.Game 4 | { 5 | /// 6 | /// Represents a prop. 7 | /// 8 | public class ScriptProp : ScriptEntity 9 | { 10 | public ScriptProp(Prop baseRef) : base(baseRef) 11 | { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptThread.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | using GTA; 3 | 4 | namespace TornadoScript.ScriptCore.Game 5 | { 6 | /// 7 | /// Base class for a script thread. 8 | /// 9 | public abstract class ScriptThread : Script 10 | { 11 | /// 12 | /// Script extension pool. 13 | /// 14 | private static ScriptExtensionPool _extensions; 15 | 16 | /// 17 | /// Script vars. 18 | /// 19 | public static ScriptVarCollection Vars { get; private set; } 20 | 21 | protected ScriptThread() 22 | { 23 | _extensions = new ScriptExtensionPool(); 24 | Vars = new ScriptVarCollection(); 25 | Tick += (s, e) => OnUpdate(GTA.Game.GameTime); 26 | KeyDown += KeyPressedInternal; 27 | } 28 | 29 | /// 30 | /// Get a script extension from the underlying pool by its type. 31 | /// 32 | /// 33 | /// 34 | public static T Get() where T : ScriptExtension 35 | { 36 | return _extensions.Get(); 37 | } 38 | 39 | /// 40 | /// Adds a script extension to this thread. 41 | /// 42 | /// 43 | public static void Add(ScriptExtension extension) 44 | { 45 | if (_extensions.Contains(extension)) return; 46 | 47 | extension.RegisterEvent("keydown"); 48 | 49 | _extensions.Add(extension); 50 | 51 | extension.OnThreadAttached(); 52 | } 53 | 54 | /// 55 | /// Adds a script extension to this thread. 56 | /// 57 | public static void Create() where T : ScriptExtension, new() 58 | { 59 | var extension = Get(); 60 | 61 | if (extension != null) return; 62 | 63 | extension = new T(); 64 | 65 | Add(extension); 66 | } 67 | 68 | /// 69 | /// Get an extension, or create it if it doesn't exist. 70 | /// 71 | /// 72 | /// 73 | public static T GetOrCreate() where T : ScriptExtension, new() 74 | { 75 | var extension = Get(); 76 | 77 | if (extension != null) 78 | return extension; 79 | 80 | extension = new T(); 81 | 82 | Add(extension); 83 | 84 | return extension; 85 | } 86 | 87 | internal static void Remove(ScriptExtension extension) 88 | { 89 | extension.OnThreadDetached(); 90 | 91 | _extensions.Remove(extension); 92 | } 93 | 94 | /// 95 | /// Register a new script variable and add it to the collection. 96 | /// 97 | /// 98 | /// The name of the var 99 | /// The default (reset) value 100 | /// 101 | public static void RegisterVar(string name, T defaultValue, bool readOnly = false) 102 | { 103 | Vars.Add(name, new ScriptVar(defaultValue, readOnly)); 104 | } 105 | 106 | /// 107 | /// Get a script variable attached to this thread. 108 | /// 109 | /// 110 | /// 111 | /// 112 | public static ScriptVar GetVar(string name) 113 | { 114 | return Vars.Get(name); 115 | } 116 | 117 | /// 118 | /// Set the value of a script variable attached to this thread. 119 | /// 120 | /// 121 | /// 122 | /// 123 | /// 124 | public static bool SetVar(string name, T value) 125 | { 126 | var foundVar = GetVar(name); 127 | 128 | if (foundVar.ReadOnly) 129 | return false; 130 | 131 | foundVar.Value = value; 132 | 133 | return true; 134 | } 135 | 136 | internal virtual void KeyPressedInternal(object sender, KeyEventArgs e) 137 | { 138 | foreach (ScriptExtension s in _extensions) 139 | { 140 | s.NotifyEvent("keydown", new ScriptEventArgs(e)); 141 | } 142 | } 143 | 144 | /// 145 | /// Updates the thread. 146 | /// 147 | public virtual void OnUpdate(int gameTime) 148 | { 149 | for (int i = 0; i < _extensions.Count; i++) 150 | { 151 | _extensions[i].OnUpdate(gameTime); 152 | } 153 | } 154 | 155 | /// 156 | /// Removes the thread and all extensions. 157 | /// 158 | /// 159 | protected override void Dispose(bool A_0) 160 | { 161 | for (int i = _extensions.Count - 1; i > -1; i--) 162 | { 163 | _extensions[i].Dispose(); 164 | } 165 | 166 | base.Dispose(A_0); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptVar.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace TornadoScript.ScriptCore.Game 3 | { 4 | /// 5 | /// Represents a script variable object. 6 | /// 7 | /// 8 | public class ScriptVar : IScriptVar 9 | { 10 | /// 11 | /// The current value of the script var. 12 | /// 13 | public T Value { get; set; } 14 | 15 | /// 16 | /// The default value of the script var. 17 | /// 18 | public T Default { get; } 19 | 20 | /// 21 | /// Whether the script var is read-only. 22 | /// 23 | public bool ReadOnly { get; } 24 | 25 | /// 26 | /// Initialize the class. 27 | /// 28 | /// The initial value of the variable. 29 | /// Whether the variable is readonly. 30 | public ScriptVar(T value, bool isReadonly) 31 | { 32 | Value = value; 33 | Default = value; 34 | ReadOnly = isReadonly; 35 | } 36 | 37 | /// 38 | /// Initialize the class. 39 | /// 40 | /// 41 | public ScriptVar(T value) : this(value, false) 42 | { } 43 | 44 | /// 45 | /// Implicit conversion from to nested value 46 | /// 47 | /// 48 | public static implicit operator T(ScriptVar var) 49 | { 50 | return var.Value; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ScriptCore/Game/ScriptVarCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace TornadoScript.ScriptCore.Game 5 | { 6 | public class ScriptVarCollection : Dictionary 7 | { 8 | public ScriptVarCollection() : base(StringComparer.OrdinalIgnoreCase) 9 | { } 10 | 11 | /// 12 | /// Get an extension from the pool by its type. 13 | /// 14 | /// 15 | /// 16 | public ScriptVar Get(string name) 17 | { 18 | <<<<<<< HEAD 19 | if (TryGetValue(name, out var result)) 20 | ======= 21 | if (TryGetValue(name, out IScriptVar result)) 22 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 23 | { 24 | return result as ScriptVar; 25 | } 26 | 27 | return null; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ScriptCore/IO/EncryptedFileStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TornadoScript.ScriptCore.IO 8 | { 9 | public class EncryptedFileStream 10 | { 11 | private readonly FileStream stream; 12 | private readonly string DataHash = "dkfcn7tz"; 13 | private readonly string Salt = "Delta0xa44"; 14 | private readonly string VIKey = "@pQsQDF6vpfJA84A"; 15 | 16 | public EncryptedFileStream(string filePath) 17 | { 18 | stream = new FileStream(filePath, FileMode.OpenOrCreate); 19 | } 20 | 21 | public async void WriteValueAsync(string key, int value) 22 | { 23 | string str = Encrypt(string.Format("{0}-{1}", key, value)); 24 | 25 | try 26 | { 27 | int seekPos = 0; 28 | 29 | byte[] buffer = new byte[24]; 30 | 31 | while (seekPos < stream.Length) 32 | { 33 | stream.Seek(seekPos, SeekOrigin.Begin); 34 | 35 | await stream.ReadAsync(buffer, 0, 24); 36 | 37 | var line = Decipher(Encoding.ASCII.GetString(buffer)); 38 | 39 | var keyVal = line.Substring(0, line.IndexOf('-')); 40 | 41 | if (keyVal == key) 42 | { 43 | using (StreamWriter writer = new StreamWriter(stream)) 44 | { 45 | writer.BaseStream.Seek(seekPos, SeekOrigin.Begin); 46 | writer.BaseStream.Write(Encoding.ASCII.GetBytes(str), 0, 24); 47 | } 48 | 49 | return; 50 | } 51 | 52 | seekPos += 24; 53 | } 54 | 55 | 56 | if (stream.CanWrite) 57 | stream.Write(Encoding.ASCII.GetBytes(str), (int)stream.Length, 24); 58 | } 59 | 60 | catch (IOException) 61 | { 62 | Logger.Log("Failed to write to data file."); 63 | } 64 | } 65 | 66 | public async Task ReadValueAsync(string key) 67 | { 68 | try 69 | { 70 | int seekPos = 0; 71 | byte[] buffer = new byte[24]; 72 | 73 | while (seekPos < stream.Length) 74 | { 75 | stream.Seek(seekPos, SeekOrigin.Begin); 76 | await stream.ReadAsync(buffer, 0, 24); 77 | var line = Decipher(Encoding.ASCII.GetString(buffer)); 78 | var keyVal = line.Substring(0, line.IndexOf('-')); 79 | var value = line.Substring(line.IndexOf('-') + 1); 80 | if (keyVal == key) 81 | return Convert.ToInt32(value); 82 | seekPos += 24; 83 | } 84 | 85 | return 0; 86 | } 87 | 88 | catch (IOException) 89 | { 90 | Logger.Log("Failed to read from stats file."); 91 | return 1; 92 | } 93 | } 94 | 95 | private string Encrypt(string plainText) 96 | { 97 | byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); 98 | 99 | byte[] keyBytes = new Rfc2898DeriveBytes(DataHash, Encoding.ASCII.GetBytes(Salt)).GetBytes(256 / 8); 100 | var symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC, Padding = PaddingMode.Zeros }; 101 | var encryptor = symmetricKey.CreateEncryptor(keyBytes, Encoding.ASCII.GetBytes(VIKey)); 102 | 103 | byte[] cipherTextBytes; 104 | 105 | using (var memoryStream = new MemoryStream()) 106 | { 107 | using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) 108 | { 109 | cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); 110 | cryptoStream.FlushFinalBlock(); 111 | cipherTextBytes = memoryStream.ToArray(); 112 | cryptoStream.Close(); 113 | } 114 | 115 | memoryStream.Close(); 116 | } 117 | return Convert.ToBase64String(cipherTextBytes); 118 | } 119 | 120 | private string Decipher(string encryptedText) 121 | { 122 | byte[] cipherTextBytes = Convert.FromBase64String(encryptedText); 123 | byte[] keyBytes = new Rfc2898DeriveBytes(DataHash, Encoding.ASCII.GetBytes(Salt)).GetBytes(256 / 8); 124 | var symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC, Padding = PaddingMode.None }; 125 | 126 | var decryptor = symmetricKey.CreateDecryptor(keyBytes, Encoding.ASCII.GetBytes(VIKey)); 127 | var memoryStream = new MemoryStream(cipherTextBytes); 128 | var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read); 129 | byte[] plainTextBytes = new byte[cipherTextBytes.Length]; 130 | 131 | int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); 132 | 133 | memoryStream.Close(); 134 | cryptoStream.Close(); 135 | 136 | return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount).TrimEnd("\0".ToCharArray()); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /ScriptCore/IO/IXMLSimpleMetadata.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace TornadoScript.ScriptCore.IO 3 | { 4 | /// 5 | /// Base interface for simple XML data. 6 | /// 7 | public interface IXMLSimpleMetadata 8 | { 9 | XMLSimpleMetadata ParseAttributes(XMLAttributesCollection col); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ScriptCore/IO/XMLAttributesCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace TornadoScript.ScriptCore.IO 5 | { 6 | /// 7 | /// Class to hold XML attributes. 8 | /// 9 | [Serializable] 10 | public class XMLAttributesCollection : Dictionary 11 | { 12 | public XMLAttributesCollection() : base(StringComparer.OrdinalIgnoreCase) 13 | { } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ScriptCore/IO/XMLSimpleMetadata.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace TornadoScript.ScriptCore.IO 3 | { 4 | /// 5 | /// Class to represent simple XML metadata. 6 | /// 7 | public abstract class XMLSimpleMetadata : IXMLSimpleMetadata 8 | { 9 | /// 10 | /// Method for parsing class members from a 11 | /// 12 | /// 13 | /// The parsed metadata. 14 | public virtual XMLSimpleMetadata ParseAttributes(XMLAttributesCollection c) 15 | { 16 | return this; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ScriptCore/IO/XMLSimpleParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Xml; 3 | 4 | namespace TornadoScript.ScriptCore.IO 5 | { 6 | class XMLSimpleParser 7 | { 8 | /// 9 | /// Lightweight function for grabbing nested xml data in a file. 10 | /// 11 | /// The path to the file. 12 | /// The name of the xml element to be parsed. 13 | /// 14 | public static IEnumerable GetNestedAttributes(string fileName, string dataType) 15 | { 16 | using (XmlReader reader = XmlReader.Create(fileName)) 17 | { 18 | while (reader.Read()) 19 | { 20 | if (reader.NodeType == XmlNodeType.Element && reader.Name == dataType && reader.HasAttributes) 21 | { 22 | var childAttributes = new XMLAttributesCollection(); 23 | 24 | while (reader.MoveToNextAttribute()) 25 | { 26 | childAttributes.Add(reader.Name, reader.Value); 27 | } 28 | 29 | yield return childAttributes; 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ScriptCore/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | <<<<<<< HEAD 5 | using System.IO; 6 | ======= 7 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 8 | 9 | namespace TornadoScript.ScriptCore 10 | { 11 | /// 12 | /// Static logger class that allows direct logging of anything to a text file 13 | /// 14 | public static class Logger 15 | { 16 | public static void Log(string format, params object[] args) 17 | { 18 | File.AppendAllText("TornadoScript.log", "[" + DateTime.Now + "] " + string.Format(format, args) + Environment.NewLine); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ScriptMain/Commands/CommandManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using TornadoScript.ScriptCore.Game; 6 | using TornadoScript.ScriptMain.Frontend; 7 | 8 | namespace TornadoScript.ScriptMain.Commands 9 | { 10 | public class CommandManager : ScriptExtension 11 | { 12 | private readonly Dictionary> _commands = 13 | new Dictionary>(); 14 | 15 | private FrontendManager _frontendMgr; 16 | 17 | public CommandManager() 18 | { 19 | <<<<<<< HEAD 20 | AddCommand("spawn", Commands.SpawnVortex); 21 | AddCommand("summon", Commands.SummonVortex); 22 | AddCommand("set", Commands.SetVar); 23 | AddCommand("reset", Commands.ResetVar); 24 | AddCommand("ls", Commands.ListVars); 25 | AddCommand("list", Commands.ListVars); 26 | AddCommand("help", Commands.ShowHelp); 27 | AddCommand("?", Commands.ShowHelp); 28 | ======= 29 | AddCommand("set", SetVar); 30 | AddCommand("reset", ResetVar); 31 | AddCommand("ls", ListVars); 32 | AddCommand("list", ListVars); 33 | AddCommand("help", ShowHelp); 34 | AddCommand("?", ShowHelp); 35 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 36 | } 37 | 38 | internal override void OnThreadAttached() 39 | { 40 | _frontendMgr = ScriptThread.GetOrCreate(); 41 | _frontendMgr.Events["textadded"] += OnInputEvent; 42 | base.OnThreadAttached(); 43 | } 44 | 45 | public void OnInputEvent(object sender, ScriptEventArgs e) 46 | { 47 | var cmd = (string)e.Data; 48 | 49 | if (cmd.Length <= 0) return; 50 | 51 | var stringArray = cmd.Split(' '); 52 | 53 | var command = stringArray[0].ToLower(); 54 | 55 | if (!_commands.TryGetValue(command, out var func)) return; 56 | 57 | var args = stringArray.Skip(1).ToArray(); 58 | 59 | var text = func?.Invoke(args); 60 | 61 | if (!string.IsNullOrEmpty(text)) 62 | _frontendMgr.WriteLine(text); 63 | } 64 | 65 | <<<<<<< HEAD 66 | ======= 67 | private static string SetVar(params string[] args) 68 | { 69 | if (args.Length < 2) return "SetVar: Invalid format."; 70 | 71 | var varName = args[0]; 72 | 73 | 74 | if (int.TryParse(args[1], out var i)) 75 | { 76 | var foundVar = ScriptThread.GetVar(varName); 77 | 78 | if (foundVar != null) 79 | { 80 | return !ScriptThread.SetVar(varName, i) ? 81 | "Failed to set the (integer) variable. Is it readonly?" : null; 82 | } 83 | } 84 | 85 | if (float.TryParse(args[1], out var f)) 86 | { 87 | var foundVar = ScriptThread.GetVar(varName); 88 | 89 | if (foundVar != null) 90 | { 91 | return !ScriptThread.SetVar(varName, f) ? 92 | "Failed to set the (float) variable. Is it readonly?" : null; 93 | } 94 | } 95 | 96 | if (bool.TryParse(args[1], out var b)) 97 | { 98 | var foundVar = ScriptThread.GetVar(varName); 99 | 100 | if (foundVar != null) 101 | { 102 | return !ScriptThread.SetVar(varName, b) ? 103 | "Failed to set the (bool) variable. Is it readonly?" : null; 104 | } 105 | } 106 | 107 | return "Variable '" + args[0] + "' not found."; 108 | } 109 | 110 | private static string ResetVar(params string[] args) 111 | { 112 | if (args.Length < 1) return "ResetVar: Invalid format."; 113 | 114 | var varName = args[0]; 115 | 116 | 117 | if (int.TryParse(args[1], out var i)) 118 | { 119 | var foundVar = ScriptThread.GetVar(varName); 120 | 121 | if (foundVar != null) 122 | { 123 | foundVar.Value = foundVar.Default; 124 | 125 | return null; 126 | } 127 | } 128 | 129 | 130 | if (float.TryParse(args[1], out var f)) 131 | { 132 | var foundVar = ScriptThread.GetVar(varName); 133 | 134 | if (foundVar != null) 135 | { 136 | foundVar.Value = foundVar.Default; 137 | 138 | return null; 139 | } 140 | } 141 | 142 | if (bool.TryParse(args[1], out var b)) 143 | { 144 | var foundVar = ScriptThread.GetVar(varName); 145 | 146 | if (foundVar == null) return "Variable '" + args[0] + "' not found."; 147 | 148 | foundVar.Value = foundVar.Default; 149 | 150 | return null; 151 | } 152 | 153 | return "Variable '" + args[0] + "' not found."; 154 | } 155 | 156 | private static string ListVars(params string[] args) 157 | { 158 | var foundCount = 0; 159 | 160 | var frontend = ScriptThread.Get(); 161 | 162 | foreach (var var in ScriptThread.Vars) 163 | { 164 | frontend.WriteLine(var.Key + (var.Value.ReadOnly ? " (read-only) " : "")); 165 | 166 | foundCount++; 167 | } 168 | 169 | return "Found " + foundCount + " vars."; 170 | } 171 | 172 | private static string ShowHelp(params string[] args) 173 | { 174 | var frontend = ScriptThread.Get(); 175 | 176 | frontend.WriteLine("~r~set~w~: Set a variable\t\t~r~reset~w~: Reset a variable\t\t~r~ls~w~: List all vars"); 177 | 178 | return "Commands:"; 179 | } 180 | 181 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 182 | public void AddCommand(string name, Func command) 183 | { 184 | _commands.Add(name, command); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /ScriptMain/Commands/Commands.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | using GTA.Native; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TornadoScript.ScriptCore; 9 | using TornadoScript.ScriptCore.Game; 10 | using TornadoScript.ScriptMain.Frontend; 11 | using TornadoScript.ScriptMain.Script; 12 | 13 | namespace TornadoScript.ScriptMain.Commands 14 | { 15 | public static class Commands 16 | { 17 | public static string SetVar(params string[] args) 18 | { 19 | if (args.Length < 2) return "SetVar: Invalid format."; 20 | 21 | var varName = args[0]; 22 | 23 | 24 | if (int.TryParse(args[1], out var i)) 25 | { 26 | var foundVar = ScriptThread.GetVar(varName); 27 | 28 | if (foundVar != null) 29 | { 30 | return !ScriptThread.SetVar(varName, i) ? 31 | "Failed to set the (integer) variable. Is it readonly?" : null; 32 | } 33 | } 34 | 35 | if (float.TryParse(args[1], out var f)) 36 | { 37 | var foundVar = ScriptThread.GetVar(varName); 38 | 39 | if (foundVar != null) 40 | { 41 | return !ScriptThread.SetVar(varName, f) ? 42 | "Failed to set the (float) variable. Is it readonly?" : null; 43 | } 44 | } 45 | 46 | if (bool.TryParse(args[1], out var b)) 47 | { 48 | var foundVar = ScriptThread.GetVar(varName); 49 | 50 | if (foundVar != null) 51 | { 52 | return !ScriptThread.SetVar(varName, b) ? 53 | "Failed to set the (bool) variable. Is it readonly?" : null; 54 | } 55 | } 56 | 57 | return "Variable '" + args[0] + "' not found."; 58 | } 59 | 60 | public static string ResetVar(params string[] args) 61 | { 62 | if (args.Length < 1) return "ResetVar: Invalid format."; 63 | 64 | var varName = args[0]; 65 | 66 | 67 | if (int.TryParse(args[1], out var i)) 68 | { 69 | var foundVar = ScriptThread.GetVar(varName); 70 | 71 | if (foundVar != null) 72 | { 73 | foundVar.Value = foundVar.Default; 74 | 75 | return null; 76 | } 77 | } 78 | 79 | 80 | if (float.TryParse(args[1], out var f)) 81 | { 82 | var foundVar = ScriptThread.GetVar(varName); 83 | 84 | if (foundVar != null) 85 | { 86 | foundVar.Value = foundVar.Default; 87 | 88 | return null; 89 | } 90 | } 91 | 92 | if (bool.TryParse(args[1], out var b)) 93 | { 94 | var foundVar = ScriptThread.GetVar(varName); 95 | 96 | if (foundVar == null) return "Variable '" + args[0] + "' not found."; 97 | 98 | foundVar.Value = foundVar.Default; 99 | 100 | return null; 101 | } 102 | 103 | return "Variable '" + args[0] + "' not found."; 104 | } 105 | 106 | public static string ListVars(params string[] args) 107 | { 108 | var foundCount = 0; 109 | 110 | var frontend = ScriptThread.Get(); 111 | 112 | foreach (var var in ScriptThread.Vars) 113 | { 114 | frontend.WriteLine(var.Key + (var.Value.ReadOnly ? " (read-only) " : "")); 115 | 116 | foundCount++; 117 | } 118 | 119 | return "Found " + foundCount + " vars."; 120 | } 121 | 122 | public static string SummonVortex(params string[] args) 123 | { 124 | var vtxmgr = ScriptThread.Get(); 125 | 126 | if (vtxmgr.ActiveVortexCount > 0) 127 | vtxmgr.ActiveVortexList[0].Position = Game.Player.Character.Position; 128 | 129 | return "Vortex summoned"; 130 | } 131 | 132 | public static string SpawnVortex(params string[] args) 133 | { 134 | var vtxmgr = ScriptThread.Get(); 135 | 136 | Function.Call(Hash.REMOVE_PARTICLE_FX_IN_RANGE, 0f, 0f, 0f, 1000000.0f); 137 | 138 | Function.Call(Hash.SET_WIND, 70.0f); 139 | 140 | var position = Game.Player.Character.Position + Game.Player.Character.ForwardVector * 180f; 141 | 142 | vtxmgr.CreateVortex(position); 143 | 144 | return "Vortex spawned (" + position + ")"; 145 | } 146 | 147 | public static string ShowHelp(params string[] args) 148 | { 149 | var frontend = ScriptThread.Get(); 150 | 151 | frontend.WriteLine("~r~set~w~: Set a variable\t\t~r~reset~w~: Reset a variable\t\t~r~ls~w~: List all vars~r~spawn~w~: Spawn a tornado vortex\t\t~r~summon~w~: Summon the vortex to your current position\t\t"); 152 | 153 | return "Commands:"; 154 | } 155 | 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /ScriptMain/Config/IniFile.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Text; 3 | 4 | namespace TornadoScript.ScriptMain.Config 5 | { 6 | /// 7 | /// Create a New INI file to store or load data 8 | /// 9 | public class IniFile 10 | { 11 | public string Path; 12 | 13 | [DllImport("kernel32")] 14 | private static extern long WritePrivateProfileString(string section, 15 | string key, string val, string filePath); 16 | [DllImport("kernel32")] 17 | private static extern int GetPrivateProfileString(string section, 18 | string key, string def, StringBuilder retVal, 19 | int size, string filePath); 20 | 21 | /// 22 | /// INIFile Constructor. 23 | /// 24 | /// 25 | public IniFile(string iniPath) 26 | { 27 | Path = iniPath; 28 | } 29 | /// 30 | /// Write Data to the INI File 31 | /// 32 | /// 33 | /// Section name 34 | /// 35 | /// Key Name 36 | /// 37 | /// Value Name 38 | public void IniWriteValue(string section, string key, string value) 39 | { 40 | WritePrivateProfileString(section, key, value, this.Path); 41 | } 42 | 43 | /// 44 | /// Read Data Value From the Ini File 45 | /// 46 | /// 47 | /// 48 | /// 49 | /// 50 | public string IniReadValue(string section, string key) 51 | { 52 | var temp = new StringBuilder(255); 53 | var i = GetPrivateProfileString(section, key, "", temp, 54 | 255, this.Path); 55 | return temp.ToString(); 56 | 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ScriptMain/Config/IniHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Reflection; 6 | using TornadoScript.ScriptMain.Utility; 7 | 8 | namespace TornadoScript.ScriptMain.Config 9 | { 10 | public static class IniHelper 11 | { 12 | public static readonly string IniPath; 13 | public static readonly IniFile IniFile; 14 | 15 | static IniHelper() 16 | { 17 | IniPath = string.Format("scripts\\{0}.ini", 18 | Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location)); 19 | if (!File.Exists(IniPath)) Create(); 20 | IniFile = new IniFile(IniPath); 21 | } 22 | 23 | /// 24 | /// Write a string value to the config file at the specified section and key 25 | /// 26 | /// The section in the config file 27 | /// The key of the config string 28 | /// The value of the config string 29 | public static void WriteValue(string section, string key, string value) 30 | { 31 | IniFile.IniWriteValue(section, key, value); 32 | } 33 | 34 | /// 35 | /// Gets a config setting 36 | /// 37 | /// The section of the config file 38 | /// The config setting 39 | /// 40 | /// 41 | public static T GetValue(string section, string key, T defaultValue = default(T)) 42 | { 43 | Type type = typeof(T); 44 | if (!type.IsValueType && type != typeof(string)) 45 | throw new ArgumentException("Not a known type."); 46 | 47 | var keyValue = IniFile.IniReadValue(section, key); 48 | var tConverter = TypeDescriptor.GetConverter(type); 49 | 50 | if (keyValue.Length > 0 && tConverter.CanConvertFrom(typeof(string))) 51 | { 52 | return (T)tConverter.ConvertFromString(null, CultureInfo.InvariantCulture, keyValue); 53 | } 54 | 55 | return defaultValue; 56 | } 57 | 58 | public static void Create() 59 | { 60 | try 61 | { 62 | if (File.Exists(IniPath)) File.Delete(IniPath); 63 | var list = Helpers.ReadEmbeddedResource(Properties.Resources.TornadoScript); 64 | Helpers.WriteListToFile(list, IniPath); 65 | } 66 | 67 | catch (AccessViolationException) 68 | { 69 | GTA.UI.Notify("TornadoScript failed to write a new INI file. Access to the path " + IniPath + " was denied"); 70 | } 71 | 72 | catch (Exception e) 73 | { 74 | GTA.UI.Notify("TornadoScript failed to write a new INI file. " + e.Message); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ScriptMain/Frontend/FrontendInput.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using GTA; 3 | using GTA.Native; 4 | 5 | namespace TornadoScript.ScriptMain.Frontend 6 | { 7 | public class FrontendInput 8 | { 9 | private const int CursorPulseSpeed = 300; 10 | 11 | private bool _cursorState; 12 | 13 | private bool _active; 14 | 15 | private string _str = ""; 16 | 17 | private int _lastCursorPulse, _currentTextWidth; 18 | 19 | private readonly UIText _text = new UIText("", new Point(14, 5), 0.3f); 20 | 21 | private readonly UIRectangle _cursorRect = new UIRectangle(new Point(14, 5), new Size(1, 15), Color.Empty); 22 | 23 | private readonly UIContainer _backgroundContainer = new UIContainer(new Point(20, 20), new Size(600, 30), Color.Empty); 24 | 25 | private readonly string WatermarkText = "Tornado Script Console - Press Backspace to exit"; 26 | 27 | /// 28 | /// Initialize the class. 29 | /// 30 | public FrontendInput() 31 | { 32 | _backgroundContainer.Items.Add(_text); 33 | _backgroundContainer.Items.Add(_cursorRect); 34 | } 35 | 36 | /// 37 | /// Add a line of text to the input box. 38 | /// 39 | /// 40 | public void AddLine(string text) 41 | { 42 | Show(); 43 | _str = text; 44 | _currentTextWidth = GetTextWidth(); 45 | } 46 | 47 | /// 48 | /// Add a single character to the input box. 49 | /// 50 | public void AddChar(char c) 51 | { 52 | Show(); 53 | _str += c; 54 | _currentTextWidth = GetTextWidth(); 55 | } 56 | 57 | /// 58 | /// Gets the current text in the input box. 59 | /// 60 | /// 61 | public string GetText() 62 | { 63 | return _str; 64 | } 65 | 66 | /// 67 | /// Remove the last character from the input box. 68 | /// 69 | public void RemoveLastChar() 70 | { 71 | if (_str.Length > 0) 72 | { 73 | _str = _str.Substring(0, _str.Length - 1); 74 | _currentTextWidth = GetTextWidth(); 75 | } 76 | } 77 | 78 | /// 79 | /// Show the input box. 80 | /// 81 | public void Show() 82 | { 83 | _backgroundContainer.Color = Color.FromArgb(140, 52, 144, 2); 84 | 85 | SetCursorColor(Color.White); 86 | SetTextColor(Color.White); 87 | 88 | _active = true; 89 | } 90 | 91 | /// 92 | /// Hide the input box. 93 | /// 94 | public void Hide() 95 | { 96 | _backgroundContainer.Color = Color.Empty; 97 | 98 | SetCursorColor(Color.Empty); 99 | SetTextColor(Color.Empty); 100 | 101 | _active = false; 102 | } 103 | 104 | /// 105 | /// Clear the active text. 106 | /// 107 | public void Clear() 108 | { 109 | _str = string.Empty; 110 | _currentTextWidth = GetTextWidth(); 111 | } 112 | 113 | /// 114 | /// Set the text color. 115 | /// 116 | /// 117 | private void SetTextColor(Color color) 118 | { 119 | _text.Color = color; 120 | } 121 | 122 | /// 123 | /// Set the cursor color. 124 | /// 125 | /// 126 | private void SetCursorColor(Color color) 127 | { 128 | _cursorRect.Color = color; 129 | } 130 | 131 | private int GetTextWidth() 132 | { 133 | Function.Call((Hash)0x54CE8AC98E120CAB, "CELL_EMAIL_BCON"); 134 | 135 | Function.Call(Hash._ADD_TEXT_COMPONENT_STRING, _str); 136 | 137 | Function.Call(Hash.SET_TEXT_FONT, (int)_text.Font); 138 | 139 | Function.Call(Hash.SET_TEXT_SCALE, _text.Scale, _text.Scale); 140 | 141 | return (int) (UI.WIDTH * Function.Call((Hash)0x85F061DA64ED2F67, (int)_text.Font)); 142 | } 143 | 144 | /// 145 | /// Update and draw the this box. 146 | /// 147 | public void Update(int gameTime) 148 | { 149 | if (!_active) return; 150 | 151 | Game.DisableAllControlsThisFrame(0); 152 | 153 | _text.Caption = _str.Length > 0 ? _str : WatermarkText; 154 | 155 | _cursorRect.Position = new Point(14 + _currentTextWidth, 7); 156 | 157 | if (gameTime - _lastCursorPulse > CursorPulseSpeed && _text.Color.A > 0) 158 | { 159 | _cursorState = !_cursorState; 160 | 161 | _cursorRect.Color = _cursorState ? Color.FromArgb(255, _cursorRect.Color) : Color.FromArgb(0, _cursorRect.Color); 162 | 163 | _lastCursorPulse = gameTime; 164 | } 165 | 166 | _backgroundContainer.Draw(); 167 | } 168 | } 169 | } -------------------------------------------------------------------------------- /ScriptMain/Frontend/FrontendManager.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | using System.Windows.Input; 3 | using GTA; 4 | using TornadoScript.ScriptCore.Game; 5 | using TornadoScript.ScriptMain.Utility; 6 | 7 | namespace TornadoScript.ScriptMain.Frontend 8 | { 9 | public class FrontendManager : ScriptExtension 10 | { 11 | private readonly FrontendInput _input = new FrontendInput(); 12 | 13 | private readonly FrontendOutput _output = new FrontendOutput(); 14 | 15 | private bool _showingConsole; 16 | 17 | private bool _capsLock; 18 | 19 | public FrontendManager() 20 | { 21 | RegisterEvent("textadded"); 22 | } 23 | 24 | internal override void OnThreadAttached() 25 | { 26 | Events["keydown"] += OnKeyDown; 27 | base.OnThreadAttached(); 28 | } 29 | 30 | private void OnKeyDown(object sender, ScriptEventArgs e) 31 | { 32 | <<<<<<< HEAD 33 | var keyArgs = e.Data as System.Windows.Forms.KeyEventArgs; 34 | ======= 35 | var keyArgs = e.Data as KeyEventArgs; 36 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 37 | 38 | if (keyArgs == null) return; 39 | 40 | if (!ScriptThread.GetVar("enableconsole")) return; 41 | 42 | if (keyArgs.KeyCode == Keys.CapsLock) 43 | { 44 | _capsLock = !_capsLock; 45 | } 46 | 47 | if (!_showingConsole) 48 | { 49 | if (keyArgs.KeyCode == ScriptThread.GetVar("toggleconsole")) 50 | { 51 | ShowConsole(); 52 | } 53 | } 54 | 55 | else 56 | GetConsoleInput(keyArgs); 57 | } 58 | 59 | public void ShowConsole() 60 | { 61 | if (_showingConsole) return; 62 | _input.Show(); 63 | _output.Show(); 64 | _output.DisableFadeOut(); 65 | _showingConsole = true; 66 | } 67 | 68 | public void HideConsole() 69 | { 70 | if (!_showingConsole) return; 71 | _input.Clear(); 72 | _input.Hide(); 73 | _output.Hide(); 74 | _output.EnableFadeOut(); 75 | _showingConsole = false; 76 | } 77 | 78 | public void WriteLine(string format, params object[] args) 79 | { 80 | if (args == null) 81 | _output.WriteLine(format); 82 | else _output.WriteLine(format, args); 83 | } 84 | 85 | <<<<<<< HEAD 86 | private void GetConsoleInput(System.Windows.Forms.KeyEventArgs e) 87 | ======= 88 | private void GetConsoleInput(KeyEventArgs e) 89 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 90 | { 91 | var key = KeyInterop.KeyFromVirtualKey((int)e.KeyCode); 92 | 93 | var keyChar = Win32Native.GetCharFromKey(key, (e.Modifiers & Keys.Shift) != 0); 94 | 95 | var capsLock = System.Windows.Forms.Control.IsKeyLocked(Keys.CapsLock); 96 | 97 | if (char.IsLetter(keyChar)) 98 | { 99 | if (capsLock || e.Shift) 100 | keyChar = char.ToUpper(keyChar); 101 | else keyChar = char.ToLower(keyChar); 102 | 103 | } 104 | 105 | else 106 | { 107 | switch (e.KeyCode) 108 | { 109 | case Keys.Back: 110 | { 111 | var text = _input.GetText(); 112 | 113 | if (text.Length < 1) 114 | { 115 | HideConsole(); 116 | } 117 | 118 | _input.RemoveLastChar(); 119 | 120 | return; 121 | } 122 | 123 | case Keys.Up: 124 | _output.ScrollUp(); 125 | return; 126 | 127 | case Keys.Down: 128 | _output.ScrollDown(); 129 | return; 130 | 131 | case Keys.Space: 132 | _input.AddChar(' '); 133 | return; 134 | 135 | case Keys.Enter: 136 | { 137 | var text = _input.GetText(); 138 | 139 | NotifyEvent("textadded", new ScriptEventArgs(text)); 140 | 141 | _output.WriteLine(text); 142 | 143 | _input.Clear(); 144 | 145 | _output.ScrollToTop(); 146 | 147 | // HideConsole(); 148 | 149 | return; 150 | } 151 | 152 | case Keys.Escape: 153 | HideConsole(); 154 | return; 155 | } 156 | } 157 | 158 | if (keyChar != ' ') 159 | { 160 | _input.AddChar(keyChar); 161 | } 162 | } 163 | 164 | public override void OnUpdate(int gameTime) 165 | { 166 | _input.Update(gameTime); 167 | 168 | _output.Update(gameTime); 169 | 170 | if (_showingConsole) 171 | { 172 | if (Game.IsControlJustPressed(0, (GTA.Control) 241) || 173 | Game.IsControlJustPressed(0, (GTA.Control) 188)) 174 | { 175 | _output.ScrollUp(); 176 | } 177 | 178 | else if (Game.IsControlJustPressed(0, (GTA.Control) 242) || 179 | Game.IsControlJustPressed(0, (GTA.Control) 187)) 180 | { 181 | _output.ScrollDown(); 182 | } 183 | } 184 | 185 | base.OnUpdate(gameTime); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /ScriptMain/Frontend/FrontendOutput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using GTA; 4 | 5 | namespace TornadoScript.ScriptMain.Frontend 6 | { 7 | public class FrontendOutput 8 | { 9 | private const int TextActiveTime = 10000; 10 | 11 | private readonly UIContainer _backsplash; 12 | 13 | private readonly string[] _messageQueue = new string[20]; 14 | 15 | private readonly UIText[] _text = new UIText[10]; 16 | 17 | private bool _startFromTop = true; 18 | 19 | private bool _stayOnScreen; 20 | 21 | private int _scrollIndex; 22 | 23 | private int _shownTime; 24 | 25 | private int _linesCount; 26 | 27 | /// 28 | /// Write a new line to the message queue with the given format. 29 | /// 30 | /// 31 | /// 32 | public void WriteLine(string format, params object[] args) 33 | { 34 | WriteLine(string.Format(format, args)); 35 | } 36 | 37 | /// 38 | /// Write a new line to the message queue. 39 | /// 40 | /// 41 | public void WriteLine(string text) 42 | { 43 | Show(); 44 | 45 | for (var i = _messageQueue.Length - 1; i > 0; i--) 46 | { 47 | _messageQueue[i] = _messageQueue[i - 1]; 48 | } 49 | 50 | _messageQueue[0] = $"~4~[{DateTime.Now.ToShortTimeString()}]: {text}"; 51 | 52 | _linesCount = Math.Min(_linesCount + 1, _messageQueue.Length); 53 | } 54 | 55 | private void SetTextColor(Color color) 56 | { 57 | foreach (var text in _text) 58 | { 59 | text.Color = color; 60 | } 61 | } 62 | 63 | public FrontendOutput() 64 | { 65 | _backsplash = new UIContainer(new Point(20, 60), new Size(600, 200), Color.Empty); 66 | CreateText(); 67 | } 68 | 69 | public void Show() 70 | { 71 | _backsplash.Color = 72 | Color.FromArgb(140, 52, 144, 2); 73 | 74 | SetTextColor(Color.White); 75 | 76 | _shownTime = Game.GameTime; 77 | } 78 | 79 | public void Hide() 80 | { 81 | _backsplash.Color = Color.Empty; 82 | 83 | SetTextColor(Color.Empty); 84 | 85 | _shownTime = 0; 86 | } 87 | 88 | public void ScrollUp() 89 | { 90 | if (_scrollIndex > 0) 91 | _scrollIndex--; 92 | } 93 | 94 | public void ScrollToTop() 95 | { 96 | _scrollIndex = 0; 97 | } 98 | 99 | public void ScrollDown() 100 | { 101 | if (_scrollIndex < _linesCount - 10) 102 | _scrollIndex++; 103 | } 104 | 105 | public void DisableFadeOut() 106 | { 107 | _stayOnScreen = true; 108 | } 109 | 110 | public void EnableFadeOut() 111 | { 112 | _stayOnScreen = false; 113 | } 114 | 115 | private void CreateText() 116 | { 117 | for (var i = 0; i < _text.Length; i++) 118 | { 119 | _text[i] = new UIText(string.Empty, new Point(14, 11 + 18 * i), 0.3f, Color.Empty); 120 | } 121 | 122 | _backsplash.Items.AddRange(_text); 123 | } 124 | 125 | public void Update(int gameTime) 126 | { 127 | if (gameTime > _shownTime + TextActiveTime && !_stayOnScreen) 128 | { 129 | if (_backsplash.Color.A > 0) 130 | _backsplash.Color = Color.FromArgb(Math.Max(0, _backsplash.Color.A - 2), _backsplash.Color); 131 | 132 | foreach (var text in _text) 133 | { 134 | if (text.Color.A > 0) 135 | { 136 | text.Color = Color.FromArgb(Math.Max(0, text.Color.A - 4), text.Color); 137 | } 138 | } 139 | } 140 | 141 | else 142 | { 143 | for (var i = _text.Length - 1; i > -1; i--) 144 | { 145 | _text[i].Caption = _messageQueue[ 146 | _startFromTop 147 | ? i + _scrollIndex 148 | : _messageQueue.Length - 1 - i + _scrollIndex] ?? string.Empty; 149 | } 150 | } 151 | 152 | _backsplash.Draw(); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /ScriptMain/Memory/MemoryAccess.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | using GTA.Math; 3 | using GTA.Native; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Drawing; 8 | using System.Runtime.CompilerServices; 9 | using System.Runtime.InteropServices; 10 | using System.Text; 11 | 12 | namespace TornadoScript.ScriptMain.Memory 13 | { 14 | public static unsafe class MemoryAccess 15 | { 16 | private static bool bInitialized = false; 17 | 18 | [UnmanagedFunctionPointer(CallingConvention.ThisCall)] 19 | public delegate IntPtr FwGetAssetIndexFn(IntPtr assetStore, out int index, StringBuilder name); 20 | 21 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 22 | public delegate int AddEntityToPoolFn(ulong address); 23 | 24 | public delegate IntPtr GetPooledPtfxAddressFn(int handle); 25 | 26 | private static IntPtr PtfxAssetStorePtr; 27 | 28 | private static IntPtr ScriptEntityPoolAddr, VehiclePoolAddr, PedPoolAddr, ObjectPoolAddr; 29 | 30 | private static FwGetAssetIndexFn FwGetAssetIndex; 31 | 32 | public static AddEntityToPoolFn AddEntityToPool; 33 | 34 | private static readonly uint PtfxColourHash = (uint)Game.GenerateHash("ptxu_Colour"); 35 | 36 | private static Dictionary ptfxRulePtrList = new Dictionary(); 37 | 38 | /* 39 | struct fwPool 40 | { 41 | void *m_pData; 42 | unsigned __int8 *m_bitMap; 43 | int m_count; 44 | int m_itemSize; 45 | int unkItemIndex; 46 | int nextFreeSlotIndex; 47 | unsigned int m_flags; 48 | char pad1[4]; 49 | }; 50 | */ 51 | 52 | public static void Initialize() 53 | { 54 | #region SetupPTFXAssetStore 55 | 56 | var pattern = new Pattern("\x0F\xBF\x04\x9F\xB9", "xxxxx"); 57 | 58 | var result = pattern.Get(0x19); 59 | 60 | if (result != IntPtr.Zero) 61 | { 62 | var rip = result.ToInt64() + 7; 63 | var value = Marshal.ReadInt32(IntPtr.Add(result, 3)); 64 | PtfxAssetStorePtr = new IntPtr(rip + value); 65 | } 66 | 67 | #endregion 68 | 69 | #region SetupfwGetAssetIndex 70 | 71 | pattern = new Pattern("\x41\x8B\xDE\x4C\x63\x00", "xxxxx?"); 72 | 73 | result = pattern.Get(); 74 | 75 | if (result != IntPtr.Zero) 76 | { 77 | var rip = result.ToInt64(); 78 | var value = Marshal.ReadInt32(result - 4); 79 | FwGetAssetIndex = Marshal.GetDelegateForFunctionPointer(new IntPtr(rip + value)); 80 | } 81 | 82 | // Entity Pool -> 83 | 84 | pattern = new Pattern("\x4C\x8B\x0D\x00\x00\x00\x00\x44\x8B\xC1\x49\x8B\x41\x08", "xxx????xxxxxxx"); 85 | 86 | result = pattern.Get(7); 87 | 88 | if (result != IntPtr.Zero) 89 | { 90 | var rip = result.ToInt64(); 91 | var value = Marshal.ReadInt32(result - 4); 92 | ScriptEntityPoolAddr = Marshal.ReadIntPtr(new IntPtr(rip + value)); 93 | 94 | // UI.ShowSubtitle(ScriptEntityPoolAddr.ToString("X")); 95 | } 96 | 97 | // Vehicle Pool -> 98 | 99 | pattern = new Pattern("\x48\x8B\x05\x00\x00\x00\x00\xF3\x0F\x59\xF6\x48\x8B\x08", "xxx????xxxxxxx"); 100 | 101 | result = pattern.Get(7); 102 | 103 | if (result != IntPtr.Zero) 104 | { 105 | var rip = result.ToInt64(); 106 | var value = Marshal.ReadInt32(result - 4); 107 | VehiclePoolAddr = Marshal.ReadIntPtr(new IntPtr(rip + value)); 108 | 109 | // UI.ShowSubtitle(VehiclePoolAddr.ToString("X")); 110 | } 111 | 112 | // Ped Pool -> 113 | 114 | pattern = new Pattern("\x48\x8B\x05\x00\x00\x00\x00\x41\x0F\xBF\xC8\x0F\xBF\x40\x10", "xxx????xxxxxxxx"); 115 | 116 | result = pattern.Get(7); 117 | 118 | if (result != IntPtr.Zero) 119 | { 120 | var rip = result.ToInt64(); 121 | var value = Marshal.ReadInt32(result - 4); 122 | PedPoolAddr = Marshal.ReadIntPtr(new IntPtr(rip + value)); 123 | 124 | // UI.ShowSubtitle(PedPoolAddr.ToString("X")); 125 | } 126 | 127 | // Object Pool -> 128 | 129 | pattern = new Pattern("\x48\x8B\x05\x00\x00\x00\x00\x8B\x78\x10\x85\xFF", "xxx????xxxxx"); 130 | 131 | result = pattern.Get(7); 132 | 133 | if (result != IntPtr.Zero) 134 | { 135 | var rip = result.ToInt64(); 136 | var value = Marshal.ReadInt32(result - 4); 137 | ObjectPoolAddr = Marshal.ReadIntPtr(new IntPtr(rip + value)); 138 | 139 | // UI.ShowSubtitle(ObjectPoolAddr.ToString("X")); 140 | } 141 | 142 | pattern = new Pattern("\x48\xF7\xF9\x49\x8B\x48\x08\x48\x63\xD0\xC1\xE0\x08\x0F\xB6\x1C\x11\x03\xD8", "xxxxxxxxxxxxxxxxxxx"); 143 | 144 | result = pattern.Get(); 145 | 146 | if (result != IntPtr.Zero) 147 | { 148 | AddEntityToPool = Marshal.GetDelegateForFunctionPointer(IntPtr.Subtract(result, 0x68)); 149 | 150 | //UI.ShowSubtitle(result.ToString("X")); 151 | } 152 | 153 | // WinHelper.CopyTlsValues(WinHelper.GetProcessMainThreadId(), Win32Native.GetCurrentThreadId(), 0xC8, 0xC0, 0xB8); 154 | 155 | #endregion 156 | 157 | bInitialized = true; 158 | } 159 | 160 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 161 | public static Vector3 GetEntityPosition(IntPtr entity) 162 | { 163 | return (Vector3)Marshal.PtrToStructure(entity + 0x90, typeof(Vector3)); 164 | } 165 | 166 | public static unsafe IList CollectEntitiesFull() 167 | { 168 | if (bInitialized == false) 169 | return null; 170 | 171 | FwPool * entityPool = (FwPool*)ScriptEntityPoolAddr; 172 | 173 | VehiclePool * vehiclePool = *(VehiclePool**)VehiclePoolAddr; 174 | 175 | GenericPool * pedPool = (GenericPool*)PedPoolAddr; 176 | 177 | GenericPool * objectPool = (GenericPool*)ObjectPoolAddr; 178 | 179 | List list = new List(); 180 | 181 | for (uint i = 0; i < vehiclePool->size; i++) 182 | { 183 | if (entityPool->IsFull()) 184 | { 185 | break; 186 | } 187 | 188 | if (vehiclePool->IsValid(i)) 189 | { 190 | list.Add(new Vehicle(AddEntityToPool(vehiclePool->poolAddress[i]))); 191 | } 192 | } 193 | 194 | for (uint i = 0; i < pedPool->size; i++) 195 | { 196 | if (entityPool->IsFull()) 197 | { 198 | break; 199 | } 200 | 201 | if (pedPool->IsValid(i)) 202 | { 203 | var address = pedPool->GetAddress(i); 204 | 205 | if (address != 0) 206 | { 207 | list.Add(new Ped(AddEntityToPool(address))); 208 | } 209 | } 210 | } 211 | 212 | for (uint i = 0; i < objectPool->size; i++) 213 | { 214 | if (entityPool->IsFull()) 215 | { 216 | break; 217 | } 218 | 219 | if (objectPool->IsValid(i)) 220 | { 221 | var address = objectPool->GetAddress(i); 222 | 223 | if (address != 0) 224 | { 225 | list.Add(new Ped(AddEntityToPool(address))); 226 | } 227 | } 228 | } 229 | 230 | return list; 231 | } 232 | 233 | public static IEnumerable GetAllEntitiesInternal() 234 | { 235 | if (bInitialized == false) 236 | yield return null; 237 | 238 | var poolItems = Marshal.ReadIntPtr(ScriptEntityPoolAddr); 239 | 240 | var bitMap = Marshal.ReadIntPtr(ScriptEntityPoolAddr + 0x8); 241 | 242 | var count = Marshal.ReadInt32(ScriptEntityPoolAddr + 0x10); 243 | 244 | for (int i = 0; i < count; i++) 245 | { 246 | var bitset = Marshal.ReadByte(bitMap + i); 247 | if ((bitset & 0x80) != 0) 248 | continue; 249 | var handle = (i << 8) + bitset; 250 | 251 | var type = Function.Call(Hash.GET_ENTITY_TYPE, handle); 252 | 253 | switch (type) 254 | { 255 | case 1: 256 | yield return new Ped(handle); 257 | break; 258 | case 2: 259 | yield return new Vehicle(handle); 260 | break; 261 | case 3: 262 | yield return new Prop(handle); 263 | break; 264 | 265 | } 266 | } 267 | } 268 | 269 | private static PgDictionary* GetPtfxRuleDictionary(string ptxAssetName) 270 | { 271 | if (bInitialized == false) 272 | return null; 273 | 274 | var assetStore = Marshal.PtrToStructure(PtfxAssetStorePtr); 275 | 276 | FwGetAssetIndex(PtfxAssetStorePtr, out var index, new StringBuilder(ptxAssetName)); 277 | 278 | var ptxFxListPtr = Marshal.ReadIntPtr(assetStore.Items + assetStore.ItemSize * index); 279 | 280 | return (PgDictionary*)Marshal.ReadIntPtr(ptxFxListPtr + 0x48); 281 | } 282 | 283 | public static bool FindPtxEffectRule(PgDictionary* ptxRulesDict, string fxName, out IntPtr result) 284 | { 285 | if (bInitialized == false) 286 | { 287 | result = IntPtr.Zero; 288 | return false; 289 | } 290 | 291 | for (var i = 0; i < ptxRulesDict->ItemsCount; i++) 292 | { 293 | var itAddress = Marshal.ReadIntPtr(ptxRulesDict->Items + i * 8); 294 | 295 | if (itAddress == IntPtr.Zero) continue; 296 | 297 | var szName = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(itAddress + 0x20)); 298 | 299 | if (szName != fxName) continue; 300 | 301 | result = itAddress; 302 | 303 | return true; 304 | } 305 | 306 | result = IntPtr.Zero; 307 | 308 | return false; 309 | } 310 | 311 | /// 312 | /// Get emitter by its name for the given asset rule 313 | /// 314 | /// Pointer to the PtfxAssetRule instance 315 | /// Name of the child emitter object 316 | /// 317 | private static PtxEventEmitter* GetPtfxEventEmitterByName(IntPtr ptxAssetRulePtr, string emitterName) 318 | { 319 | if (bInitialized == false) 320 | return null; 321 | 322 | PtxEventEmitter* foundEmitter = null; 323 | 324 | var ptxRule = Marshal.PtrToStructure(ptxAssetRulePtr); 325 | 326 | for (var i = 0; i < ptxRule.EmittersCount; i++) 327 | { 328 | var emitter = ptxRule.Emitters[i]; 329 | 330 | var szName = Marshal.PtrToStringAnsi(emitter->SzEmitterName); 331 | 332 | if (szName == emitterName) 333 | { 334 | foundEmitter = emitter; 335 | 336 | break; 337 | } 338 | } 339 | 340 | return foundEmitter; 341 | } 342 | 343 | /// 344 | /// Lightweight function for when we know the emitters index 345 | /// 346 | /// 347 | /// 348 | /// 349 | private static PtxEventEmitter* GetPtfxEventEmitterByIndex(IntPtr ptxAssetRulePtr, int emitterIndex) 350 | { 351 | return (*(PtxEventEmitter***)IntPtr.Add(ptxAssetRulePtr, 0x38))[emitterIndex]; 352 | } 353 | 354 | public static void SetPtfxLOD(string baseAsset, string particleName) 355 | { 356 | string key = baseAsset + ':' + particleName; 357 | 358 | if (!ptfxRulePtrList.TryGetValue(key, out var result) && 359 | !FindPtxEffectRule(GetPtfxRuleDictionary(baseAsset), particleName, out result)) 360 | { 361 | return; 362 | } 363 | 364 | ptfxRulePtrList[key] = result; 365 | } 366 | 367 | public static void SetPtfxColor(string baseAsset, string particleName, int emitterIndex, Color newColor) 368 | { 369 | if (bInitialized == false) 370 | return; 371 | 372 | string key = baseAsset + ':' + particleName; 373 | 374 | if (!ptfxRulePtrList.TryGetValue(key, out var result) && 375 | !FindPtxEffectRule(GetPtfxRuleDictionary(baseAsset), particleName, out result)) { 376 | return; 377 | } 378 | 379 | ptfxRulePtrList[key] = result; 380 | 381 | PtxEventEmitter* emitter = GetPtfxEventEmitterByIndex(result, emitterIndex); 382 | 383 | Debug.Assert(emitter != null); 384 | 385 | SetEmitterColour(emitter, newColor); 386 | } 387 | 388 | private static void SetEmitterColour(PtxEventEmitter* emitter, Color colour) 389 | { 390 | SetEmitterColour(emitter, colour.R, colour.G, colour.B, colour.A); 391 | } 392 | 393 | private static void SetEmitterColour(PtxEventEmitter* emitter, byte red, byte green, byte blue, byte alpha) 394 | { 395 | var r = 1.0f / 255 * red; 396 | var g = 1.0f / 255 * green; 397 | var b = 1.0f / 255 * blue; 398 | var a = 1.0f / 255 * alpha; 399 | 400 | for (var i = 0; i < emitter->ParticleRule->BehavioursCount; i++) 401 | { 402 | Ptxu_Colour* behaviour = emitter->ParticleRule->Behaviours[i]; 403 | 404 | if (behaviour->HashName != PtfxColourHash) continue; 405 | 406 | for (var x = 0; x < behaviour->NumFrames; x++) 407 | { 408 | PtxKeyframeProp* keyframe = behaviour->KeyframeProps[x]; 409 | 410 | if (keyframe->Current.Items == IntPtr.Zero) continue; 411 | 412 | var items = (PtxVarVector*)keyframe->Current.Items; 413 | 414 | for (var y = 0; y < keyframe->Current.Count; y++) 415 | { 416 | if (items == null) continue; 417 | 418 | items[y].Min.R = r; 419 | items[y].Min.G = g; 420 | items[y].Min.B = b; 421 | items[y].Min.A = a; 422 | 423 | items[y].Max.R = r; 424 | items[y].Max.G = g; 425 | items[y].Max.B = b; 426 | items[y].Max.A = a; 427 | } 428 | } 429 | 430 | break; 431 | } 432 | } 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /ScriptMain/Memory/NativeTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace TornadoScript.ScriptMain.Memory 6 | { 7 | [StructLayout(LayoutKind.Explicit)] 8 | public struct PtfxAssetStore 9 | { 10 | [FieldOffset(0x10)] 11 | public int MaxItems; //0x10-0x14 12 | [FieldOffset(0x18)] 13 | [MarshalAs(UnmanagedType.LPStr)] 14 | public string StoreName; 15 | [FieldOffset(0x38)] 16 | public IntPtr Items; //0x38 17 | [FieldOffset(0x40)] 18 | public IntPtr BitMap; //0x40 19 | [FieldOffset(0x48)] 20 | public int ItemCount; //0x48-0x4C 21 | [FieldOffset(0x4C)] 22 | public int ItemSize; 23 | [FieldOffset(0x60)] 24 | [MarshalAs(UnmanagedType.LPStr)] 25 | public string AssetType; //0x60-0x68 26 | [FieldOffset(0x82)] 27 | public short NextAvailableSlot; //0x82 (hashMap + 0x12) 28 | }; 29 | 30 | [StructLayout(LayoutKind.Sequential)] 31 | public struct PtxColour 32 | { 33 | public float R; 34 | public float G; 35 | public float B; 36 | public float A; 37 | }; //sizeof=0x8 38 | 39 | [StructLayout(LayoutKind.Sequential)] 40 | public struct PtxVarVector 41 | { 42 | public PtxColour Min; //0x0-0x8 43 | public PtxColour Max; //0x8-0x10 44 | }; //sizeof=0x10 45 | 46 | [StructLayout(LayoutKind.Sequential)] 47 | public unsafe struct PtxKeyframeVars 48 | { 49 | public PtxVarVector* Vectors; 50 | public short UnkCount; 51 | public short MaxVars; 52 | }; 53 | 54 | [StructLayout(LayoutKind.Sequential)] 55 | public unsafe struct PtxKeyframeData 56 | { 57 | public PtxKeyframeVars* Vars; 58 | public short UnkCount; 59 | public short MaxVars; 60 | }; 61 | 62 | [StructLayout(LayoutKind.Explicit)] 63 | public struct PtxVarVectorKfp 64 | { 65 | [FieldOffset(0x0)] 66 | public IntPtr SzArg1; //0x0-0x28 67 | [FieldOffset(0x4)] 68 | public IntPtr SzArg2; 69 | [FieldOffset(0x8)] 70 | public IntPtr SzArg3; 71 | [FieldOffset(0xC)] 72 | public IntPtr SzArg4; 73 | [FieldOffset(0x10)] 74 | public IntPtr SzArg5; 75 | [FieldOffset(0x40)] 76 | public PtxVarVector data; 77 | }; 78 | 79 | [StructLayout(LayoutKind.Explicit)] 80 | public unsafe struct PtxKeyframeProp 81 | { 82 | [FieldOffset(0x18)] 83 | public PtxKeyframeData* F1; 84 | [FieldOffset(0x20)] 85 | public PtxKeyframeData* F2; 86 | [FieldOffset(0x28)] 87 | public PtxKeyframeData* F3; 88 | [FieldOffset(0x30)] 89 | public PtxKeyframeData* F4; 90 | [FieldOffset(0x70)] 91 | public PgCollection Current; // PtxVarVector* collection 92 | [FieldOffset(0x88)] 93 | public PtxVarVectorKfp Defaults; 94 | }; 95 | 96 | [StructLayout(LayoutKind.Explicit)] 97 | public unsafe struct Ptxu_Colour 98 | { 99 | [FieldOffset(0x8)] 100 | public uint HashName; // inherited from ptxBehaviour 101 | [FieldOffset(0x10)] 102 | public PtxKeyframeProp** KeyframeProps; //0x10-0x18 // inherited from ptxBehaviour 103 | [FieldOffset(0x18)] 104 | public short NumFrames; //0x18-0x1A // inherited from ptxBehaviour 105 | [FieldOffset(0x1A)] 106 | public short MaxFrames; //0x1A-0x1C assumed // inherited from ptxBehaviour 107 | [FieldOffset(0xA0)] 108 | public PgCollection RGBA_Min; //0xA0-0xA8 PtxVarVector* collection 109 | [FieldOffset(0x130)] 110 | public PgCollection RGBA_Max; //0x130-0x138 PtxVarVector* collection 111 | [FieldOffset(0x1C0)] 112 | public PgCollection Emissive_Intensity; //0x130-0x138 PtxVarVector* collection 113 | }; 114 | 115 | [StructLayout(LayoutKind.Explicit)] 116 | public unsafe struct PtxEffectRule 117 | { 118 | [FieldOffset(0x20)] 119 | [MarshalAs(UnmanagedType.LPStr)] 120 | public string EffectName; //0x20-0x28 121 | [FieldOffset(0x38)] 122 | public PtxEventEmitter** Emitters; 123 | [FieldOffset(0x40)] 124 | public short EmittersCount; 125 | [FieldOffset(0x42)] 126 | public short MaxEmitters; 127 | }; 128 | 129 | [StructLayout(LayoutKind.Explicit)] 130 | public unsafe struct PtxParticleRule 131 | { 132 | [FieldOffset(0x20)] 133 | public IntPtr Spawner; //0x20 ptxEffectSpawner 134 | [FieldOffset(0x90)] 135 | public IntPtr Spawner1; //0x90-0x100 ptxEffectSpawner 136 | [FieldOffset(0x128)] 137 | public Ptxu_Colour** Behaviours; //0x128-0x130 138 | [FieldOffset(0x130)] 139 | public short BehavioursCount; //0x130-0x132 140 | [FieldOffset(0x132)] 141 | public short MaxBehaviours; // 0x132-0x134 assumed 142 | }; 143 | 144 | [StructLayout(LayoutKind.Explicit)] 145 | public unsafe struct PtxEventEmitter 146 | { 147 | [FieldOffset(0x8)] 148 | public int Index; //0x8-0xC 149 | [FieldOffset(0x18)] 150 | public IntPtr EvolutionParams; //0x18-0x20 151 | [FieldOffset(0x30)] 152 | public IntPtr SzEmitterName; //0x30-0x38 153 | [FieldOffset(0x38)] 154 | public IntPtr SzParticleName; //0x38-0x40 155 | [FieldOffset(0x40)] 156 | public IntPtr EmitterRule; //0x40-0x48 157 | [FieldOffset(0x48)] 158 | public PtxParticleRule* ParticleRule; //0x48-0x50 159 | [FieldOffset(0x50)] 160 | public float MoveSpeedScale; //0x50-0x54 161 | [FieldOffset(0x54)] 162 | public float MoveSpeedScaleModifier; //0x54-0x58 163 | [FieldOffset(0x58)] 164 | public float ParticleScale; //0x58-0x5C 165 | [FieldOffset(0x5C)] 166 | public float ParticleScaleModifier; //0x5C-0x60 167 | }; 168 | 169 | [StructLayout(LayoutKind.Explicit)] 170 | public struct PgCollection 171 | { 172 | [FieldOffset(0x0)] 173 | public IntPtr Items; //0x30-0x38 174 | [FieldOffset(0x08)] 175 | public short Count;//0x38-0x3A 176 | [FieldOffset(0xC)] 177 | public short Size;//0x38-0x3A 178 | }; 179 | 180 | [StructLayout(LayoutKind.Explicit)] 181 | public struct PgDictionary 182 | { 183 | [FieldOffset(0x30)] 184 | public IntPtr Items; //0x30-0x38 185 | [FieldOffset(0x38)] 186 | public short ItemsCount;//0x38-0x3A 187 | }; 188 | 189 | [StructLayout(LayoutKind.Sequential)] 190 | internal struct FwPool 191 | { 192 | public long Items; //0x0-0x8 193 | public IntPtr BitMap; //0x8-0x10 194 | public int Count; //0x10-0x14 195 | public int ItemSize; //0x14-0x18 196 | public int UnkIndex; //0x18-0x1C 197 | public int NextFreeSlot; //0x1C-0x20 198 | public uint Flags; //0x20-0x24 199 | public int Unk; //0x24-0x28 200 | 201 | public long GetMask(int index) 202 | { 203 | long num = Marshal.ReadByte(BitMap + index) & 0x80; 204 | return ~((num | -num) >> 0x3F); 205 | } 206 | 207 | public IntPtr GetAddress(int index) 208 | { 209 | return new IntPtr(GetMask(index) & (Items + ItemSize * index)); 210 | } 211 | 212 | public bool IsFull() 213 | { 214 | return Count - (Flags & 0x3FFFFFFF) <= 256; 215 | } 216 | } 217 | 218 | [StructLayout(LayoutKind.Explicit)] 219 | internal unsafe struct GenericPool 220 | { 221 | [FieldOffset(0x00)] 222 | public ulong poolStartAddress; 223 | [FieldOffset(0x08)] 224 | public IntPtr byteArray; 225 | [FieldOffset(0x10)] 226 | public uint size; 227 | [FieldOffset(0x14)] 228 | public uint itemSize; 229 | 230 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 231 | public bool IsValid(uint index) 232 | { 233 | return Mask(index) != 0; 234 | } 235 | 236 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 237 | public ulong GetAddress(uint index) 238 | { 239 | return ((Mask(index) & (poolStartAddress + index * itemSize))); 240 | } 241 | 242 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 243 | private ulong Mask(uint index) 244 | { 245 | unsafe 246 | { 247 | byte* byteArrayPtr = (byte*)byteArray.ToPointer(); 248 | long num1 = byteArrayPtr[index] & 0x80; 249 | return (ulong)(~((num1 | -num1) >> 63)); 250 | } 251 | } 252 | } 253 | 254 | 255 | [StructLayout(LayoutKind.Explicit)] 256 | internal unsafe struct VehiclePool 257 | { 258 | [FieldOffset(0x00)] 259 | internal ulong* poolAddress; 260 | [FieldOffset(0x08)] 261 | internal uint size; 262 | [FieldOffset(0x30)] 263 | internal uint* bitArray; 264 | [FieldOffset(0x60)] 265 | internal uint itemCount; 266 | 267 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 268 | internal bool IsValid(uint i) 269 | { 270 | return (((bitArray[i >> 5] >> ((int)i & 0x1F)) & 1) != 0) && poolAddress[i] != 0; 271 | } 272 | 273 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 274 | internal ulong GetAddress(uint i) 275 | { 276 | return poolAddress[i]; 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /ScriptMain/Memory/Pattern.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using TornadoScript.ScriptMain.Utility; 3 | 4 | namespace TornadoScript.ScriptMain.Memory 5 | { 6 | public sealed unsafe class Pattern 7 | { 8 | private readonly string _bytes, _mask; 9 | 10 | public Pattern(string bytes, string mask) 11 | { 12 | _bytes = bytes; 13 | _mask = mask; 14 | } 15 | 16 | public IntPtr Get(int offset = 0) 17 | { 18 | MODULEINFO module; 19 | 20 | Win32Native.GetModuleInformation( 21 | Win32Native.GetCurrentProcess(), 22 | Win32Native.GetModuleHandle(null), out module, (uint)sizeof(MODULEINFO)); 23 | 24 | for (var address = module.LpBaseOfDll.ToInt64(); 25 | address < address + module.SizeOfImage; address++) 26 | { 27 | if (ByteCompare((byte*)address, _bytes.ToCharArray(), _mask.ToCharArray())) 28 | { 29 | return new IntPtr(address + offset); 30 | } 31 | } 32 | 33 | return IntPtr.Zero; 34 | } 35 | 36 | private static bool ByteCompare(byte* pData, char[] bMask, char[] szMask) 37 | { 38 | for (int i = 0; i < bMask.Length; i++) 39 | { 40 | if (szMask[i] != '?' && pData[i] != bMask[i]) 41 | { 42 | break; 43 | } 44 | else if (i + 1 == bMask.Length) 45 | { 46 | return true; 47 | } 48 | } 49 | 50 | return false; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ScriptMain/Script/TDebris.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | using GTA.Math; 3 | using System; 4 | using System.Collections.Generic; 5 | using TornadoScript.ScriptCore.Game; 6 | using TornadoScript.ScriptMain.Utility; 7 | 8 | namespace TornadoScript.ScriptMain.Script 9 | { 10 | /// 11 | /// Ttesting physical tornado debris 12 | /// 13 | class TDebris : ScriptProp 14 | { 15 | private float _radius; 16 | 17 | private int _spawnTime = 0; 18 | 19 | public TornadoVortex Parent { get; set; } 20 | 21 | enum MaterialGroup 22 | { 23 | urban, 24 | desert, 25 | countryside, 26 | dirt 27 | } 28 | 29 | private static readonly List debrisItems = new List 30 | { 31 | "prop_bush_med_02", 32 | "prop_fncwood_16d", 33 | "prop_fncwood_16e", 34 | "prop_railsleepers01" 35 | }; 36 | 37 | /* readonly Dictionary> materialDebrisMap = new Dictionary> 38 | { 39 | { MaterialGroup.countryside, // countryside debris objects 40 | new List { 41 | "prop_bush_med_02", 42 | "prop_bush_med_05", 43 | "prop_fncwood_16d", 44 | "prop_fncwood_16e", 45 | "prop_fnclog_02b", 46 | "prop_railsleepers01", } 47 | }, 48 | 49 | { MaterialGroup.desert, // desert debris objects 50 | new List { 51 | "prop_joshua_tree_01d", 52 | "prop_bush_med_02", 53 | } 54 | }, 55 | 56 | { MaterialGroup.urban, // debris objects found in urban areas (downtown etc.) 57 | new List { 58 | "prop_wallbrick_01", 59 | "prop_fncwood_16d", 60 | "prop_fncwood_16e", 61 | "prop_railsleepers01", 62 | "ng_proc_food_burg02a", 63 | "ng_proc_sodacub_03a", 64 | "prop_fire_hydrant_1", 65 | "prop_fnclink_03c", 66 | "prop_dumpster_02a", 67 | "prop_dumpster_01a", 68 | "ng_proc_block_02a", 69 | "ng_proc_brick_01a", 70 | "prop_bin_01a", 71 | "prop_postbox_01a", 72 | "prop_cablespool_02", 73 | "prop_barrier_work06a", 74 | "prop_roadcone02a", 75 | "prop_sign_road_03g", 76 | "prop_lawnmower_01", 77 | "prop_table_04", 78 | "prop_chair_01a", 79 | "prop_chair_01b", 80 | "prop_table_03_chr", 81 | "prop_rub_binbag_04", 82 | "prop_rub_binbag_05", 83 | "prop_sacktruck_02a" } 84 | } 85 | };*/ 86 | 87 | public TDebris(TornadoVortex vortex, Vector3 position, float radius) 88 | : base(Setup(position)) 89 | { 90 | Parent = vortex; 91 | _radius = radius; 92 | _spawnTime = Game.GameTime; 93 | PostSetup(); 94 | } 95 | 96 | private void PostSetup() 97 | { 98 | var centerPos = Parent.Position + new Vector3(0, 0, 5.0f); 99 | 100 | var angle = Probability.GetFloat(0.0f, (float)Math.PI * 2.0f); 101 | 102 | Ref.Position = centerPos + new Vector3(_radius * (float)Math.Cos(angle), _radius * (float)Math.Sin(angle), 0); 103 | } 104 | 105 | /// 106 | /// Setup the base entity. 107 | /// 108 | /// 109 | /// 110 | private static Prop Setup(Vector3 position) 111 | { 112 | var model = new Model(debrisItems[Probability.GetInteger(0, debrisItems.Count - 1)]); 113 | 114 | if (!model.IsLoaded) model.Request(1000); 115 | 116 | var prop = World.CreateProp(model, position, false, false); 117 | 118 | prop.LodDistance = 1000; 119 | 120 | return prop; 121 | } 122 | 123 | public override void OnUpdate(int gameTime) 124 | { 125 | base.OnUpdate(gameTime); 126 | 127 | if (gameTime - _spawnTime > 6400) 128 | { 129 | Dispose(); 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /ScriptMain/Script/TEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using ScriptCore; 7 | using GTA.Math; 8 | using GTA.Native; 9 | using GTA; 10 | 11 | namespace TornadoScript.Script 12 | { 13 | public class TEntity : ScriptEntity 14 | { 15 | private TVortex parent; 16 | 17 | public TEntity(Entity baseRef, TVortex parent) : base(baseRef) 18 | { 19 | this.parent = parent; 20 | } 21 | 22 | public override void OnUpdate(int gameTime) 23 | { 24 | var parentPos = parent.Position; 25 | 26 | float dist = Vector2.Distance(Ref.Position.Vec2(), parentPos.Vec2()); 27 | 28 | if (dist > parent.MaxEntityDist || Ref.HeightAboveGround > 300.0f) 29 | { 30 | Dispose(); 31 | } 32 | 33 | else 34 | { 35 | var direction = Vector3.Normalize(new Vector3(parentPos.X, parentPos.Y, Ref.Position.Z) - Ref.Position); 36 | 37 | if (dist < parent.InternalForcesDist) 38 | { 39 | direction = -direction; 40 | 41 | Ref.ApplyForceToCenterOfMass(direction * 2.0f); 42 | 43 | var cross = Vector3.Cross(direction * 2.0f, Vector3.Cross(direction, Ref.ForwardVector)); 44 | 45 | Ref.ApplyForceToCenterOfMass(Vector3.Normalize(cross)); 46 | 47 | Dispose(); 48 | } 49 | 50 | else 51 | { 52 | var upDir = Vector3.Normalize(new Vector3(parentPos.X, parentPos.Y, parentPos.Z + 1000.0f) - Ref.Position); 53 | 54 | Ref.ApplyForce(direction, new Vector3(Probability.NextFloat(), 55 | Probability.NextFloat(), 56 | Probability.NextFloat())); 57 | 58 | Ref.ApplyForceToCenterOfMass(direction * parent.ForceScale * (ScriptThread.GetVar("vortexHorzizontalPullForce") / dist)); 59 | 60 | Ref.ApplyForceToCenterOfMass(upDir * ScriptThread.GetVar("vortexVerticalPullForce") * (2.0f / dist)); 61 | 62 | var cross = Vector3.Cross(direction, Vector3.WorldUp); 63 | 64 | float forceBias = Probability.NextFloat(); 65 | 66 | Ref.ApplyForceToCenterOfMass(Vector3.Normalize(cross) * parent.ForceScale * (((forceBias * 0.6f) + 0.16f) + (forceBias / dist))); // move them horizontally relative to the vortex. 67 | 68 | if (Probability.GetBoolean(0.23f)) 69 | { 70 | Ref.ApplyForceToCenterOfMass(direction * parent.ForceScale); 71 | 72 | cross = Vector3.Cross(direction, Vector3.Cross(direction, Ref.ForwardVector)); 73 | 74 | Ref.ApplyForceToCenterOfMass(Vector3.Normalize(cross) * parent.ForceScale); 75 | } 76 | 77 | if (Ref is Ped && Ref.Handle != Game.Player.Character.Handle && !(Ref as Ped).IsRagdoll) 78 | { 79 | Function.Call(Hash.SET_PED_TO_RAGDOLL, Ref.Handle, 800, 1500, 2, 1, 1, 0); 80 | } 81 | } 82 | 83 | Function.Call(Hash.SET_ENTITY_MAX_SPEED, Ref.Handle, 30.0f); 84 | } 85 | 86 | base.OnUpdate(gameTime); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ScriptMain/Script/TFactory.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | using GTA.Math; 3 | using GTA.Native; 4 | using System; 5 | using System.IO; 6 | using TornadoScript.ScriptCore; 7 | using TornadoScript.ScriptCore.Game; 8 | using TornadoScript.ScriptMain.Utility; 9 | 10 | namespace TornadoScript.ScriptMain.Script 11 | { 12 | /// 13 | /// Extension to manage the spawning of tornadoes. 14 | /// 15 | public class TornadoFactory : ScriptExtension 16 | { 17 | private WavePlayer _tornadoWarningSiren; 18 | 19 | private WavePlayer _tornadoLowRumble; 20 | 21 | private const int VortexLimit = 30; 22 | 23 | private const int TornadoSpawnDelayBase = 20000; 24 | 25 | private int _spawnDelayAdditive = 0; 26 | 27 | private int _spawnDelayStartTime = 0; 28 | 29 | private int _lastSpawnAttempt; 30 | 31 | public int ActiveVortexCount { get; private set; } 32 | 33 | private bool soundEnabled = true, sirenEnabled = true; 34 | 35 | private readonly TornadoVortex[] _activeVortexList = new TornadoVortex[VortexLimit]; 36 | 37 | public TornadoVortex[] ActiveVortexList => _activeVortexList; 38 | 39 | /// 40 | /// Whether we are in the process of spawning a tornado. 41 | /// If this is true, the script will prepare to spawn a tornado based on the 42 | /// set parameters. 43 | /// 44 | private bool spawnInProgress = false; 45 | 46 | private bool delaySpawn = false; 47 | 48 | public TornadoFactory() 49 | { 50 | InitSounds(); 51 | } 52 | 53 | private void InitSounds() 54 | { 55 | soundEnabled = ScriptThread.GetVar("soundenabled"); 56 | 57 | if (!soundEnabled) return; 58 | 59 | sirenEnabled = ScriptThread.GetVar("sirenenabled"); 60 | 61 | SoundLoad("tornado-weather-alert.wav", ref _tornadoWarningSiren, true); 62 | 63 | SoundLoad("rumble-bass-2.wav", ref _tornadoLowRumble, true); 64 | } 65 | 66 | /// 67 | /// Load a sound by name from the working directory of the program 68 | /// 69 | /// 70 | private void SoundLoad(string soundName, ref WavePlayer loadedSound, bool looping = false) 71 | { 72 | string absolutePath = 73 | AppDomain.CurrentDomain.BaseDirectory + "\\TornadoScript\\sounds\\" + soundName; 74 | 75 | if (File.Exists(absolutePath)) 76 | { 77 | loadedSound = new WavePlayer(absolutePath); 78 | 79 | loadedSound.SetLoopAudio(looping); 80 | } 81 | 82 | else 83 | Logger.Log("Could not load audio file '{0}'. Expected path: '{1}'", soundName, absolutePath); 84 | } 85 | 86 | /// 87 | /// Create a vortex at the given position. 88 | /// 89 | /// 90 | /// 91 | public TornadoVortex CreateVortex(Vector3 position) 92 | { 93 | if (spawnInProgress) // tornado already spawning in 94 | return null; 95 | 96 | for (var i = _activeVortexList.Length - 1; i > 0; i--) 97 | _activeVortexList[i] = _activeVortexList[i - 1]; 98 | 99 | position.Z = World.GetGroundHeight(position) - 10.0f; 100 | 101 | var tVortex = new TornadoVortex(position, false); 102 | 103 | tVortex.Build(); 104 | 105 | _activeVortexList[0] = tVortex; 106 | 107 | ActiveVortexCount = Math.Min(ActiveVortexCount + 1, _activeVortexList.Length); 108 | 109 | if (soundEnabled) 110 | { 111 | if (_tornadoLowRumble != null) 112 | { 113 | _tornadoLowRumble.SetVolume(0.0f); 114 | 115 | var volumeLevel = 1.0f - (1.0f / 300.0f * Vector3.Distance2D(position, GameplayCamera.Position)); 116 | 117 | volumeLevel = volumeLevel < 0.0f ? 0.0f : volumeLevel > 1.0f ? 1.0f : volumeLevel; 118 | 119 | _tornadoLowRumble.DoFadeIn(5000, volumeLevel); 120 | } 121 | } 122 | 123 | if (ScriptThread.GetVar("notifications")) 124 | { 125 | UI.Notify("Tornado spawned nearby."); 126 | } 127 | 128 | spawnInProgress = true; 129 | 130 | return null; 131 | } 132 | 133 | public override void OnUpdate(int gameTime) 134 | { 135 | //UI.ShowSubtitle("vcount: " + ActiveVortexCount + " spawning: " + spawnInProgress + " delay spawn: " + delaySpawn); 136 | 137 | if (ActiveVortexCount < 1) 138 | { 139 | if (World.Weather == Weather.ThunderStorm && ScriptThread.GetVar("spawnInStorm")) 140 | { 141 | if (!spawnInProgress && Game.GameTime - _lastSpawnAttempt > 1000) 142 | { 143 | if (Probability.GetBoolean(0.05f)) 144 | { 145 | _spawnDelayStartTime = Game.GameTime; 146 | 147 | _spawnDelayAdditive = Probability.GetInteger(0, 40); 148 | 149 | Function.Call(Hash.SET_WIND_SPEED, 70.0f); // add suspense :p 150 | 151 | if (soundEnabled && sirenEnabled && _tornadoWarningSiren != null) 152 | { 153 | _tornadoWarningSiren.SetVolume(0.6f); 154 | 155 | _tornadoWarningSiren.Play(true); 156 | } 157 | 158 | if (ScriptThread.GetVar("notifications")) 159 | { 160 | Helpers.NotifyWithIcon("Severe Weather Alert", "Tornado Warning issued for Los Santos and Blaine County", "char_milsite"); 161 | } 162 | 163 | spawnInProgress = true; 164 | delaySpawn = true; 165 | } 166 | 167 | _lastSpawnAttempt = Game.GameTime; 168 | } 169 | } 170 | 171 | else 172 | { 173 | delaySpawn = false; 174 | } 175 | 176 | if (delaySpawn) 177 | { 178 | // UI.ShowSubtitle("current: " + (Game.GameTime - _spawnDelayStartTime) + " target: " + (TornadoSpawnDelayBase + _spawnDelayAdditive)); 179 | 180 | if (Game.GameTime - _spawnDelayStartTime > (TornadoSpawnDelayBase + _spawnDelayAdditive)) 181 | { 182 | spawnInProgress = false; 183 | delaySpawn = false; 184 | 185 | var position = Game.Player.Character.Position + Game.Player.Character.ForwardVector * 100f; 186 | 187 | CreateVortex(position.Around(150.0f).Around(175.0f)); 188 | } 189 | } 190 | } 191 | 192 | else 193 | { 194 | if (_activeVortexList[0].DespawnRequested || Game.Player.IsDead && Function.Call(Hash.IS_SCREEN_FADED_OUT)) 195 | { 196 | RemoveAll(); 197 | } 198 | 199 | else if (soundEnabled) 200 | { 201 | if (_tornadoLowRumble != null) 202 | { 203 | var distance = Vector3.Distance2D(_activeVortexList[0].Position, GameplayCamera.Position); //attenuation factor 204 | 205 | var volumeLevel = 1.0f - 1.0f / 800.0f * distance; 206 | 207 | if (distance < 170.0f) 208 | volumeLevel += 0.087f * (2.219f * volumeLevel); 209 | 210 | volumeLevel = volumeLevel < 0.0f ? 0.0f : volumeLevel > 1.0f ? 1.0f : volumeLevel; 211 | 212 | _tornadoLowRumble.SetVolume(volumeLevel); 213 | } 214 | } 215 | } 216 | 217 | base.OnUpdate(gameTime); 218 | } 219 | 220 | public void RemoveAll() 221 | { 222 | spawnInProgress = false; 223 | 224 | if (_tornadoWarningSiren != null && _tornadoWarningSiren.IsPlaying()) 225 | _tornadoWarningSiren.DoFadeOut(3000, 0.0f); 226 | 227 | if (_tornadoLowRumble != null && _tornadoLowRumble.IsPlaying()) 228 | _tornadoLowRumble.DoFadeOut(3000, 0.0f); 229 | 230 | for (var i = 0; i < ActiveVortexCount; i++) 231 | { 232 | _activeVortexList[i].Dispose(); 233 | 234 | _activeVortexList[i] = null; 235 | } 236 | 237 | 238 | ActiveVortexCount = 0; 239 | } 240 | 241 | public override void Dispose() 242 | { 243 | for (var i = 0; i < ActiveVortexCount; i++) 244 | { 245 | _activeVortexList[i].Dispose(); 246 | } 247 | 248 | base.Dispose(); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /ScriptMain/Script/TParticle.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | using GTA.Math; 3 | using GTA.Native; 4 | using System; 5 | using TornadoScript.ScriptCore.Game; 6 | using TornadoScript.ScriptMain.Utility; 7 | 8 | namespace TornadoScript.ScriptMain.Script 9 | { 10 | /// 11 | /// Represents a particle in the tornado. 12 | /// 13 | public sealed class TornadoParticle : ScriptProp 14 | { 15 | public int LayerIndex { get; } 16 | 17 | public TornadoVortex Parent { get; set; } 18 | 19 | public bool IsCloud { get; } 20 | 21 | private Vector3 _centerPos; 22 | 23 | private readonly Vector3 _offset; 24 | 25 | private readonly Quaternion _rotation; 26 | 27 | private readonly LoopedParticle _ptfx; 28 | 29 | private readonly float _radius; 30 | 31 | private float _angle, _layerMask; 32 | 33 | /// 34 | /// Instantiate the class. 35 | /// 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// 41 | /// 42 | /// 43 | public TornadoParticle(TornadoVortex vortex, Vector3 position, Vector3 angle, string fxAsset, string fxName, float radius, int layerIdx, bool isCloud = false) 44 | : base(Setup(position)) 45 | { 46 | Parent = vortex; 47 | _centerPos = position; 48 | _rotation = MathEx.Euler(angle); 49 | _ptfx = new LoopedParticle(fxAsset, fxName); 50 | _radius = radius; 51 | _offset = new Vector3(0, 0, ScriptThread.GetVar("vortexLayerSeperationScale") * layerIdx); 52 | LayerIndex = layerIdx; 53 | IsCloud = isCloud; 54 | PostSetup(); 55 | } 56 | 57 | private void PostSetup() 58 | { 59 | _layerMask = 1.0f - (float)LayerIndex / (ScriptThread.GetVar("vortexMaxParticleLayers") * 4); 60 | 61 | _layerMask *= 0.1f * LayerIndex; 62 | 63 | _layerMask = 1.0f - _layerMask; 64 | 65 | if (_layerMask <= 0.3f) 66 | _layerMask = 0.3f; 67 | } 68 | 69 | /// 70 | /// Setup the base entity. 71 | /// 72 | /// 73 | /// 74 | private static Prop Setup(Vector3 position) 75 | { 76 | var model = new Model("prop_beachball_02"); 77 | 78 | if (!model.IsLoaded) model.Request(1000); 79 | 80 | var prop = World.CreateProp(model, position, false, false); 81 | 82 | Function.Call(Hash.SET_ENTITY_COLLISION, prop.Handle, 0, 0); 83 | 84 | prop.IsVisible = false; 85 | 86 | return prop; 87 | } 88 | 89 | /// 90 | /// Set the center position that the particle should rotate around. 91 | /// 92 | /// 93 | public void SetPosition(Vector3 center) 94 | { 95 | _centerPos = center; 96 | } 97 | 98 | /// 99 | /// Set the particle scale. 100 | /// 101 | /// 102 | public void SetScale(float scale) 103 | { 104 | _ptfx.Scale = scale; 105 | } 106 | 107 | public override void OnUpdate(int gameTime) 108 | { 109 | /* if (Parent == null) 110 | { 111 | Dispose(); 112 | } 113 | 114 | else 115 | {*/ 116 | _centerPos = Parent.Position + _offset; 117 | 118 | if (Math.Abs(_angle) > Math.PI * 2.0f) 119 | { 120 | _angle = 0.0f; 121 | } 122 | 123 | Ref.Position = _centerPos + 124 | MathEx.MultiplyVector(new Vector3(_radius * (float)Math.Cos(_angle), _radius * (float)Math.Sin(_angle), 0), _rotation); 125 | 126 | if (IsCloud) 127 | { 128 | _angle -= ScriptThread.GetVar("vortexRotationSpeed") * 0.16f * Game.LastFrameTime; 129 | } 130 | else 131 | { 132 | _angle -= ScriptThread.GetVar("vortexRotationSpeed") * _layerMask * Game.LastFrameTime; 133 | } 134 | 135 | // } 136 | 137 | base.OnUpdate(gameTime); 138 | } 139 | 140 | public void StartFx(float scale) 141 | { 142 | if (!_ptfx.IsLoaded) 143 | { 144 | _ptfx.Load(); 145 | } 146 | 147 | _ptfx.Start(this, scale); 148 | 149 | // _ptfx.Alpha = 0.5f; 150 | } 151 | 152 | public void RemoveFx() 153 | { 154 | _ptfx.Remove(); 155 | } 156 | 157 | public override void Dispose() 158 | { 159 | RemoveFx(); 160 | base.Dispose(); 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /ScriptMain/Script/TScript.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | using GTA.Native; 3 | using System.Windows.Forms; 4 | using TornadoScript.ScriptCore.Game; 5 | using TornadoScript.ScriptMain.Commands; 6 | using TornadoScript.ScriptMain.Config; 7 | using TornadoScript.ScriptMain.Memory; 8 | using TornadoScript.ScriptMain.Utility; 9 | 10 | namespace TornadoScript.ScriptMain.Script 11 | { 12 | public class MainScript : ScriptThread 13 | { 14 | private readonly TornadoFactory _factory; 15 | 16 | public MainScript() 17 | { 18 | RegisterVars(); 19 | SetupAssets(); 20 | _factory = GetOrCreate(); 21 | GetOrCreate(); 22 | KeyDown += KeyPressed; 23 | } 24 | 25 | private static void SetupAssets() 26 | { 27 | MemoryAccess.Initialize(); 28 | 29 | if (GetVar("vortexParticleMod")) 30 | { 31 | Function.Call(Hash.REQUEST_NAMED_PTFX_ASSET, "core"); // asset must be loaded before we can access its internal handle 32 | MemoryAccess.SetPtfxColor("core", "ent_amb_smoke_foundry", 1, System.Drawing.Color.Black); 33 | MemoryAccess.SetPtfxColor("core", "ent_amb_smoke_foundry", 2, System.Drawing.Color.Black); 34 | } 35 | } 36 | 37 | private static void RegisterVars() 38 | { 39 | RegisterVar("toggleconsole", Keys.T, true); 40 | RegisterVar("enableconsole", IniHelper.GetValue("Other", "EnableConsole", false)); 41 | RegisterVar("notifications", IniHelper.GetValue("Other", "Notifications", true)); 42 | RegisterVar("spawninstorm", IniHelper.GetValue("Other", "SpawnInStorm", true)); 43 | RegisterVar("soundenabled", IniHelper.GetValue("Other", "SoundEnabled", true)); 44 | RegisterVar("sirenenabled", IniHelper.GetValue("Other", "SirenEnabled", true)); 45 | RegisterVar("togglescript", IniHelper.GetValue("KeyBinds", "ToggleScript", Keys.F6), true); 46 | RegisterVar("enablekeybinds", IniHelper.GetValue("KeyBinds", "KeybindsEnabled", true)); 47 | RegisterVar("multiVortex", IniHelper.GetValue("VortexAdvanced", "MultiVortexEnabled", true)); 48 | RegisterVar("vortexMovementEnabled", IniHelper.GetValue("Vortex", "MovementEnabled", true)); 49 | RegisterVar("vortexMoveSpeedScale", IniHelper.GetValue("Vortex", "MoveSpeedScale", 1.0f)); 50 | RegisterVar("vortexTopEntitySpeed", IniHelper.GetValue("Vortex", "MaxEntitySpeed", 40.0f)); 51 | RegisterVar("vortexMaxEntityDist", IniHelper.GetValue("Vortex", "MaxEntityDistance", 57.0f)); 52 | RegisterVar("vortexHorizontalPullForce", IniHelper.GetValue("Vortex", "HorizontalForceScale", 1.7f)); 53 | RegisterVar("vortexVerticalPullForce", IniHelper.GetValue("Vortex", "VerticalForceScale", 2.29f)); 54 | RegisterVar("vortexRotationSpeed", IniHelper.GetValue("Vortex", "RotationSpeed", 2.4f)); 55 | RegisterVar("vortexRadius", IniHelper.GetValue("Vortex", "VortexRadius", 9.40f)); 56 | RegisterVar("vortexReverseRotation", IniHelper.GetValue("Vortex", "ReverseRotation", false)); 57 | RegisterVar("vortexMaxParticleLayers", IniHelper.GetValue("VortexAdvanced", "MaxParticleLayers", 48)); 58 | RegisterVar("vortexParticleCount", IniHelper.GetValue("VortexAdvanced", "ParticlesPerLayer", 9)); 59 | RegisterVar("vortexLayerSeperationScale", IniHelper.GetValue("VortexAdvanced", "LayerSeperationAmount", 22.0f)); 60 | RegisterVar("vortexParticleName", IniHelper.GetValue("VortexAdvanced", "ParticleName", "ent_amb_smoke_foundry")); 61 | RegisterVar("vortexParticleAsset", IniHelper.GetValue("VortexAdvanced", "ParticleAsset", "core")); 62 | RegisterVar("vortexParticleMod", IniHelper.GetValue("VortexAdvanced", "ParticleMod", true)); 63 | RegisterVar("vortexEnableCloudTopParticle", IniHelper.GetValue("VortexAdvanced", "CloudTopEnabled", true)); 64 | RegisterVar("vortexEnableCloudTopParticleDebris", IniHelper.GetValue("VortexAdvanced", "CloudTopDebrisEnabled", true)); 65 | RegisterVar("vortexEnableSurfaceDetection", IniHelper.GetValue("VortexAdvanced", "EnableSurfaceDetection", true)); 66 | RegisterVar("vortexUseEntityPool", IniHelper.GetValue("VortexAdvanced", "UseInternalPool", true)); 67 | } 68 | 69 | private void KeyPressed(object sender, KeyEventArgs e) 70 | { 71 | if (!GetVar("enablekeybinds")) return; 72 | 73 | if (e.KeyCode != GetVar("togglescript")) return; 74 | 75 | if (_factory.ActiveVortexCount > 0 && !GetVar("multiVortex")) 76 | { 77 | _factory.RemoveAll(); 78 | } 79 | 80 | else 81 | { 82 | Function.Call(Hash.REMOVE_PARTICLE_FX_IN_RANGE, 0f, 0f, 0f, 1000000.0f); 83 | 84 | Function.Call(Hash.SET_WIND, 70.0f); 85 | 86 | var position = Game.Player.Character.Position + Game.Player.Character.ForwardVector * 180f; 87 | 88 | _factory.CreateVortex(position); 89 | } 90 | } 91 | 92 | private bool didInitTlsAlloc = false; 93 | 94 | public override void OnUpdate(int gameTime) 95 | { 96 | if (!didInitTlsAlloc) 97 | { 98 | WinHelper.CopyTlsValues(WinHelper.GetProcessMainThreadId(), Win32Native.GetCurrentThreadId(), 0xC8, 0xC0, 0xB8); 99 | didInitTlsAlloc = true; 100 | } 101 | 102 | base.OnUpdate(gameTime); 103 | } 104 | 105 | private static void ReleaseAssets() 106 | { 107 | // 108 | } 109 | 110 | protected override void Dispose(bool a0) 111 | { 112 | Function.Call(Hash.REMOVE_PARTICLE_FX_IN_RANGE, 0f, 0f, 0f, 1000000.0f); 113 | 114 | ReleaseAssets(); 115 | 116 | base.Dispose(a0); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /ScriptMain/Script/TVortex.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | using GTA.Math; 3 | using GTA.Native; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Drawing; 7 | using System.Runtime.CompilerServices; 8 | using TornadoScript.ScriptCore.Game; 9 | using TornadoScript.ScriptMain.Memory; 10 | using TornadoScript.ScriptMain.Utility; 11 | 12 | namespace TornadoScript.ScriptMain.Script 13 | { 14 | public class TornadoVortex : ScriptExtension 15 | { 16 | /// 17 | /// Scale of the vortex forces. 18 | /// 19 | public float ForceScale { get; } = 3.0f; 20 | 21 | /// 22 | /// Maximum distance entites must be from the vortex before we start using internal vortext forces on them. 23 | /// 24 | public float InternalForcesDist { get; } = 5.0f; 25 | 26 | readonly List _particles = new List(); 27 | 28 | private int _aliveTime, _createdTime, _nextUpdateTime; 29 | 30 | private int _lastDebrisSpawnTime = 0; 31 | 32 | private int _lastFullUpdateTime; 33 | 34 | private int _lifeSpan; 35 | 36 | private struct ActiveEntity 37 | { 38 | public ActiveEntity(Entity entity, float xBias, float yBias) 39 | { 40 | Entity = entity; 41 | XBias = xBias; 42 | YBias = yBias; 43 | IsPlayer = entity == Helpers.GetLocalPed(); 44 | } 45 | 46 | public Entity Entity { get; } 47 | public float XBias { get; } 48 | public float YBias { get; } 49 | public bool IsPlayer { get; } 50 | } 51 | 52 | public const int MaxEntityCount = 300; 53 | 54 | private readonly Dictionary _pulledEntities = new Dictionary(); 55 | 56 | private readonly List pendingRemovalEntities = new List(); 57 | 58 | private Vector3 _position, _destination; 59 | 60 | private bool _despawnRequested; 61 | 62 | public Vector3 Position 63 | { 64 | get { return _position; } 65 | set { _position = value; } 66 | } 67 | 68 | public bool DespawnRequested 69 | { 70 | get { return _despawnRequested; } 71 | set { _despawnRequested = value; } 72 | } 73 | 74 | private readonly Ped _player = Helpers.GetLocalPed(); 75 | 76 | private int _lastPlayerShapeTestTime; 77 | 78 | bool _lastRaycastResultFailed; 79 | 80 | private materials lastMaterialTraversed; 81 | 82 | private int lastParticleShapeTestTime = 0; 83 | 84 | private Color particleColorPrev, particleColorGoal; 85 | 86 | private Color particleColor = Color.Black; 87 | 88 | private float particleLerpTime = 0.0f; 89 | 90 | private const float ColorLerpDuration = 200.0f; 91 | 92 | private bool _useInternalEntityArray = false; 93 | 94 | // todo: Add crosswinds at vortex base w/ raycast 95 | public TornadoVortex(Vector3 initialPosition, bool neverDespawn) 96 | { 97 | _position = initialPosition; 98 | _createdTime = Game.GameTime; 99 | _lifeSpan = neverDespawn ? -1 : Probability.GetInteger(160000, 600000); 100 | _useInternalEntityArray = ScriptThread.GetVar("vortexUseEntityPool"); 101 | } 102 | 103 | public void ChangeDestination(bool trackToPlayer ) 104 | { 105 | for (int i = 0; i < 50; i++) 106 | { 107 | _destination = trackToPlayer ? _player.Position.Around(130.0f) : Helpers.GetRandomPositionFromCoords(_destination, 100.0f); 108 | 109 | _destination.Z = World.GetGroundHeight(_destination) - 10.0f; 110 | 111 | var nearestRoadPos = World.GetNextPositionOnStreet(_destination); 112 | 113 | if (_destination.DistanceTo(nearestRoadPos) < 40.0f && Math.Abs(nearestRoadPos.Z - _destination.Z) < 10.0f) 114 | { 115 | break; 116 | } 117 | } 118 | } 119 | 120 | public void Build() 121 | { 122 | float radius = ScriptThread.GetVar("vortexRadius"); 123 | 124 | int particleCount = ScriptThread.GetVar("vortexParticleCount"); 125 | 126 | int maxLayers = ScriptThread.GetVar("vortexMaxParticleLayers"); 127 | 128 | string particleAsset = ScriptThread.GetVar("vortexParticleAsset"); 129 | 130 | string particleName = ScriptThread.GetVar("vortexParticleName"); 131 | 132 | bool enableClouds = ScriptThread.GetVar("vortexEnableCloudTopParticle"); 133 | 134 | bool enableDebris = ScriptThread.GetVar("vortexEnableCloudTopParticleDebris"); 135 | 136 | var multiplier = 360 / particleCount; 137 | 138 | var particleSize = 3.0685f; 139 | 140 | maxLayers = enableClouds ? 12 : maxLayers; // cannot spawn top particles with more than 12 layers!! 141 | 142 | for (var layerIdx = 0; layerIdx < maxLayers; layerIdx++) 143 | { 144 | //var lyrParticleNum = (i > maxLayers - 4 ? particleCount + 5 : particleCount); 145 | 146 | //multiplier = 360 / lyrParticleNum; 147 | for (var angle = 0; angle < (layerIdx > maxLayers - 4 ? particleCount + 5 : particleCount); angle++) 148 | { 149 | // increment the Z axis as we build up. 150 | var position = _position; 151 | 152 | position.Z += ScriptThread.GetVar("vortexLayerSeperationScale") * layerIdx; 153 | 154 | // place the particles at 360 / 10 on the X axis. 155 | var rotation = new Vector3(angle * multiplier, 0, 0); 156 | 157 | TornadoParticle particle; 158 | 159 | bool bIsTopParticle = false; 160 | 161 | if (layerIdx < 2) //debris layer 162 | { 163 | particle = new TornadoParticle(this, position, rotation, "scr_agencyheistb", "scr_env_agency3b_smoke", radius, layerIdx); 164 | 165 | particle.StartFx(4.7f); 166 | 167 | _particles.Add(particle); 168 | 169 | Function.Call(Hash.ADD_SHOCKING_EVENT_FOR_ENTITY, 86, particle.Ref.Handle, 0.0f); // shocking event at outer vorticies 170 | } 171 | 172 | if (enableClouds && layerIdx > maxLayers - 3) 173 | { 174 | if (enableDebris) 175 | { 176 | particle = new TornadoParticle(this, position, rotation, "scr_agencyheistb", "scr_env_agency3b_smoke", radius * 2.2f, layerIdx); 177 | 178 | particle.StartFx(12.7f); 179 | 180 | _particles.Add(particle); 181 | } 182 | 183 | position.Z += 12f; 184 | particleSize += 6.0f; 185 | 186 | radius += 7f; 187 | 188 | bIsTopParticle = true; 189 | } 190 | 191 | particle = new TornadoParticle(this, position, rotation, particleAsset, particleName, radius, layerIdx, bIsTopParticle); 192 | 193 | particle.StartFx(particleSize); 194 | 195 | radius += 0.0799999982118607f * (0.720000028610229f * layerIdx); 196 | 197 | particleSize += 0.00999999977648258f * (0.119999997317791f * layerIdx); 198 | 199 | _particles.Add(particle); 200 | 201 | } 202 | } 203 | } 204 | 205 | private void ReleaseEntity(int entityIdx) 206 | { 207 | pendingRemovalEntities.Add(entityIdx); 208 | } 209 | 210 | /// 211 | /// Adds a to the queue to be processed next frame 212 | /// 213 | /// 214 | private void AddEntity(ActiveEntity entity) 215 | { 216 | _pulledEntities[entity.Entity.Handle] = entity; 217 | } 218 | 219 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 220 | private void CollectNearbyEntities(int gameTime, float maxDistanceDelta) 221 | { 222 | if (gameTime < _nextUpdateTime) 223 | return; 224 | 225 | foreach (var ent in MemoryAccess.CollectEntitiesFull()) 226 | { 227 | if (_pulledEntities.Count >= MaxEntityCount) break; 228 | 229 | if (_pulledEntities.ContainsKey(ent.Handle) || 230 | ent.Position.DistanceTo2D(_position) > maxDistanceDelta + 4.0f || ent.HeightAboveGround > 300.0f) continue; 231 | 232 | if (ent is Ped && /*entities[p].Handle != _player.Handle &&*/ !(ent as Ped).IsRagdoll) 233 | { 234 | Function.Call(Hash.SET_PED_TO_RAGDOLL, ent.Handle, 800, 1500, 2, 1, 1, 0); 235 | } 236 | 237 | AddEntity(new ActiveEntity(ent, 3.0f * Probability.GetScalar(), 3.0f * Probability.GetScalar())); 238 | } 239 | 240 | _nextUpdateTime = gameTime + 600; 241 | } 242 | 243 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 244 | private void CollectNearbyEntitiesInternal(int gameTime, float maxDistanceDelta) 245 | { 246 | if (gameTime - _lastFullUpdateTime > 5000) 247 | { 248 | MemoryAccess.CollectEntitiesFull(); 249 | 250 | _lastFullUpdateTime = gameTime; 251 | } 252 | 253 | if (gameTime > _nextUpdateTime ) 254 | { 255 | foreach (var ent in MemoryAccess.GetAllEntitiesInternal()) 256 | { 257 | if (_pulledEntities.Count >= MaxEntityCount) break; 258 | 259 | if (_pulledEntities.ContainsKey(ent.Handle) || 260 | ent.Position.DistanceTo2D(_position) > maxDistanceDelta || 261 | ent.HeightAboveGround > 300.0f) continue; 262 | 263 | if (ent is Ped && !(ent as Ped).IsRagdoll && ent.HeightAboveGround > 2.0f) 264 | { 265 | Function.Call(Hash.SET_PED_TO_RAGDOLL, ent.Handle, 800, 1500, 2, 1, 1, 0); 266 | } 267 | 268 | AddEntity(new ActiveEntity(ent, 3.0f * Probability.GetScalar(), 3.0f * Probability.GetScalar())); 269 | } 270 | 271 | _nextUpdateTime = gameTime + 200; 272 | } 273 | } 274 | 275 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 276 | private void UpdatePulledEntities(int gameTime, float maxDistanceDelta) 277 | { 278 | float verticalForce = ScriptThread.GetVar("vortexVerticalPullForce"); 279 | 280 | float horizontalForce = ScriptThread.GetVar("vortexHorizontalPullForce"); 281 | 282 | float topSpeed = ScriptThread.GetVar("vortexTopEntitySpeed"); 283 | 284 | pendingRemovalEntities.Clear(); 285 | 286 | foreach (var e in _pulledEntities) 287 | { 288 | var entity = e.Value.Entity; 289 | 290 | var dist = Vector2.Distance(entity.Position.Vec2(), _position.Vec2()); 291 | 292 | if (dist > maxDistanceDelta - 13f || entity.HeightAboveGround > 300.0f) 293 | { 294 | ReleaseEntity(e.Key); 295 | continue; 296 | } 297 | 298 | var targetPos = new Vector3(_position.X + e.Value.XBias, _position.Y + e.Value.YBias, entity.Position.Z); 299 | 300 | var direction = Vector3.Normalize(targetPos - entity.Position); 301 | 302 | var forceBias = Probability.NextFloat(); 303 | 304 | var force = ForceScale * (forceBias + forceBias / dist); 305 | 306 | if (e.Value.IsPlayer) 307 | { 308 | verticalForce *= 1.62f; 309 | 310 | horizontalForce *= 1.2f; 311 | 312 | // horizontalForce *= 1.5f; 313 | 314 | if (gameTime - _lastPlayerShapeTestTime > 1000) 315 | { 316 | var raycast = World.Raycast(entity.Position, targetPos, IntersectOptions.Map); 317 | 318 | _lastRaycastResultFailed = raycast.DitHitAnything; 319 | 320 | _lastPlayerShapeTestTime = gameTime; 321 | } 322 | 323 | if (_lastRaycastResultFailed) 324 | continue; 325 | } 326 | 327 | if (entity.Model.IsPlane) 328 | { 329 | force *= 6.0f; 330 | verticalForce *= 6.0f; 331 | } 332 | 333 | // apply a directional force pulling them into the tornado... 334 | entity.ApplyForce(direction * horizontalForce, 335 | new Vector3(Probability.NextFloat(), 0, Probability.GetScalar())); 336 | 337 | var upDir = Vector3.Normalize(new Vector3(_position.X, _position.Y, _position.Z + 1000.0f) - 338 | entity.Position); 339 | // apply vertical forces 340 | entity.ApplyForceToCenterOfMass(upDir * verticalForce); 341 | 342 | var cross = Vector3.Cross(direction, Vector3.WorldUp); 343 | 344 | // move them along side the vortex. 345 | entity.ApplyForceToCenterOfMass(Vector3.Normalize(cross) * force * 346 | horizontalForce); 347 | 348 | Function.Call(Hash.SET_ENTITY_MAX_SPEED, entity.Handle, topSpeed); 349 | } 350 | 351 | foreach (var e in pendingRemovalEntities) 352 | { 353 | _pulledEntities.Remove(e); 354 | } 355 | } 356 | 357 | private static void ApplyDirectionalForce(Entity entity, Vector3 origin, Vector3 direction, float scale) 358 | { 359 | if (Function.Call(Hash.GET_VEHICLE_CLASS, entity) == 16 || entity.HeightAboveGround > 15.0f) return; 360 | 361 | float entityDist = Vector3.Distance(entity.Position, origin); 362 | 363 | float zForce, scaleModifier; 364 | 365 | Vector3 rotationalForce; 366 | 367 | if (entity is Vehicle) 368 | { 369 | zForce = Probability.GetBoolean(0.50f) ? 0.0332f : 0.0318f; 370 | scaleModifier = 22.0f; 371 | rotationalForce = new Vector3(0.0f, 0.1f, 0.40f); 372 | } 373 | 374 | else if (entity is Ped) 375 | { 376 | if (((Ped)entity).IsRagdoll == false) 377 | Function.Call(Hash.SET_PED_TO_RAGDOLL, entity.Handle, 800, 1500, 2, 1, 1, 0); 378 | zForce = 0.0034f; 379 | scaleModifier = 30.0f; 380 | rotationalForce = new Vector3(0.0f, 0.0f, 0.12f); 381 | } 382 | 383 | else 384 | { 385 | zForce = 0.000f; 386 | scaleModifier = 30.0f; 387 | rotationalForce = new Vector3(0.0f, 0.338f, 0.0f); 388 | } 389 | 390 | var force = (direction + new Vector3(0, 0, zForce)) * Math.Min(1.0f, scaleModifier / entityDist) * scale; 391 | 392 | entity.ApplyForce(force, rotationalForce, ForceType.MaxForceRot); 393 | } 394 | 395 | private bool DoEntityCapsuleTest(Vector3 start, Vector3 target, float radius, Entity ignore, out Entity hitEntity) 396 | { 397 | var raycastResult = World.RaycastCapsule(start, target, radius, IntersectOptions.Everything, ignore); 398 | 399 | hitEntity = raycastResult.HitEntity; 400 | 401 | return raycastResult.DitHitEntity; 402 | } 403 | 404 | private void UpdateCrosswinds(int gameTime) 405 | { 406 | var forwardLeft = _position + Vector3.WorldNorth * 100.0f; 407 | 408 | var rearLeft = _position - Vector3.WorldNorth * 100.0f; 409 | 410 | var direction = Vector3.Normalize(rearLeft - forwardLeft); 411 | 412 | Entity target; 413 | 414 | if (DoEntityCapsuleTest(forwardLeft, rearLeft, 22.0f, null, out target)) 415 | ApplyDirectionalForce(target, forwardLeft, direction, 4.0f); 416 | } 417 | 418 | private void UpdateSurfaceDetection(int gameTime) 419 | { 420 | if (gameTime - lastParticleShapeTestTime > 1200) 421 | { 422 | var str = ShapeTestEx.RunShapeTest(_position + Vector3.WorldUp * 10.0f, 423 | _position + Vector3.WorldDown * 10.0f, null, IntersectOptions.Everything); 424 | 425 | if (str.HitMaterial != lastMaterialTraversed) 426 | { 427 | switch (lastMaterialTraversed) 428 | { 429 | case materials.sand_track: 430 | case materials.sand_compact: 431 | case materials.sand_dry_deep: 432 | case materials.sand_loose: 433 | case materials.sand_wet: 434 | case materials.sand_wet_deep: 435 | { 436 | particleColorPrev = particleColor; 437 | particleColorGoal = Color.NavajoWhite; 438 | particleLerpTime = 0.0f; 439 | } 440 | 441 | break; 442 | default: 443 | particleColorPrev = particleColor; 444 | particleColorGoal = Color.Black; 445 | particleLerpTime = 0.0f; 446 | break; 447 | } 448 | 449 | lastMaterialTraversed = str.HitMaterial; 450 | } 451 | 452 | lastParticleShapeTestTime = gameTime; 453 | } 454 | 455 | if (particleLerpTime < 1.0f) 456 | { 457 | particleLerpTime += Game.LastFrameTime / ColorLerpDuration; 458 | particleColor = particleColor.Lerp(particleColorGoal, particleLerpTime); 459 | } 460 | 461 | MemoryAccess.SetPtfxColor("core", "ent_amb_smoke_foundry", 0, particleColor); 462 | MemoryAccess.SetPtfxColor("core", "ent_amb_smoke_foundry", 1, particleColor); 463 | MemoryAccess.SetPtfxColor("core", "ent_amb_smoke_foundry", 2, particleColor); 464 | } 465 | 466 | private void UpdateDebrisLayer(materials material) 467 | { 468 | if (Game.GameTime - _lastDebrisSpawnTime > 3000 + Probability.GetInteger(0, 5000)) 469 | { 470 | // UI.ShowSubtitle("spawn debris"); 471 | 472 | new TDebris(this, _position, ScriptThread.GetVar("vortexRadius")); 473 | } 474 | } 475 | 476 | public override void OnUpdate(int gameTime) 477 | { 478 | if (gameTime - _createdTime > _lifeSpan) 479 | _despawnRequested = true; 480 | 481 | if (ScriptThread.GetVar("vortexEnableSurfaceDetection")) 482 | UpdateSurfaceDetection(gameTime); 483 | 484 | if (ScriptThread.GetVar("vortexMovementEnabled")) 485 | { 486 | if (_destination == Vector3.Zero || _position.DistanceTo(_destination) < 15.0f) 487 | { 488 | ChangeDestination(false); 489 | } 490 | 491 | if (_position.DistanceTo(_player.Position) > 200.0f) 492 | { 493 | ChangeDestination(true); 494 | } 495 | 496 | var vTarget = MathEx.MoveTowards(_position, _destination, ScriptThread.GetVar("vortexMoveSpeedScale") * 0.287f); 497 | 498 | _position = Vector3.Lerp(_position, vTarget, Game.LastFrameTime * 20.0f); 499 | } 500 | 501 | float maxEntityDist = ScriptThread.GetVar("vortexMaxEntityDist"); 502 | 503 | CollectNearbyEntities(gameTime, maxEntityDist); 504 | 505 | UpdatePulledEntities(gameTime, maxEntityDist); 506 | 507 | UpdateDebrisLayer(lastMaterialTraversed); 508 | // UpdateCrosswinds(gameTime); 509 | } 510 | 511 | public override void Dispose() 512 | { 513 | _particles.ForEach(x => x.Dispose()); 514 | 515 | base.Dispose(); 516 | } 517 | } 518 | } 519 | -------------------------------------------------------------------------------- /ScriptMain/Utility/Audio/LoopStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using NAudio.Wave; 7 | 8 | namespace TornadoScript.ScriptMain.Utility 9 | { 10 | /// 11 | /// Stream for looping playback 12 | /// 13 | public class LoopStream : WaveStream 14 | { 15 | WaveStream sourceStream; 16 | 17 | /// 18 | /// Creates a new Loop stream 19 | /// 20 | /// The stream to read from. Note: the Read method of this stream should return 0 when it reaches the end 21 | /// or else we will not loop to the start again. 22 | public LoopStream(WaveStream sourceStream) 23 | { 24 | this.sourceStream = sourceStream; 25 | this.EnableLooping = true; 26 | } 27 | 28 | /// 29 | /// Use this to turn looping on or off 30 | /// 31 | public bool EnableLooping { get; set; } 32 | 33 | /// 34 | /// Return source stream's wave format 35 | /// 36 | public override WaveFormat WaveFormat 37 | { 38 | get { return sourceStream.WaveFormat; } 39 | } 40 | 41 | /// 42 | /// LoopStream simply returns 43 | /// 44 | public override long Length 45 | { 46 | get { return sourceStream.Length; } 47 | } 48 | 49 | /// 50 | /// LoopStream simply passes on positioning to source stream 51 | /// 52 | public override long Position 53 | { 54 | get { return sourceStream.Position; } 55 | set { sourceStream.Position = value; } 56 | } 57 | 58 | public override int Read(byte[] buffer, int offset, int count) 59 | { 60 | int totalBytesRead = 0; 61 | 62 | while (totalBytesRead < count) 63 | { 64 | int bytesRead = sourceStream.Read(buffer, offset + totalBytesRead, count - totalBytesRead); 65 | if (bytesRead == 0) 66 | { 67 | if (sourceStream.Position == 0 || !EnableLooping) 68 | { 69 | // something wrong with the source stream 70 | break; 71 | } 72 | // loop 73 | sourceStream.Position = 0; 74 | } 75 | totalBytesRead += bytesRead; 76 | } 77 | return totalBytesRead; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ScriptMain/Utility/Audio/SoundManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows.Automation; 4 | using TornadoScript.ScriptCore.Game; 5 | 6 | namespace TornadoScript.ScriptMain.Utility 7 | { 8 | public class SoundManager : ScriptExtension 9 | { 10 | private List sounds = new List(); 11 | 12 | public SoundManager() 13 | { 14 | SetupWindowHandling(); 15 | } 16 | 17 | private void SetupWindowHandling() 18 | { 19 | #if !DEBUG 20 | AutomationFocusChangedEventHandler handler = SoundManager_OnWindowFocusChange; 21 | 22 | Automation.AddAutomationFocusChangedEventHandler(handler); 23 | #endif 24 | } 25 | 26 | public void Add(WavePlayer sound) 27 | { 28 | sounds.Add(sound); 29 | } 30 | 31 | private void SoundManager_OnWindowFocusChange(object source, AutomationFocusChangedEventArgs e) 32 | { 33 | var focusedHandle = new IntPtr(AutomationElement.FocusedElement.Current.NativeWindowHandle); 34 | var mainWindowHandle = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle; 35 | 36 | if (focusedHandle == mainWindowHandle) 37 | { 38 | foreach (var sound in sounds) 39 | { 40 | if (sound.IsPaused()) 41 | sound.Play(); 42 | } 43 | } 44 | 45 | else 46 | { 47 | foreach (var sound in sounds) 48 | { 49 | if (sound.IsPlaying()) 50 | sound.Pause(); 51 | } 52 | } 53 | } 54 | 55 | public override void OnUpdate(int gameTime) 56 | { 57 | foreach (var sound in sounds) 58 | { 59 | sound.Update(); 60 | } 61 | 62 | base.OnUpdate(gameTime); 63 | } 64 | 65 | public override void Dispose() 66 | { 67 | #if !DEBUG 68 | Automation.RemoveAutomationFocusChangedEventHandler(SoundManager_OnWindowFocusChange); 69 | #endif 70 | 71 | base.Dispose(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ScriptMain/Utility/Audio/WavePlayer.cs: -------------------------------------------------------------------------------- 1 | using NAudio.Wave; 2 | using NAudio.Wave.SampleProviders; 3 | using TornadoScript.ScriptCore.Game; 4 | 5 | namespace TornadoScript.ScriptMain.Utility 6 | { 7 | public class WavePlayer 8 | { 9 | private bool soundWasPlaying = false; 10 | 11 | private float fadeStartVolume = 0.0f, fadeTarget = 0.0f; 12 | 13 | private float currentVolume = 0.0f; 14 | 15 | private int fadeTime = 0; 16 | 17 | private bool soundFadingIn = false, soundFadingOut = false; 18 | 19 | private LoopStream _waveStream; 20 | 21 | private SampleChannel _waveChannel; 22 | 23 | private WaveOutEvent _waveOut; 24 | 25 | public WavePlayer(string audioFilename) 26 | { 27 | _waveStream = new LoopStream(new WaveFileReader(audioFilename)); 28 | 29 | _waveChannel = new SampleChannel(_waveStream); 30 | 31 | _waveOut = new WaveOutEvent(); 32 | 33 | _waveOut.Init(_waveChannel); 34 | 35 | var soundManager = ScriptThread.GetOrCreate(); 36 | 37 | soundManager.Add(this); 38 | } 39 | 40 | public void SetVolume(float volumeLevel) 41 | { 42 | // GTA.UI.ShowSubtitle(_waveOut.Volume.ToString()); 43 | if (soundFadingIn || soundFadingOut) 44 | return; 45 | _waveChannel.Volume = volumeLevel; 46 | } 47 | 48 | public bool IsPlaying() 49 | { 50 | return _waveOut.PlaybackState == PlaybackState.Playing; 51 | } 52 | 53 | public bool IsPaused() 54 | { 55 | return _waveOut.PlaybackState == PlaybackState.Paused; 56 | } 57 | 58 | public void SetLoopAudio(bool shouldLoopAudio) 59 | { 60 | _waveStream.EnableLooping = shouldLoopAudio; 61 | } 62 | 63 | public void DoFadeIn(int fadeTime, float fadeTarget) 64 | { 65 | this.fadeTime = fadeTime; 66 | this.fadeTarget = fadeTarget; 67 | soundFadingOut = false; 68 | soundFadingIn = true; 69 | currentVolume = _waveChannel.Volume; 70 | _waveOut.Play(); 71 | } 72 | 73 | 74 | public void DoFadeOut(int fadeTime, float fadeTarget) 75 | { 76 | this.fadeTime = fadeTime; 77 | this.fadeTarget = fadeTarget; 78 | soundFadingOut = true; 79 | soundFadingIn = false; 80 | currentVolume = _waveChannel.Volume; 81 | _waveOut.Play(); 82 | } 83 | 84 | public void Pause() 85 | { 86 | _waveOut.Pause(); 87 | } 88 | 89 | public void Stop() 90 | { 91 | _waveOut.Stop(); 92 | } 93 | 94 | public void Play(bool fromStart = false) 95 | { 96 | if (fromStart) 97 | _waveStream.CurrentTime = System.TimeSpan.Zero; 98 | 99 | _waveOut.Play(); 100 | 101 | soundWasPlaying = true; 102 | } 103 | 104 | public void Update() 105 | { 106 | if (soundFadingIn) 107 | { 108 | if (currentVolume < fadeTarget) 109 | { 110 | currentVolume += GTA.Game.LastFrameTime * (1000.0f / fadeTime); 111 | 112 | currentVolume = currentVolume < 0.0f ? 0.0f : currentVolume > 1.0f ? 1.0f : currentVolume; 113 | 114 | _waveChannel.Volume = currentVolume; 115 | } 116 | 117 | else 118 | soundFadingIn = false; 119 | } 120 | 121 | else if (soundFadingOut) 122 | { 123 | if (currentVolume > fadeTarget) 124 | { 125 | currentVolume -= GTA.Game.LastFrameTime * (1000.0f / fadeTime); 126 | 127 | currentVolume = currentVolume < 0.0f ? 0.0f : currentVolume > 1.0f ? 1.0f : currentVolume; 128 | 129 | _waveChannel.Volume = currentVolume; 130 | } 131 | 132 | else 133 | { 134 | _waveOut.Stop(); 135 | 136 | soundFadingOut = false; 137 | } 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /ScriptMain/Utility/GameSound.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | using GTA.Math; 3 | using GTA.Native; 4 | 5 | namespace TornadoScript.ScriptMain.Utility 6 | { 7 | public class GameSound 8 | { 9 | private int _soundId; 10 | private readonly string _soundSet, _sound; 11 | 12 | public bool Active { get; private set; } 13 | 14 | public GameSound(string sound, string soundSet) 15 | { 16 | Active = false; 17 | _sound = sound; 18 | _soundSet = soundSet; 19 | _soundId = -1; 20 | } 21 | 22 | public static void Load(string audioBank) 23 | { 24 | Function.Call(Hash.REQUEST_SCRIPT_AUDIO_BANK, audioBank, false); 25 | } 26 | 27 | public static void Release(string audioBank) 28 | { 29 | Function.Call(Hash.RELEASE_NAMED_SCRIPT_AUDIO_BANK, audioBank); 30 | } 31 | 32 | public static void Load(GameSound sound) 33 | { 34 | Function.Call(Hash.REQUEST_SCRIPT_AUDIO_BANK, sound._soundSet, false); 35 | } 36 | 37 | public void Play(Entity ent) 38 | { 39 | _soundId = Function.Call(Hash.GET_SOUND_ID); 40 | Function.Call(Hash.PLAY_SOUND_FROM_ENTITY, _soundId, _sound, ent.Handle, 0, 0, 0); 41 | Active = true; 42 | } 43 | 44 | public void Play(Vector3 position, int range) 45 | { 46 | _soundId = Function.Call(Hash.GET_SOUND_ID); 47 | Function.Call(Hash.PLAY_SOUND_FROM_COORD, _soundId, _sound, position.X, position.Y, position.Z, 0, 1, range, 0); 48 | Active = true; 49 | } 50 | 51 | public void Destroy() 52 | { 53 | if (_soundId == -1) return; 54 | Function.Call(Hash.STOP_SOUND, _soundId); 55 | Function.Call(Hash.RELEASE_SOUND_ID, _soundId); 56 | _soundId = -1; 57 | Active = false; 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /ScriptMain/Utility/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Runtime.CompilerServices; 6 | using GTA; 7 | using GTA.Math; 8 | using GTA.Native; 9 | 10 | namespace TornadoScript.ScriptMain.Utility 11 | { 12 | public static class Helpers 13 | { 14 | public static Ped GetLocalPed() 15 | { 16 | return Game.Player.Character; 17 | } 18 | 19 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 20 | public static Vector2 Vec2(this Vector3 v) 21 | { 22 | return new Vector2(v.X, v.Y); 23 | } 24 | 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public static void ApplyForceToCenterOfMass(this Entity entity, Vector3 force) 27 | { 28 | Function.Call(Hash.APPLY_FORCE_TO_ENTITY_CENTER_OF_MASS, entity.Handle, 1, force.X, force.Y, force.Z, 0, 0, 1, 1); 29 | } 30 | 31 | public static Vector3 GetRandomPositionFromCoords(Vector3 position, float multiplier) 32 | { 33 | float randX, randY; 34 | 35 | int v1 = Function.Call(Hash.GET_RANDOM_INT_IN_RANGE, 0, 3999) / 1000; 36 | 37 | if (v1 == 0) 38 | { 39 | randX = Function.Call(Hash.GET_RANDOM_FLOAT_IN_RANGE, 50.0f, 200.0f) * multiplier; 40 | randY = Function.Call(Hash.GET_RANDOM_FLOAT_IN_RANGE, -50.0f, 50.0f) * multiplier; 41 | } 42 | else if (v1 == 1) 43 | { 44 | randX = Function.Call(Hash.GET_RANDOM_FLOAT_IN_RANGE, 50.0f, 200.0f) * multiplier; 45 | randY = Function.Call(Hash.GET_RANDOM_FLOAT_IN_RANGE, -50.0f, 50.0f) * multiplier; 46 | } 47 | else if (v1 == 2) 48 | { 49 | randX = Function.Call(Hash.GET_RANDOM_FLOAT_IN_RANGE, -50.0f, -200.0f) * multiplier; 50 | randY = Function.Call(Hash.GET_RANDOM_FLOAT_IN_RANGE, 50.0f, 50.0f) * multiplier; 51 | } 52 | else 53 | { 54 | randX = Function.Call(Hash.GET_RANDOM_FLOAT_IN_RANGE, 50.0f, -200.0f) * multiplier; 55 | randY = Function.Call(Hash.GET_RANDOM_FLOAT_IN_RANGE, -50.0f, 50.0f) * multiplier; 56 | } 57 | return new Vector3(randX + position.X, randY + position.Y, position.Z); 58 | 59 | } 60 | 61 | public static string[] GetLines(this string s) 62 | { 63 | return s.Split(new[] { Environment.NewLine }, StringSplitOptions.None); 64 | } 65 | 66 | /// 67 | /// Populates a list of strings from an embedded string resource. 68 | /// 69 | /// The string resource (Properties.Resources.ProjectName...) 70 | /// 71 | public static IList ReadEmbeddedResource(string resource) 72 | { 73 | string[] text = resource.GetLines(); 74 | return new List(text); 75 | } 76 | 77 | public static float Lerp(this float a, float b, float f) 78 | { 79 | return a * (1.0f - f) + b * f; 80 | } 81 | 82 | public static Color Lerp(this Color source, Color target, double percent) 83 | { 84 | var r = (byte)(source.R + (target.R - source.R) * percent); 85 | var g = (byte)(source.G + (target.G - source.G) * percent); 86 | var b = (byte)(source.B + (target.B - source.B) * percent); 87 | 88 | return Color.FromArgb(source.A, r, g, b); 89 | } 90 | 91 | /// 92 | /// Writes a list of strings to a file at the specified path. 93 | /// 94 | /// The list to write 95 | /// The specified path 96 | public static void WriteListToFile(IList list, string filepath) 97 | { 98 | if (File.Exists(filepath)) File.Delete(filepath); 99 | using (StreamWriter stream = new StreamWriter(filepath)) 100 | { 101 | foreach (string line in list) 102 | { 103 | stream.WriteLine(line); 104 | } 105 | } 106 | } 107 | 108 | public static void NotifyWithIcon(string title, string text, string icon) 109 | { 110 | Function.Call(Hash._SET_NOTIFICATION_TEXT_ENTRY, "STRING"); 111 | Function.Call(Hash._ADD_TEXT_COMPONENT_STRING, text); 112 | Function.Call(Hash._SET_NOTIFICATION_MESSAGE, icon, icon, false, 4, title, ""); 113 | Function.Call(Hash._DRAW_NOTIFICATION, false, true); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /ScriptMain/Utility/LoopedParticle.cs: -------------------------------------------------------------------------------- 1 | using GTA; 2 | using GTA.Math; 3 | using GTA.Native; 4 | using Color = System.Drawing.Color; 5 | 6 | namespace TornadoScript.ScriptMain.Utility 7 | { 8 | public class LoopedParticle 9 | { 10 | private float _scale; 11 | 12 | private float _alpha; 13 | 14 | public string AssetName { get; } 15 | 16 | public string FxName { get; } 17 | 18 | public int Handle { get; private set; } 19 | 20 | /// 21 | /// If the particle FX is spawned. 22 | /// 23 | public bool Exists => Handle != -1 && Function.Call(Hash.DOES_PARTICLE_FX_LOOPED_EXIST, Handle); 24 | 25 | /// 26 | /// If the particle FX asset is loaded. 27 | /// 28 | public bool IsLoaded => Function.Call(Hash.HAS_NAMED_PTFX_ASSET_LOADED, AssetName); 29 | 30 | /// 31 | /// Set the particle FX scale. 32 | /// 33 | public float Alpha { get { return _alpha; } set { Function.Call(Hash.SET_PARTICLE_FX_LOOPED_ALPHA, Handle, _alpha = value); } } 34 | 35 | /// 36 | /// Set the particle FX scale. 37 | /// 38 | public float Scale { get { return _scale; } set { Function.Call(Hash.SET_PARTICLE_FX_LOOPED_SCALE, Handle, _scale = value); } } 39 | 40 | 41 | /// 42 | /// Set the particle FX looped colour. 43 | /// 44 | public Color Colour { set { Function.Call(Hash.SET_PARTICLE_FX_LOOPED_COLOUR, Handle, value.R, value.G, value.B, 0); } } 45 | 46 | public LoopedParticle(string assetName, string fxName) 47 | { 48 | Handle = -1; 49 | AssetName = assetName; 50 | FxName = fxName; 51 | } 52 | 53 | /// 54 | /// Load the particle FX asset. 55 | /// 56 | public void Load() 57 | { 58 | Function.Call(Hash.REQUEST_NAMED_PTFX_ASSET, AssetName); 59 | } 60 | 61 | /// 62 | /// Start particle FX on the specified entity. 63 | /// 64 | /// Entity to attach to. 65 | /// Scale of the fx. 66 | /// Optional offset. 67 | /// Optional rotation. 68 | /// Entity bone. 69 | public void Start(Entity entity, float scale, Vector3 offset, Vector3 rotation, Bone? bone) 70 | { 71 | if (Handle != -1) return; 72 | 73 | _scale = scale; 74 | 75 | Function.Call(Hash._SET_PTFX_ASSET_NEXT_CALL, AssetName); 76 | 77 | Handle = bone == null ? 78 | Function.Call(Hash.START_PARTICLE_FX_LOOPED_ON_ENTITY, FxName, 79 | entity, offset.X, offset.Y, offset.Z, rotation.X, rotation.Y, rotation.Z, scale, 0, 0, 1) : 80 | Function.Call(Hash._START_PARTICLE_FX_LOOPED_ON_ENTITY_BONE, FxName, 81 | entity, offset.X, offset.Y, offset.Z, rotation.X, rotation.Y, rotation.Z, (int)bone, scale, 0, 0, 0); 82 | } 83 | 84 | /// 85 | /// Start particle FX on the specified entity. 86 | /// 87 | /// Entity to attach to. 88 | /// Scale of the fx. 89 | public void Start(Entity entity, float scale) 90 | { 91 | Start(entity, scale, Vector3.Zero, Vector3.Zero, null); 92 | } 93 | 94 | /// 95 | /// Start particle FX at the specified position. 96 | /// 97 | /// Position in world space. 98 | /// Scale of the fx. 99 | /// Optional rotation. 100 | public void Start(Vector3 position, float scale, Vector3 rotation) 101 | { 102 | if (Handle != -1) return; 103 | 104 | _scale = scale; 105 | 106 | Function.Call(Hash._SET_PTFX_ASSET_NEXT_CALL, AssetName); 107 | 108 | Handle = Function.Call(Hash.START_PARTICLE_FX_LOOPED_AT_COORD, FxName, 109 | position.X, position.Y, position.Z, rotation.X, rotation.Y, rotation.Z, scale, 0, 0, 0, 0); 110 | } 111 | 112 | /// 113 | /// Start particle FX at the specified position. 114 | /// 115 | /// Position in world space. 116 | /// Scale of the fx. 117 | public void Start(Vector3 position, float scale) 118 | { 119 | Start(position, scale, Vector3.Zero); 120 | } 121 | 122 | /// 123 | /// Set position offsets. 124 | /// 125 | /// 126 | /// 127 | public void SetOffsets(Vector3 offset, Vector3 rotOffset) 128 | { 129 | Function.Call(Hash.SET_PARTICLE_FX_LOOPED_OFFSETS, Handle, offset.X, offset.Y, offset.Z, rotOffset.X, rotOffset.Y, rotOffset.Z); 130 | } 131 | 132 | /// 133 | /// Set custom PTFX evolution variables. 134 | /// 135 | /// 136 | /// 137 | public void SetEvolution(string variableName, float value) 138 | { 139 | Function.Call(Hash.SET_PARTICLE_FX_LOOPED_EVOLUTION, Handle, variableName, value, 0); 140 | } 141 | 142 | /// 143 | /// Remove the particle FX. 144 | /// 145 | public void Remove() 146 | { 147 | if (Handle == -1) return; 148 | 149 | Function.Call(Hash.STOP_PARTICLE_FX_LOOPED, Handle, 0); 150 | 151 | Function.Call(Hash.REMOVE_PARTICLE_FX, Handle, 0); 152 | Handle = -1; 153 | } 154 | 155 | /// 156 | /// Remove the particle FX in range. 157 | /// 158 | public void Remove(Vector3 position, float radius) 159 | { 160 | if (Handle == -1) return; 161 | 162 | Function.Call(Hash.REMOVE_PARTICLE_FX_IN_RANGE, position.X, position.Y, position.Z, radius); 163 | Handle = -1; 164 | } 165 | 166 | /// 167 | /// Unload the loaded particle FX asset. 168 | /// 169 | public void Unload() 170 | { 171 | if (IsLoaded) 172 | Function.Call((Hash)0x5F61EBBE1A00F96D, AssetName); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /ScriptMain/Utility/MathEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using GTA.Math; 4 | 5 | namespace TornadoScript.ScriptMain.Utility 6 | { 7 | public static class MathEx 8 | { 9 | private static Dictionary _cosTable = new Dictionary(); 10 | 11 | private static float[] _cos = new float[720]; 12 | 13 | private static float[] _sin = new float[720]; 14 | 15 | public const double RadToDeg = 180 / Math.PI; 16 | 17 | public const double DegToRad = Math.PI / 180; 18 | 19 | static MathEx() 20 | { 21 | for (int i = 0; i < 360; i++) 22 | { 23 | _cos[i] = (float) Math.Cos(ToRadians(360 - i)); 24 | _sin[i] = (float) Math.Sin(ToRadians(360 - i)); 25 | _cos[i + 360] = (float)Math.Cos(ToRadians(i)); 26 | _sin[i + 360] = (float)Math.Sin(ToRadians(i)); 27 | } 28 | } 29 | 30 | public static float Cos(double value) 31 | { 32 | int deg = (int)value.ToDegrees(); 33 | return value < 0 ? _cos[-deg] : _cos[deg + 360]; 34 | } 35 | 36 | public static float Sin(double value) 37 | { 38 | int deg = (int)value.ToDegrees(); 39 | return value < 0 ? _sin[-deg] : _sin[deg + 360]; 40 | } 41 | 42 | public static Vector3 MoveTowards(Vector3 current, Vector3 target, float maxDistanceDelta) 43 | { 44 | Vector3 a = target - current; 45 | float magnitude = a.Length(); 46 | if (magnitude <= maxDistanceDelta || magnitude == 0f) 47 | { 48 | return target; 49 | } 50 | 51 | return current + a / magnitude * maxDistanceDelta; 52 | } 53 | 54 | public static Vector3 AnglesToForward(Vector3 position, Vector3 angles, int length) 55 | { 56 | float num = (float)Math.Sin(angles.X * Math.PI / 180) * length; 57 | float num1 = (float)Math.Sqrt(length * length - num * num); 58 | float num2 = (float)Math.Sin(angles.Y * Math.PI / 180) * num1; 59 | float num3 = (float)Math.Cos(angles.Y * Math.PI / 180) * num1; 60 | return new Vector3(position.X + num3, position.Y + num2, position.Z - num); 61 | } 62 | 63 | private static Quaternion AngleAxis(float degress, ref Vector3 axis) 64 | { 65 | if (axis.Length() == 0.0f) 66 | return Quaternion.Identity; 67 | 68 | Quaternion result = Quaternion.Identity; 69 | var radians = degress * (float)(Math.PI / 180.0); 70 | radians *= 0.5f; 71 | axis.Normalize(); 72 | axis = axis * (float)Math.Sin(radians); 73 | result.X = axis.X; 74 | result.Y = axis.Y; 75 | result.Z = axis.Z; 76 | result.W = (float)Math.Cos(radians); 77 | 78 | result.Normalize(); 79 | 80 | return result; 81 | } 82 | 83 | /// 84 | /// Convert degrees to radians. 85 | /// 86 | /// The value in degrees. 87 | /// 88 | public static double ToRadians(this double val) 89 | { 90 | return DegToRad * val; 91 | } 92 | 93 | 94 | /// 95 | /// Convert degrees to radians. 96 | /// 97 | /// The value in degrees. 98 | /// 99 | public static double ToDegrees(this double val) 100 | { 101 | return RadToDeg * val; 102 | } 103 | 104 | public static Quaternion Euler(Vector3 eulerAngles) 105 | { 106 | float halfPhi = 0.5f * eulerAngles.X; // Half the roll. 107 | float halfTheta = 0.5f * eulerAngles.Y; // Half the pitch. 108 | float halfPsi = 0.5f * eulerAngles.Z; // Half the yaw. 109 | 110 | float cosHalfPhi = (float)Math.Cos(halfPhi); 111 | float sinHalfPhi = (float)Math.Sin(halfPhi); 112 | float cosHalfTheta = (float)Math.Cos(halfTheta); 113 | float sinHalfTheta = (float)Math.Sin(halfTheta); 114 | float cosHalfPsi = (float)Math.Cos(halfPsi); 115 | float sinHalfPsi = (float)Math.Sin(halfPsi); 116 | 117 | return new Quaternion( 118 | cosHalfPhi * cosHalfTheta * cosHalfPsi - sinHalfPhi * sinHalfTheta * sinHalfPsi, 119 | sinHalfPhi * cosHalfTheta * cosHalfPsi + cosHalfPhi * sinHalfTheta * sinHalfPsi, 120 | cosHalfPhi * sinHalfTheta * cosHalfPsi - sinHalfPhi * cosHalfTheta * sinHalfPsi, 121 | cosHalfPhi * cosHalfTheta * sinHalfPsi + sinHalfPhi * sinHalfTheta * cosHalfPsi 122 | ); 123 | } 124 | 125 | public static Vector3 MultiplyVector(Vector3 vec, Quaternion quat) 126 | { 127 | float num = quat.X * 2f; 128 | float num2 = quat.Y * 2f; 129 | float num3 = quat.Z * 2f; 130 | float num4 = quat.X * num; 131 | float num5 = quat.Y * num2; 132 | float num6 = quat.Z * num3; 133 | float num7 = quat.X * num2; 134 | float num8 = quat.X * num3; 135 | float num9 = quat.Y * num3; 136 | float num10 = quat.W * num; 137 | float num11 = quat.W * num2; 138 | float num12 = quat.W * num3; 139 | Vector3 result; 140 | result.X = (1f - (num5 + num6)) * vec.X + (num7 - num12) * vec.Y + (num8 + num11) * vec.Z; 141 | result.Y = (num7 + num12) * vec.X + (1f - (num4 + num6)) * vec.Y + (num9 - num10) * vec.Z; 142 | result.Z = (num8 - num11) * vec.X + (num9 + num10) * vec.Y + (1f - (num4 + num5)) * vec.Z; 143 | return result; 144 | } 145 | 146 | public static Quaternion Euler(float x, float y, float z) 147 | { 148 | return Euler(new Vector3(x, y, z)); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /ScriptMain/Utility/Probability.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TornadoScript.ScriptMain.Utility 4 | { 5 | public static class Probability 6 | { 7 | private static int _lastCheckedTime; 8 | 9 | private static readonly Random Rand = new Random(); 10 | 11 | /// 12 | /// Gets a random float value 13 | /// 14 | /// 15 | public static float GetFloat() 16 | { 17 | return GetScalar() * float.MaxValue; 18 | } 19 | 20 | 21 | /// 22 | /// Gets a random float value in range 23 | /// 24 | /// 25 | public static float GetFloat(float min, float max) 26 | { 27 | return NextFloat() * (max - min) + min; 28 | } 29 | 30 | /// 31 | /// Gets a random float value from 0.0 to 1.0 32 | /// 33 | /// 34 | public static float NextFloat() 35 | { 36 | return (float)Rand.NextDouble(); 37 | } 38 | 39 | /// 40 | /// Gets a random float value from -1.0 to 1.0 41 | /// 42 | /// 43 | public static float GetScalar() 44 | { 45 | var val = Rand.NextDouble(); 46 | val -= 0.5; 47 | val *= 2; 48 | return (float) val; 49 | } 50 | 51 | /// 52 | /// Gets a random integer value in range 53 | /// 54 | /// 55 | public static int GetInteger(int min, int max) 56 | { 57 | return GetInteger(min, max, false); 58 | } 59 | 60 | /// 61 | /// Gets a random integer value 62 | /// 63 | /// 64 | public static int GetInteger() 65 | { 66 | return GetInteger(0, int.MaxValue, false); 67 | } 68 | 69 | /// 70 | /// Gets a random integer value in range 71 | /// 72 | /// Return the absolute value. 73 | /// 74 | public static int GetInteger(int min, int max, bool abs) 75 | { 76 | var result = StrongRandom.Next(min, max); 77 | return abs ? Math.Abs(result) : result; 78 | } 79 | 80 | /// 81 | /// Checks for a conditon given a % of chance and interval 82 | /// 83 | /// % chance of success 84 | /// rand 85 | public static bool GetBoolean() 86 | { 87 | return GetBoolean(0.5f); 88 | } 89 | 90 | /// 91 | /// Checks for a conditon given a % of chance and interval 92 | /// 93 | /// % chance of success 94 | /// rand 95 | public static bool GetBoolean(float chance) 96 | { 97 | return GetBoolean(chance, 0); 98 | } 99 | 100 | public static bool GetBoolean(float chance, int checkInterval) 101 | { 102 | if (checkInterval <= 0) 103 | return StrongRandom.Next(0, 1000) < (int) (chance * 1000.0f); 104 | 105 | if (Environment.TickCount - _lastCheckedTime < checkInterval) 106 | return false; 107 | 108 | _lastCheckedTime = Environment.TickCount; 109 | 110 | return StrongRandom.Next(0, 1000) < (int)(chance * 1000.0f); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /ScriptMain/Utility/ShapeTestEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using GTA; 7 | using GTA.Native; 8 | using GTA.Math; 9 | 10 | namespace TornadoScript.ScriptMain.Utility 11 | { 12 | public class ShapeTestResult 13 | { 14 | public bool DidHit { get; private set; } 15 | public int HitEntity { get; private set; } 16 | public Vector3 HitPosition { get; private set; } 17 | public Vector3 HitNormal { get; private set; } 18 | public materials HitMaterial { get; private set; } 19 | 20 | public ShapeTestResult(bool didHit, int hitEntity, Vector3 hitPosition, Vector3 hitNormal, materials hitMaterial) 21 | { 22 | DidHit = didHit; 23 | HitEntity = hitEntity; 24 | HitPosition = hitPosition; 25 | HitNormal = hitNormal; 26 | HitMaterial = hitMaterial; 27 | } 28 | } 29 | 30 | public static class ShapeTestEx 31 | { 32 | public unsafe static ShapeTestResult RunShapeTest(Vector3 start, Vector3 end, Entity ignoreEntity, IntersectOptions options) 33 | { 34 | var shapeTest = Function.Call(Hash._CAST_RAY_POINT_TO_POINT, 35 | start.X, start.Y, start.Z, end.X, end.Y, end.Z, (int)options, ignoreEntity, 7); 36 | 37 | bool didHit; 38 | 39 | int result, handle; 40 | 41 | float[] hitPosition = new float[6], hitNormal = new float[6]; 42 | 43 | int material; 44 | 45 | fixed (float* position = hitPosition) 46 | fixed (float* normal = hitNormal) 47 | { 48 | result = Function.Call((Hash)0x65287525D951F6BE, shapeTest, &didHit, position, normal, &material, &handle); 49 | } 50 | 51 | return new ShapeTestResult(didHit, handle, new Vector3(hitPosition[0], hitPosition[2], hitPosition[4]), 52 | new Vector3(hitNormal[0], hitNormal[2], hitNormal[4]), (materials)material); 53 | } 54 | } 55 | 56 | public enum materials 57 | { 58 | none = -1, 59 | unk = -1775485061, 60 | concrete = 1187676648, 61 | concrete_pothole = 359120722, 62 | concrete_dusty = -1084640111, 63 | tarmac = 282940568, 64 | tarmac_painted = -1301352528, 65 | tarmac_pothole = 1886546517, 66 | rumble_strip = -250168275, 67 | breeze_block = -954112554, 68 | rock = -840216541, 69 | rock_mossy = -124769592, 70 | stone = 765206029, 71 | cobblestone = 576169331, 72 | brick = 1639053622, 73 | marble = 1945073303, 74 | paving_slab = 1907048430, 75 | sandstone_solid = 592446772, 76 | sandstone_brittle = 1913209870, 77 | sand_loose = -1595148316, 78 | sand_compact = 510490462, 79 | sand_wet = 909950165, 80 | sand_track = -1907520769, 81 | sand_underwater = -1136057692, 82 | sand_dry_deep = 509508168, 83 | sand_wet_deep = 1288448767, 84 | ice = -786060715, 85 | ice_tarmac = -1931024423, 86 | snow_loose = -1937569590, 87 | snow_compact = -878560889, 88 | snow_deep = 1619704960, 89 | snow_tarmac = 1550304810, 90 | gravel_small = 951832588, 91 | gravel_large = 2128369009, 92 | gravel_deep = -356706482, 93 | gravel_train_track = 1925605558, 94 | dirt_track = -1885547121, 95 | mud_hard = -1942898710, 96 | mud_pothole = 312396330, 97 | mud_soft = 1635937914, 98 | mud_underwater = -273490167, 99 | mud_deep = 1109728704, 100 | marsh = 223086562, 101 | marsh_deep = 1584636462, 102 | soil = -700658213, 103 | clay_hard = 1144315879, 104 | clay_soft = 560985072, 105 | grass_long = -461750719, 106 | grass = 1333033863, 107 | grass_short = -1286696947, 108 | hay = -1833527165, 109 | bushes = 581794674, 110 | twigs = -913351839, 111 | leaves = -2041329971, 112 | woodchips = -309121453, 113 | tree_bark = -1915425863, 114 | metal_solid_small = -1447280105, 115 | metal_solid_medium = -365631240, 116 | metal_solid_large = 752131025, 117 | metal_hollow_small = 15972667, 118 | metal_hollow_medium = 1849540536, 119 | metal_hollow_large = -583213831, 120 | metal_chainlink_small = 762193613, 121 | metal_chainlink_large = 125958708, 122 | metal_corrugated_iron = 834144982, 123 | metal_grille = -426118011, 124 | metal_railing = 2100727187, 125 | metal_duct = 1761524221, 126 | metal_garage_door = -231260695, 127 | metal_manhole = -754997699, 128 | wood_solid_small = -399872228, 129 | wood_solid_medium = 555004797, 130 | wood_solid_large = 815762359, 131 | wood_solid_polished = 126470059, 132 | wood_floor_dusty = -749452322, 133 | wood_hollow_small = 1993976879, 134 | wood_hollow_medium = -365476163, 135 | wood_hollow_large = -925419289, 136 | wood_chipboard = 1176309403, 137 | wood_old_creaky = 722686013, 138 | wood_high_density = -1742843392, 139 | wood_lattice = 2011204130, 140 | ceramic = -1186320715, 141 | roof_tile = 1755188853, 142 | roof_felt = -1417164731, 143 | fibreglass = 1354180827, 144 | tarpaulin = -642658848, 145 | plastic = -2073312001, 146 | plastic_hollow = 627123000, 147 | plastic_high_density = -1625995479, 148 | plastic_clear = -1859721013, 149 | plastic_hollow_clear = 772722531, 150 | plastic_high_density_clear = -1338473170, 151 | fibreglass_hollow = -766055098, 152 | rubber = -145735917, 153 | rubber_hollow = -783934672, 154 | linoleum = 289630530, 155 | laminate = 1845676458, 156 | carpet_solid = 669292054, 157 | carpet_solid_dusty = 158576196, 158 | carpet_floorboard = -1396484943, 159 | cloth = 122789469, 160 | plaster_solid = -574122433, 161 | plaster_brittle = -251888898, 162 | cardboard_sheet = 236511221, 163 | cardboard_box = -1409054440, 164 | paper = 474149820, 165 | foam = 808719444, 166 | feather_pillow = 1341866303, 167 | polystyrene = -1756927331, 168 | leather = -570470900, 169 | tvscreen = 1429989756, 170 | slatted_blinds = 673696729, 171 | glass_shoot_through = 937503243, 172 | glass_bulletproof = 244521486, 173 | glass_opaque = 1500272081, 174 | perspex = -1619794068, 175 | car_metal = -93061983, 176 | car_plastic = 2137197282, 177 | car_softtop = -979647862, 178 | car_softtop_clear = 2130571536, 179 | car_glass_weak = 1247281098, 180 | car_glass_medium = 602884284, 181 | car_glass_strong = 1070994698, 182 | car_glass_bulletproof = -1721915930, 183 | car_glass_opaque = 513061559, 184 | water = 435688960, 185 | blood = 5236042, 186 | oil = -634481305, 187 | petrol = -1634184340, 188 | fresh_meat = 868733839, 189 | dried_meat = -1445160429, 190 | emissive_glass = 1501078253, 191 | emissive_plastic = 1059629996, 192 | vfx_metal_electrified = -309134265, 193 | vfx_metal_water_tower = 611561919, 194 | vfx_metal_steam = -691277294, 195 | vfx_metal_flame = 332778253, 196 | phys_no_friction = 1666473731, 197 | phys_golf_ball = -1693813558, 198 | phys_tennis_ball = -256704763, 199 | phys_caster = -235302683, 200 | phys_caster_rusty = 2016463089, 201 | phys_car_void = 1345867677, 202 | phys_ped_capsule = -291631035, 203 | phys_electric_fence = -1170043733, 204 | phys_electric_metal = -2013761145, 205 | phys_barbed_wire = -1543323456, 206 | phys_pooltable_surface = 605776921, 207 | phys_pooltable_cushion = 972939963, 208 | phys_pooltable_ball = -748341562, 209 | buttocks = 483400232, 210 | thigh_left = -460535871, 211 | shin_left = 652772852, 212 | foot_left = 1926285543, 213 | thigh_right = -236981255, 214 | shin_right = -446036155, 215 | foot_right = -1369136684, 216 | spine0 = -1922286884, 217 | spine1 = -1140112869, 218 | spine2 = 1457572381, 219 | spine3 = 32752644, 220 | clavicle_left = -1469616465, 221 | upper_arm_left = -510342358, 222 | lower_arm_left = 1045062756, 223 | hand_left = 113101985, 224 | clavicle_right = -1557288998, 225 | upper_arm_right = 1501153539, 226 | lower_arm_right = 1777921590, 227 | hand_right = 2000961972, 228 | neck = 1718294164, 229 | head = -735392753, 230 | animal_default = 286224918, 231 | car_engine = -1916939624, 232 | puddle = 999829011, 233 | concrete_pavement = 2015599386, 234 | brick_pavement = -1147361576, 235 | phys_dynamic_cover_bound = -2047468855, 236 | vfx_wood_beer_barrel = 998201806, 237 | wood_high_friction = -2140087047, 238 | rock_noinst = 127813971, 239 | bushes_noinst = 1441114862, 240 | metal_solid_road_surface = -729112334, 241 | stunt_ramp_surface = -2088174996, 242 | temp_01 = 746881105, 243 | temp_02 = -1977970111, 244 | temp_03 = 1911121241, 245 | temp_04 = 1923995104, 246 | temp_05 = -1393662448, 247 | temp_06 = 1061250033, 248 | temp_07 = -1765523682, 249 | temp_08 = 1343679702, 250 | temp_09 = 1026054937, 251 | temp_10 = 63305994, 252 | temp_11 = 47470226, 253 | temp_12 = 702596674, 254 | temp_13 = -1637485913, 255 | temp_14 = -645955574, 256 | temp_15 = -1583997931, 257 | temp_16 = -1512735273, 258 | temp_17 = 1011960114, 259 | temp_18 = 1354993138, 260 | temp_19 = -801804446, 261 | temp_20 = -2052880405, 262 | temp_21 = -1037756060, 263 | temp_22 = -620388353, 264 | temp_23 = 465002639, 265 | temp_24 = 1963820161, 266 | temp_25 = 1952288305, 267 | temp_26 = -1116253098, 268 | temp_27 = 889255498, 269 | temp_28 = -1179674098, 270 | temp_29 = 1078418101, 271 | temp_30 = 13626292 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /ScriptMain/Utility/StrongRandom.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | 4 | namespace TornadoScript.ScriptMain.Utility 5 | { 6 | /// 7 | /// http://stackoverflow.com/questions/1399039/best-way-to-seed-random-in-singleton 8 | /// 9 | public static class StrongRandom 10 | { 11 | [ThreadStatic] 12 | private static Random _random; 13 | 14 | public static int Next(int inclusiveLowerBound, int inclusiveUpperBound) 15 | { 16 | if (_random == null) 17 | { 18 | var cryptoResult = new byte[4]; 19 | new RNGCryptoServiceProvider().GetBytes(cryptoResult); 20 | 21 | int seed = BitConverter.ToInt32(cryptoResult, 0); 22 | 23 | _random = new Random(seed); 24 | } 25 | 26 | // upper bound of Random.Next is exclusive 27 | int exclusiveUpperBound = inclusiveUpperBound + 1; 28 | return _random.Next(inclusiveLowerBound, exclusiveUpperBound); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ScriptMain/Utility/Win32Native.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | <<<<<<< HEAD 3 | using System.Runtime.ConstrainedExecution; 4 | using System.Runtime.InteropServices; 5 | using System.Security; 6 | ======= 7 | using System.Runtime.InteropServices; 8 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 9 | using System.Text; 10 | using System.Windows.Input; 11 | 12 | namespace TornadoScript.ScriptMain.Utility 13 | { 14 | <<<<<<< HEAD 15 | [Flags] 16 | public enum ThreadAccess : int 17 | { 18 | TERMINATE = (0x0001), 19 | SUSPEND_RESUME = (0x0002), 20 | GET_CONTEXT = (0x0008), 21 | SET_CONTEXT = (0x0010), 22 | SET_INFORMATION = (0x0020), 23 | QUERY_INFORMATION = (0x0040), 24 | SET_THREAD_TOKEN = (0x0080), 25 | IMPERSONATE = (0x0100), 26 | DIRECT_IMPERSONATION = (0x0200) 27 | } 28 | 29 | [StructLayout(LayoutKind.Sequential)] 30 | public struct CLIENT_ID 31 | { 32 | public uint UniqueProcess; // original: PVOID 33 | public uint UniqueThread; // original: PVOID 34 | } 35 | 36 | [StructLayout(LayoutKind.Explicit, Size = 0x30)] 37 | public struct THREAD_BASIC_INFORMATION 38 | { 39 | [FieldOffset(0x0000)] public int ExitStatus; 40 | [FieldOffset(0x0008)] public IntPtr TebBaseAddress; 41 | } 42 | 43 | // http://msdn.moonsols.com/win7rtm_x64/TEB.html 44 | [StructLayout(LayoutKind.Explicit, Size = 0x1818)] 45 | public struct TEB 46 | { 47 | [FieldOffset(0x0058)] public IntPtr ThreadLocalStoragePointer; 48 | } 49 | 50 | public enum PlaySoundFlags : uint 51 | { 52 | SND_SYNC = 0x0, // play synchronously (default) 53 | SND_ASYNC = 0x1, // play asynchronously 54 | SND_NODEFAULT = 0x2, // silence (!default) if sound not found 55 | SND_MEMORY = 0x4, // pszSound points to a memory file 56 | SND_LOOP = 0x8, // loop the sound until next sndPlaySound 57 | SND_NOSTOP = 0x10, // don't stop any currently playing sound 58 | SND_NOWAIT = 0x2000, // don't wait if the driver is busy 59 | SND_ALIAS = 0x10000, // name is a registry alias 60 | SND_ALIAS_ID = 0x110000, // alias is a predefined ID 61 | SND_FILENAME = 0x20000, // name is file name 62 | SND_RESOURCE = 0x40004, // name is resource name or atom 63 | }; 64 | 65 | public struct MODULEINFO 66 | { 67 | public IntPtr LpBaseOfDll; 68 | public uint SizeOfImage; 69 | public IntPtr EntryPoint; 70 | } 71 | 72 | public enum ThreadInfoClass : int 73 | { 74 | ThreadQuerySetWin32StartAddress = 9 75 | } 76 | 77 | public sealed unsafe class Win32Native 78 | { 79 | public delegate int NtQueryInformationThreadDelegate(IntPtr threadHandle, uint threadInformationClass, THREAD_BASIC_INFORMATION* outThreadInformation, ulong threadInformationLength, ulong* returnLength); 80 | 81 | public static NtQueryInformationThreadDelegate NtQueryInformationThread { get; } 82 | 83 | static Win32Native() 84 | { 85 | IntPtr ntdllHandle = GetModuleHandle("ntdll.dll"); 86 | NtQueryInformationThread = Marshal.GetDelegateForFunctionPointer(GetProcAddress(ntdllHandle, "NtQueryInformationThread")); 87 | } 88 | 89 | [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] 90 | public static extern IntPtr GetProcAddress(IntPtr moduleHandle, string procName); 91 | 92 | [DllImport("kernel32.dll", SetLastError = true)] 93 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 94 | [SuppressUnmanagedCodeSecurity] 95 | [return: MarshalAs(UnmanagedType.Bool)] 96 | public static extern bool CloseHandle(IntPtr hObject); 97 | 98 | [DllImport("kernel32.dll", SetLastError = true)] 99 | public static extern IntPtr OpenThread(ThreadAccess desiredAccess, bool inheritHandle, int threadId); 100 | 101 | [DllImport("kernel32.dll")] 102 | public static extern int GetCurrentThreadId(); 103 | 104 | [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] 105 | public static extern IntPtr GetForegroundWindow(); 106 | 107 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 108 | public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId); 109 | 110 | ======= 111 | public sealed class Win32Native 112 | { 113 | public struct MODULEINFO 114 | { 115 | public IntPtr LpBaseOfDll; 116 | public uint SizeOfImage; 117 | public IntPtr EntryPoint; 118 | } 119 | 120 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 121 | [DllImport("kernel32.dll")] 122 | public static extern IntPtr GetCurrentProcess(); 123 | 124 | [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 125 | public static extern IntPtr GetModuleHandle(string lpModuleName); 126 | 127 | [DllImport("psapi.dll", SetLastError = true)] 128 | public static extern bool GetModuleInformation(IntPtr hProcess, IntPtr hModule, out MODULEINFO lpmodinfo, uint cb); 129 | 130 | [DllImport("user32.dll", EntryPoint = "BlockInput")] 131 | [return: MarshalAs(UnmanagedType.Bool)] 132 | public static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt); 133 | 134 | public enum MapType : uint 135 | { 136 | MapvkVkToVsc = 0x0, 137 | MapvkVscToVk = 0x1, 138 | MapvkVkToChar = 0x2, 139 | MapvkVscToVkEx = 0x3, 140 | } 141 | 142 | [DllImport("user32.dll")] 143 | public static extern int ToUnicode( 144 | uint wVirtKey, 145 | uint wScanCode, 146 | byte[] lpKeyState, 147 | [Out, MarshalAs(UnmanagedType.LPWStr, SizeParamIndex = 4)] 148 | StringBuilder pwszBuff, 149 | int cchBuff, 150 | uint wFlags); 151 | 152 | [DllImport("user32.dll")] 153 | public static extern bool GetKeyboardState(byte[] lpKeyState); 154 | 155 | [DllImport("user32.dll")] 156 | public static extern uint MapVirtualKey(uint uCode, MapType uMapType); 157 | 158 | public static char GetCharFromKey(Key key, bool shift) 159 | { 160 | char ch = ' '; 161 | 162 | int virtualKey = KeyInterop.VirtualKeyFromKey(key); 163 | byte[] keyboardState = new byte[256]; 164 | 165 | if (shift) 166 | keyboardState[0x10] = 0x80; 167 | GetKeyboardState(keyboardState); 168 | 169 | uint scanCode = MapVirtualKey((uint)virtualKey, MapType.MapvkVkToVsc); 170 | StringBuilder stringBuilder = new StringBuilder(2); 171 | 172 | int result = ToUnicode((uint)virtualKey, scanCode, keyboardState, stringBuilder, stringBuilder.Capacity, 0); 173 | switch (result) 174 | { 175 | case -1: 176 | break; 177 | case 0: 178 | break; 179 | case 1: 180 | { 181 | ch = stringBuilder[0]; 182 | break; 183 | } 184 | default: 185 | { 186 | ch = stringBuilder[0]; 187 | break; 188 | } 189 | } 190 | return ch; 191 | } 192 | <<<<<<< HEAD 193 | 194 | [DllImport("winmm.dll", SetLastError = true)] 195 | public static extern int PlaySound( 196 | string szSound, 197 | IntPtr hModule, 198 | int flags); 199 | ======= 200 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 201 | } 202 | } 203 | 204 | 205 | -------------------------------------------------------------------------------- /ScriptMain/WinHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Diagnostics; 7 | using TornadoScript.ScriptMain.Utility; 8 | using System.Runtime.InteropServices; 9 | namespace TornadoScript.ScriptMain 10 | { 11 | /// 12 | /// thanks alex 13 | /// https://github.com/alexguirre/Spotlight/blob/master/Source/Core/Memory/WinFunctions.cs 14 | /// 15 | public static unsafe class WinHelper 16 | { 17 | private static int mainThreadId = -1; 18 | 19 | private static Dictionary threadHandleDictionary = new Dictionary(); 20 | 21 | private static Dictionary threadInformationDictionary = new Dictionary(); 22 | 23 | public static int GetProcessMainThreadId() 24 | { 25 | if (mainThreadId == -1) 26 | { 27 | long lowestStartTime = long.MaxValue; 28 | ProcessThread lowestStartTimeThread = null; 29 | foreach (ProcessThread thread in Process.GetCurrentProcess().Threads) 30 | { 31 | long startTime = thread.StartTime.Ticks; 32 | if (startTime < lowestStartTime) 33 | { 34 | lowestStartTime = startTime; 35 | lowestStartTimeThread = thread; 36 | } 37 | } 38 | 39 | mainThreadId = lowestStartTimeThread == null ? -1 : lowestStartTimeThread.Id; 40 | } 41 | 42 | return mainThreadId; 43 | } 44 | 45 | public static void CopyTlsValues(IntPtr sourceThreadHandle, IntPtr targetThreadHandle, params int[] valuesOffsets) 46 | { 47 | THREAD_BASIC_INFORMATION sourceThreadInfo, targetThreadInfo; 48 | 49 | if (!threadInformationDictionary.TryGetValue(sourceThreadHandle, out sourceThreadInfo)) 50 | { 51 | sourceThreadInfo = new THREAD_BASIC_INFORMATION(); 52 | int sourceStatus = Win32Native.NtQueryInformationThread(sourceThreadHandle, 0, &sourceThreadInfo, (ulong)sizeof(THREAD_BASIC_INFORMATION), null); 53 | if (sourceStatus != 0) 54 | { 55 | ScriptCore.Logger.Log($"Source Thread Invalid Query Status: {sourceStatus}"); 56 | return; 57 | } 58 | 59 | threadInformationDictionary[sourceThreadHandle] = sourceThreadInfo; 60 | } 61 | 62 | if (!threadInformationDictionary.TryGetValue(targetThreadHandle, out targetThreadInfo)) 63 | { 64 | targetThreadInfo = new THREAD_BASIC_INFORMATION(); 65 | int sourceStatus = Win32Native.NtQueryInformationThread(targetThreadHandle, 0, &targetThreadInfo, (ulong)sizeof(THREAD_BASIC_INFORMATION), null); 66 | if (sourceStatus != 0) 67 | { 68 | ScriptCore.Logger.Log($"Source Thread Invalid Query Status: {sourceStatus}"); 69 | return; 70 | } 71 | 72 | threadInformationDictionary[targetThreadHandle] = targetThreadInfo; 73 | } 74 | 75 | TEB* sourceTeb = (TEB*)sourceThreadInfo.TebBaseAddress; 76 | TEB* targetTeb = (TEB*)targetThreadInfo.TebBaseAddress; 77 | 78 | foreach (int offset in valuesOffsets) 79 | { 80 | *(long*)(*(byte**)(targetTeb->ThreadLocalStoragePointer) + offset) = *(long*)(*(byte**)(sourceTeb->ThreadLocalStoragePointer) + offset); 81 | } 82 | } 83 | 84 | public static void CopyTlsValues(int sourceThreadId, int targetThreadId, params int[] valuesOffsets) 85 | { 86 | IntPtr sourceThreadHandle = IntPtr.Zero, targetThreadHandle = IntPtr.Zero; 87 | 88 | if (!threadHandleDictionary.TryGetValue(sourceThreadId, out sourceThreadHandle)) 89 | { 90 | try 91 | { 92 | sourceThreadHandle = Win32Native.OpenThread(ThreadAccess.QUERY_INFORMATION, false, sourceThreadId); 93 | 94 | threadHandleDictionary[sourceThreadId] = sourceThreadHandle; 95 | } 96 | catch { } 97 | } 98 | 99 | if (!threadHandleDictionary.TryGetValue(targetThreadId, out targetThreadHandle)) 100 | { 101 | try 102 | { 103 | targetThreadHandle = Win32Native.OpenThread(ThreadAccess.QUERY_INFORMATION, false, targetThreadId); 104 | 105 | threadHandleDictionary[targetThreadId] = targetThreadHandle; 106 | } 107 | catch { } 108 | } 109 | 110 | CopyTlsValues(sourceThreadHandle, targetThreadHandle, valuesOffsets); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /TornadoScript.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {61ED2E12-E7E2-42ED-8CCB-8D1DFCCC5FD9} 8 | Library 9 | Properties 10 | TornadoScript 11 | TornadoScript 12 | <<<<<<< HEAD 13 | v4.6.1 14 | ======= 15 | v4.7.1 16 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 17 | 512 18 | 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | true 37 | 38 | 39 | true 40 | bin\x64\Debug\ 41 | DEBUG;TRACE 42 | full 43 | x64 44 | prompt 45 | MinimumRecommendedRules.ruleset 46 | true 47 | 7.1 48 | false 49 | 50 | 51 | bin\x64\Release\ 52 | TRACE 53 | true 54 | pdbonly 55 | x64 56 | prompt 57 | MinimumRecommendedRules.ruleset 58 | true 59 | 7.2 60 | 61 | 62 | <<<<<<< HEAD 63 | 64 | False 65 | E:\Program Files\Rockstar Games\Grand Theft Auto V\scripts\NAudio.dll 66 | 67 | 68 | False 69 | ..\..\..\..\Desktop\Projects\ScriptHookVDotNet2.dll 70 | ======= 71 | 72 | ..\..\..\..\..\Desktop\ScriptHookVDotNet.dll 73 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | True 87 | True 88 | Resources.resx 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | <<<<<<< HEAD 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | ======= 127 | 128 | 129 | 130 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 131 | 132 | 133 | 134 | 135 | 136 | 137 | <<<<<<< HEAD 138 | 139 | 140 | ======= 141 | 142 | 143 | 144 | 145 | True 146 | True 147 | Resources.resx 148 | 149 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | <<<<<<< HEAD 159 | 160 | ======= 161 | >>>>>>> 46660d5b9e2a5942c1c3eb32c40357e5d9abfc48 162 | 163 | 164 | 165 | ResXFileCodeGenerator 166 | Resources.Designer.cs 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | copy "$(TargetPath)" "E:\Program Files\Rockstar Games\Grand Theft Auto V\scripts\$(ProjectName).dll" 176 | 177 | 184 | -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | --------------------------------------------------------------------------------