Implementing Wanted Level Script - KimonoBoy/SHVDNTutorial-NucleiLite GitHub Wiki

We've implemented two of our scripts so far - the Invincible as a CheckBox and the Fix Player as a regular Item. Now we want to create something a bit different, something called a NativeListItem - this is an item that can hold many different values to select from.

The Code

Follow the instructions below

  1. Copy and Paste the following
using System;
using System.Windows.Forms;
using GTA;
using GTA.UI;
using LemonUI;
using LemonUI.Menus;

namespace NucleiLite
{
    public class Main : Script
    {
        ObjectPool menuPool = new ObjectPool();
        NativeMenu mainMenu = new NativeMenu("NucleiLite", "Main Menu");
        NativeMenu playerMenu = new NativeMenu("NucleiLite", "Player Menu");
        NativeMenu vehicleSpawnerMenu = new NativeMenu("NucleiLite", "Vehicle Spawner Menu");
        NativeMenu weaponsMenu = new NativeMenu("NucleiLite", "Weapons Menu");

        public Main()
        {
            CreateMainMenu();
            CreatePlayerMenu();
            CreateVehicleSpawnerMenu();
            CreateWeaponsMenu();

            AddMenusToPool();

            KeyDown += OnKeyDown;
            Tick += OnTick;
        }

        private void CreateMainMenu()
        {
            mainMenu.AddSubMenu(playerMenu);
            mainMenu.AddSubMenu(vehicleSpawnerMenu);
            mainMenu.AddSubMenu(weaponsMenu);
        }

        private void CreatePlayerMenu()
        {
            // Fix Player
            NativeItem itemFixPlayer = new NativeItem("Fix Player", "Restores Player's Health and Armor");
            itemFixPlayer.Activated += (sender, args) =>
            {
                Game.Player.Character.Health = Game.Player.Character.MaxHealth;
                Game.Player.Character.Armor = Game.Player.MaxArmor;
                Notification.Show("Health and Armor Restored!");
            };
            playerMenu.Add(itemFixPlayer);

            // Invincible
            NativeCheckboxItem checkBoxInvincible = new NativeCheckboxItem("Invincible", "Your character can no longer die.");
            checkBoxInvincible.CheckboxChanged += (sender, args) =>
            {
                Game.Player.Character.IsInvincible = checkBoxInvincible.Checked;
                Notification.Show($"Invincible: {Game.Player.Character.IsInvincible}");
            };
            playerMenu.Add(checkBoxInvincible);

            // Wanted Level
            NativeListItem<int> listItemWantedLevel = new NativeListItem<int>("Wanted Level", "Adjust Player's Wanted Level.", 0, 1, 2, 3, 4, 5);
            listItemWantedLevel.ItemChanged += (sender, args) =>
            {
                Game.Player.WantedLevel = args.Object;
            };
            playerMenu.Add(listItemWantedLevel);
        }

        private void CreateWeaponsMenu()
        {
        }

        private void CreateVehicleSpawnerMenu()
        {
        }

        private void AddMenusToPool()
        {
            menuPool.Add(mainMenu);
            menuPool.Add(playerMenu);
            menuPool.Add(vehicleSpawnerMenu);
            menuPool.Add(weaponsMenu);
        }

        private void OnTick(object sender, EventArgs e)
        {
            menuPool.Process();
        }

        private void OnKeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.F5)
            {
                mainMenu.Visible = !mainMenu.Visible;                
            }
        }
    }
}
  1. Build, Reload() and Test the script by changing the Wanted Level (Go Left or Right when the Wanted Level item is selected)

image

Code Breakdown - Simplified

            // Wanted Level
            NativeListItem<int> listItemWantedLevel = new NativeListItem<int>("Wanted Level", "Adjust Player's Wanted Level.", 0, 1, 2, 3, 4, 5);
            listItemWantedLevel.ItemChanged += (sender, args) =>
            {
                Game.Player.WantedLevel = args.Object;
            };
            playerMenu.Add(listItemWantedLevel);

The purpose of the NativeListItem is to select between a bunch of different values. The List Item can hold any value type, from strings to ints to floats to enums, you can even specify the values to be dynamic and have the List Item determine it's value type at runtime - which basically means that the value type is determined when the application is running and not when building it.

To define a NativeListItem we have to specify what type of value it can hold, so when we create it we do the following

NativeListItem<int>

Saying: Create a NativeListItem that can store int values, here is an example of a NativeListItem that can hold string values

            NativeListItem<string> listItemHairColor = new NativeListItem<string>("Hair Color", "Change Hair Color", "Black", "White", "Blue", "Green");
            listItemHairColor.ItemChanged += (sender, args) =>
            {
                Notification.Show($"You've selected the Hair Color: {args.Object}");
            };
            playerMenu.Add(listItemHairColor);

image


listItemWantedLevel.ItemChanged += (sender, args) =>

The NativeListItem has an event defined called ItemChanged which is an event that is triggered whenever the value of the ListItem changes


Game.Player.WantedLevel = args.Object;

Previously when we worked with NativeItem and NativeCheckboxItem we didn't use the args parameter we passed in, this time around we use it to get the value of the current selected object in our NativeListItem. If the NativeListItem is of type int the .Object will return an int value, if the type is string the .Object will return a string value, etc. The above code would be the equivalent of doing the following

            // Wanted Level
            NativeListItem<int> listItemWantedLevel = new NativeListItem<int>("Wanted Level", "Adjust Player's Wanted Level.", 0, 1, 2, 3, 4, 5);
            listItemWantedLevel.ItemChanged += (sender, args) =>
            {
                Game.Player.WantedLevel = listItemWantedLevel.SelectedItem;
            };
            playerMenu.Add(listItemWantedLevel);
Game.Player.WantedLevel = listItemWantedLevel.SelectedItem;

That is, setting the WantedLevel equal to that of the SelectedItem of the List Item


playerMenu.Add(listItemWantedLevel);

Like we always do - adds the item to the menu

Code Breakdown - Advanced

In this section we'll cover the meaning of the <int> in a more detailed way, actually the NativeListItem definition in LemonUI looks like the following

public class NativeListItem<T>

Where T is the Type we pass in. This is called Generics - let me explain:

The concept of Generics in C# involves defining a class or method** with placeholders for data types, known as type parameters. This approach allows for the creation of reusable code that can function with various data types, without duplicating code for each specific type. This not only saves time but also reduces the likelihood of errors in the code.

For example, if a NativeListItem was defined for a specific data type, such as int or string, it would require creating a definition for each data type. However, by defining it as a Generic type with T as the placeholder, the logic only needs to be written once, and the developer using the code can define the type.

Generics also provide type-safety, which means that the compiler checks that the code is being used with the correct data type. This helps identify errors at compile-time, which is more efficient than dealing with potential bugs during runtime.

You can learn much more about Generics at Generics


Game.Player.WantedLevel = args.Object;

Let's inspect the documentation of the NativeListItem.ItemChangedEventArgs to better understand what the args does in this context

namespace LemonUI.Menus
{
    //
    // Summary:
    //     Represents the change of the selection of an item.
    //
    // Type parameters:
    //   T:
    //     The type of object that got changed.
    public class ItemChangedEventArgs<T>
    {
        //
        // Summary:
        //     The new object.
        public T Object { get; set; }

        //
        // Summary:
        //     The index of the object.
        public int Index { get; }

        //
        // Summary:
        //     The direction of the Item Changed event.
        public Direction Direction { get; }

        internal ItemChangedEventArgs(T obj, int index, Direction direction)
        {
            Object = obj;
            Index = index;
            Direction = direction;
        }
    }
}

The class definition contains three public properties and a constructor with three parameters.

public T Object { get; set; }

This public property represents the new object that got changed and is of type T.

public int Index { get; }

This public property represents the index of the object and is of type int.

Indexing in C# refers to the process of accessing elements in an array, list, or other collection types using a specific position or index. An index is a numeric value that represents the position of an element in a collection.

Arrays, for example, are a collection of elements of the same data type, stored in contiguous memory locations, and accessed using an index. The index of the first element in an array is zero, and the index of the last element is the length of the array minus one. To access an element in an array, you can use the square bracket notation, which specifies the index of the element you want to access.

Lists and other collection types in C# also support indexing. However, not all collections types support direct indexing, and some require more complex methods for accessing elements. For example, a dictionary uses a key-value pair to store elements, and elements are accessed using the key instead of an index.

Indexing is a powerful feature of C# because it allows for efficient and straightforward access to elements in a collection. However, it's important to note that indexing operations can throw exceptions if the index is out of range or invalid, so it's crucial to check the validity of the index before using it to access an element in a collection.

You can learn more about Data Ranges and Indexes at Data Ranges and Indexes and more about Indexing at Indexing

public Direction Direction { get; }

This is the direction of which the item changed - left or right (or unknown)

        internal ItemChangedEventArgs(T obj, int index, Direction direction)
        {
            Object = obj;
            Index = index;
            Direction = direction;
        }

This constructor takes in three parameters/arguments - a new object of type T, an index of type int, and a direction of type Direction. The constructor then initializes the three properties accordingly.

To Summarize: ItemChangedEventArgs class provides a template for creating an object that represents the change of selection of an item. By using a type parameter, this class can work with any data type specified by the user, making it versatile and reusable.

Conclusion

In this section we've Created a Wanted Level-implementation to our Menu which allows us to change the Player's Current Wanted Level to whatever listItemWantedLevel-value is set to. We've also talked about indexing which we'll be using later.

Previous - Implementing Invincible Script
Next - Implementing Super Jump Script

⚠️ **GitHub.com Fallback** ⚠️