diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1947600 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,382 @@ +name: CI/CD + +on: + push: + branches: + - '**' + pull_request: + +permissions: + contents: write + +# Cancel any in-progress run on the same branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +jobs: + # ============================================================ + # VERSION — calculates the shared version number once + # ============================================================ + version: + name: Calculate Version + runs-on: windows-latest + + outputs: + semver: ${{ steps.version.outputs.semver }} + assembly_ver: ${{ steps.version.outputs.assembly_ver }} + zip_prefix: ${{ steps.version.outputs.zip_prefix }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install GitVersion + if: github.ref == 'refs/heads/master' + uses: gittools/actions/gitversion/setup@v3.0.0 + with: + versionSpec: '5.x' + + - name: Execute GitVersion + if: github.ref == 'refs/heads/master' + id: gitversion + uses: gittools/actions/gitversion/execute@v3.0.0 + + - name: Set version outputs + id: version + shell: bash + run: | + if [ "$GITHUB_REF" = "refs/heads/master" ]; then + SEMVER="${{ steps.gitversion.outputs.semVer }}" + ASSEMBLY_VER="${{ steps.gitversion.outputs.assemblySemVer }}" + else + SEMVER="" + ASSEMBLY_VER="" + fi + ZIP_PREFIX="MenuAPI-${SEMVER:-dev-${{ github.run_number }}}" + echo "semver=$SEMVER" >> "$GITHUB_OUTPUT" + echo "assembly_ver=$ASSEMBLY_VER" >> "$GITHUB_OUTPUT" + echo "zip_prefix=$ZIP_PREFIX" >> "$GITHUB_OUTPUT" + + # ============================================================ + # BUILD FIVEM + # Restores only the FiveM configuration to avoid CitizenFX + # NuGet package conflicting with the RedM local DLL. + # ============================================================ + build-fivem: + name: Build FiveM + needs: version + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Restore NuGet (FiveM only) + shell: bash + run: dotnet restore "MenuAPI/MenuAPI.csproj" "-p:Configuration=Release FiveM" + + - name: Build FiveM (versioned) + if: github.ref == 'refs/heads/master' + shell: bash + run: | + dotnet build MenuAPI/MenuAPI.csproj -c "Release FiveM" --no-restore \ + -p:Version=${{ needs.version.outputs.semver }} \ + -p:AssemblyVersion=${{ needs.version.outputs.assembly_ver }} \ + -p:FileVersion=${{ needs.version.outputs.assembly_ver }} + + - name: Build FiveM + if: github.ref != 'refs/heads/master' + run: dotnet build MenuAPI/MenuAPI.csproj -c "Release FiveM" --no-restore + + - name: Package FiveM zip + shell: pwsh + run: | + $outDir = 'MenuAPI/bin/Release FiveM/net452' + Copy-Item README.md "$outDir/README.md" -Force + Compress-Archive -Path "$outDir/*" -DestinationPath "${{ needs.version.outputs.zip_prefix }}-FiveM.zip" -Force + + - name: Upload FiveM zip + uses: actions/upload-artifact@v4 + with: + name: zip-fivem + path: ${{ needs.version.outputs.zip_prefix }}-FiveM.zip + + - name: Pack NuGet FiveM + if: github.ref == 'refs/heads/master' + run: >- + dotnet pack MenuAPI/MenuAPI.csproj + -c "Release FiveM" + --no-restore + -p:PackageVersion=${{ needs.version.outputs.semver }} + -p:DefineConstants=FIVEM + -p:PackageId=MenuAPI.FiveM + -o nupkgs + + - name: Upload NuGet FiveM + if: github.ref == 'refs/heads/master' + uses: actions/upload-artifact@v4 + with: + name: nuget-fivem + path: nupkgs/*.nupkg + + # ============================================================ + # BUILD REDM + # Restores only the RedM configuration so the local + # dependencies/RedM/CitizenFX.Core.dll is used, not the + # FiveM NuGet package. + # ============================================================ + build-redm: + name: Build RedM + needs: version + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Restore NuGet (RedM only) + shell: bash + run: dotnet restore "MenuAPI/MenuAPI.csproj" "-p:Configuration=Release RedM" + + - name: Build RedM (versioned) + if: github.ref == 'refs/heads/master' + shell: bash + run: | + dotnet build MenuAPI/MenuAPI.csproj -c "Release RedM" --no-restore \ + -p:Version=${{ needs.version.outputs.semver }} \ + -p:AssemblyVersion=${{ needs.version.outputs.assembly_ver }} \ + -p:FileVersion=${{ needs.version.outputs.assembly_ver }} + + - name: Build RedM + if: github.ref != 'refs/heads/master' + run: dotnet build MenuAPI/MenuAPI.csproj -c "Release RedM" --no-restore + + - name: Package RedM zip + shell: pwsh + run: | + $outDir = 'MenuAPI/bin/Release RedM/net452' + Copy-Item README.md "$outDir/README.md" -Force + Compress-Archive -Path "$outDir/*" -DestinationPath "${{ needs.version.outputs.zip_prefix }}-RedM.zip" -Force + + - name: Upload RedM zip + uses: actions/upload-artifact@v4 + with: + name: zip-redm + path: ${{ needs.version.outputs.zip_prefix }}-RedM.zip + + - name: Pack NuGet RedM + if: github.ref == 'refs/heads/master' + run: >- + dotnet pack MenuAPI/MenuAPI.csproj + -c "Release RedM" + --no-restore + -p:PackageVersion=${{ needs.version.outputs.semver }} + -p:DefineConstants=REDM + -p:PackageId=MenuAPI.RedM + -o nupkgs + + - name: Upload NuGet RedM + if: github.ref == 'refs/heads/master' + uses: actions/upload-artifact@v4 + with: + name: nuget-redm + path: nupkgs/*.nupkg + + # ============================================================ + # RELEASE — master only, after both builds succeed + # ============================================================ + release: + name: Release + needs: [version, build-fivem, build-redm] + if: github.ref == 'refs/heads/master' + runs-on: windows-latest + + outputs: + release_url: ${{ steps.create_release.outputs.release_url }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download FiveM zip + uses: actions/download-artifact@v4 + with: + name: zip-fivem + path: release-assets + + - name: Download RedM zip + uses: actions/download-artifact@v4 + with: + name: zip-redm + path: release-assets + + - name: Download NuGet FiveM + uses: actions/download-artifact@v4 + with: + name: nuget-fivem + path: release-assets + + - name: Download NuGet RedM + uses: actions/download-artifact@v4 + with: + name: nuget-redm + path: release-assets + + # Generate changelog BEFORE tagging so the range is correct + - name: Generate changelog + shell: bash + run: | + LAST_TAG=$(git tag --sort=-version:refname | head -n 1) + if [ -n "$LAST_TAG" ]; then + git log "$LAST_TAG..HEAD" --pretty=format:'- `%h` (%an) %s' > changelog.txt + else + git log --pretty=format:'- `%h` (%an) %s' > changelog.txt + fi + echo "Last tag used for changelog: ${LAST_TAG:-}" + cat changelog.txt + + - name: Create and push git tag + shell: bash + run: | + VERSION="${{ needs.version.outputs.semver }}" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}" + git tag "v$VERSION" + git push origin "v$VERSION" + + - name: Create GitHub Release + id: create_release + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ needs.version.outputs.semver }}" + { + echo "MenuAPI release v${VERSION}. Download the **FiveM** or **RedM** versions below." + echo "" + echo "## Changes" + cat changelog.txt + echo "" + echo "For more info, check the [docs](https://docs.vespura.com/mapi)." + } > release_notes.md + + RELEASE_URL=$(gh release create "v$VERSION" \ + --title "MenuAPI v$VERSION" \ + --notes-file release_notes.md \ + "release-assets/MenuAPI-$VERSION-FiveM.zip" \ + "release-assets/MenuAPI-$VERSION-RedM.zip") + + echo "release_url=$RELEASE_URL" >> "$GITHUB_OUTPUT" + + - name: Publish NuGet — FiveM + run: >- + dotnet nuget push + "release-assets/MenuAPI.FiveM.${{ needs.version.outputs.semver }}.nupkg" + --api-key ${{ secrets.NUGET_API_KEY }} + --source https://api.nuget.org/v3/index.json + --skip-duplicate + + - name: Publish NuGet — RedM + run: >- + dotnet nuget push + "release-assets/MenuAPI.RedM.${{ needs.version.outputs.semver }}.nupkg" + --api-key ${{ secrets.NUGET_API_KEY }} + --source https://api.nuget.org/v3/index.json + --skip-duplicate + + # ============================================================ + # DISCORD NOTIFICATION — always runs + # ============================================================ + notify: + name: Discord Notification + needs: [version, build-fivem, build-redm, release] + if: always() + runs-on: ubuntu-latest + + steps: + - name: Send Discord notification + shell: bash + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: | + if [ -z "$DISCORD_WEBHOOK_URL" ]; then + echo "DISCORD_WEBHOOK_URL not configured, skipping." + exit 0 + fi + + FIVEM_RESULT="${{ needs.build-fivem.result }}" + REDM_RESULT="${{ needs.build-redm.result }}" + RELEASE_RESULT="${{ needs.release.result }}" + VERSION="${{ needs.version.outputs.semver }}" + + # ── Status colour & label ────────────────────────────────────────── + if [ "$FIVEM_RESULT" = "cancelled" ] || [ "$REDM_RESULT" = "cancelled" ] || [ "$RELEASE_RESULT" = "cancelled" ]; then + COLOR=16776960 + STATUS_TEXT="Build cancelled." + elif [ "$FIVEM_RESULT" = "success" ] && [ "$REDM_RESULT" = "success" ] && \ + { [ "$RELEASE_RESULT" = "success" ] || [ "$RELEASE_RESULT" = "skipped" ]; }; then + COLOR=4502298 + STATUS_TEXT="Build passed!" + else + COLOR=13632027 + STATUS_TEXT="Build FAILED!" + fi + + # ── Link field ───────────────────────────────────────────────────── + RELEASE_URL="${{ needs.release.outputs.release_url }}" + if [ -n "$RELEASE_URL" ] && [ "$RELEASE_RESULT" = "success" ]; then + LINK_NAME="GitHub Release" + LINK_URL="$RELEASE_URL" + else + LINK_NAME="Build Run" + LINK_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + fi + + DISPLAY_VERSION="${VERSION:-dev-${{ github.run_number }}}" + SHA="${{ github.sha }}" + SHORT_SHA="${SHA:0:7}" + + PAYLOAD=$(jq -n \ + --arg title "MenuAPI (v${DISPLAY_VERSION})" \ + --arg desc "$STATUS_TEXT" \ + --argjson color "$COLOR" \ + --arg actor "${{ github.actor }}" \ + --arg actor_url "https://github.com/${{ github.actor }}" \ + --arg branch "${{ github.ref_name }}" \ + --arg link_name "$LINK_NAME" \ + --arg link_url "$LINK_URL" \ + --arg short_sha "$SHORT_SHA" \ + --arg commit_url "https://github.com/${{ github.repository }}/commit/${{ github.sha }}" \ + --arg commit_msg "${{ github.event.head_commit.message }}" \ + '{ + embeds: [{ + title: $title, + description: $desc, + color: $color, + author: { + name: ("Committed by " + $actor), + url: $actor_url + }, + fields: [ + { name: "Branch", value: $branch, inline: true }, + { name: $link_name, value: ("[Link](" + $link_url + ")"), inline: true }, + { name: "Commit", value: ("[`" + $short_sha + "`](" + $commit_url + ") — " + $commit_msg), inline: false } + ] + }] + }') + + curl -fsSL -H "Content-Type: application/json" -X POST -d "$PAYLOAD" "$DISCORD_WEBHOOK_URL" diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..c7391de --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,20 @@ +# GitVersion configuration +# Docs: https://gitversion.net/docs/reference/configuration +# +# Mode: Mainline — every commit to master auto-increments the patch version. +# Override the increment for a specific commit by including one of these +# anywhere in the commit message: +# +semver: major → bumps major (e.g. 1.2.3 → 2.0.0) +# +semver: minor → bumps minor (e.g. 1.2.3 → 1.3.0) +# +semver: patch → bumps patch (e.g. 1.2.3 → 1.2.4) ← default +# +semver: none → no bump on this commit + +assembly-versioning-scheme: MajorMinorPatch +assembly-file-versioning-scheme: MajorMinorPatch +mode: Mainline + +branches: + master: + regex: '^master$' + increment: Patch + is-mainline: true diff --git a/MenuAPI/Menu.cs b/MenuAPI/Menu.cs index db62220..af4e3a6 100644 --- a/MenuAPI/Menu.cs +++ b/MenuAPI/Menu.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using CitizenFX.Core; using static CitizenFX.Core.Native.API; @@ -188,7 +187,7 @@ public class Menu /// The in which this event occurred. /// The that was selected. /// The of this . - protected virtual void ItemSelectedEvent(MenuItem menuItem, int itemIndex) + internal virtual void ItemSelectedEvent(MenuItem menuItem, int itemIndex) { OnItemSelect?.Invoke(this, menuItem, itemIndex); } @@ -200,7 +199,7 @@ protected virtual void ItemSelectedEvent(MenuItem menuItem, int itemIndex) /// The that was toggled. /// The of this . /// The new state of this . - protected virtual void CheckboxChangedEvent(MenuCheckboxItem menuItem, int itemIndex, bool _checked) + internal virtual void CheckboxChangedEvent(MenuCheckboxItem menuItem, int itemIndex, bool _checked) { OnCheckboxChange?.Invoke(this, menuItem, itemIndex, _checked); } @@ -212,7 +211,7 @@ protected virtual void CheckboxChangedEvent(MenuCheckboxItem menuItem, int itemI /// The that was selected. /// The of the . /// The of the in the . - protected virtual void ListItemSelectEvent(Menu menu, MenuListItem listItem, int selectedIndex, int itemIndex) + internal virtual void ListItemSelectEvent(Menu menu, MenuListItem listItem, int selectedIndex, int itemIndex) { OnListItemSelect?.Invoke(menu, listItem, selectedIndex, itemIndex); } @@ -225,7 +224,7 @@ protected virtual void ListItemSelectEvent(Menu menu, MenuListItem listItem, int /// The old of the . /// The new of the . /// The of the in the . - protected virtual void ListItemIndexChangeEvent(Menu menu, MenuListItem listItem, int oldSelectionIndex, int newSelectionIndex, int itemIndex) + internal virtual void ListItemIndexChangeEvent(Menu menu, MenuListItem listItem, int oldSelectionIndex, int newSelectionIndex, int itemIndex) { OnListIndexChange?.Invoke(menu, listItem, oldSelectionIndex, newSelectionIndex, itemIndex); } @@ -234,7 +233,7 @@ protected virtual void ListItemIndexChangeEvent(Menu menu, MenuListItem listItem /// Triggered when a is closed. /// /// The that was closed. - protected virtual void MenuCloseEvent(Menu menu) + internal virtual void MenuCloseEvent(Menu menu) { OnMenuClose?.Invoke(menu); } @@ -243,7 +242,7 @@ protected virtual void MenuCloseEvent(Menu menu) /// Triggered when a is opened. /// /// The that has been opened. - protected virtual void MenuOpenEvent(Menu menu) + internal virtual void MenuOpenEvent(Menu menu) { OnMenuOpen?.Invoke(menu); } @@ -256,7 +255,7 @@ protected virtual void MenuOpenEvent(Menu menu) /// The new that is now selected. /// The old of this item. /// The new of this item. - protected virtual void IndexChangeEvent(Menu menu, MenuItem oldItem, MenuItem newItem, int oldIndex, int newIndex) + internal virtual void IndexChangeEvent(Menu menu, MenuItem oldItem, MenuItem newItem, int oldIndex, int newIndex) { OnIndexChange?.Invoke(menu, oldItem, newItem, oldIndex, newIndex); } @@ -270,7 +269,7 @@ protected virtual void IndexChangeEvent(Menu menu, MenuItem oldItem, MenuItem ne /// The old position of the slider bar. /// The new position of the slider bar. /// The index of this . - protected virtual void SliderItemChangedEvent(Menu menu, MenuSliderItem sliderItem, int oldPosition, int newPosition, int itemIndex) + internal virtual void SliderItemChangedEvent(Menu menu, MenuSliderItem sliderItem, int oldPosition, int newPosition, int itemIndex) { OnSliderPositionChange?.Invoke(menu, sliderItem, oldPosition, newPosition, itemIndex); } @@ -282,7 +281,7 @@ protected virtual void SliderItemChangedEvent(Menu menu, MenuSliderItem sliderIt /// The that was pressed. /// The current position of the slider bar. /// The index of this . - protected virtual void SliderSelectedEvent(Menu menu, MenuSliderItem sliderItem, int sliderPosition, int itemIndex) + internal virtual void SliderSelectedEvent(Menu menu, MenuSliderItem sliderItem, int sliderPosition, int itemIndex) { OnSliderItemSelect?.Invoke(menu, sliderItem, sliderPosition, itemIndex); } @@ -295,7 +294,7 @@ protected virtual void SliderSelectedEvent(Menu menu, MenuSliderItem sliderItem, /// The that was changed. /// The old of the . /// The new of the . - protected virtual void DynamicListItemCurrentItemChanged(Menu menu, MenuDynamicListItem dynamicListItem, string oldValue, string newValue) + internal virtual void DynamicListItemCurrentItemChanged(Menu menu, MenuDynamicListItem dynamicListItem, string oldValue, string newValue) { OnDynamicListItemCurrentItemChange?.Invoke(menu, dynamicListItem, oldValue, newValue); } @@ -306,7 +305,7 @@ protected virtual void DynamicListItemCurrentItemChanged(Menu menu, MenuDynamicL /// The in which this event occurred. /// The that was selected. /// The of the in the . - protected virtual void DynamicListItemSelectEvent(Menu menu, MenuDynamicListItem dynamicListItem, string currentItem) + internal virtual void DynamicListItemSelectEvent(Menu menu, MenuDynamicListItem dynamicListItem, string currentItem) { OnDynamicListItemSelect?.Invoke(menu, dynamicListItem, currentItem); } @@ -328,6 +327,8 @@ protected virtual void DynamicListItemSelectEvent(Menu menu, MenuDynamicListItem private int index = 0; + private bool visible = false; + public int ViewIndexOffset { get; private set; } = 0; private List VisibleMenuItems @@ -345,7 +346,6 @@ private List VisibleMenuItems var items = GetMenuItems().ToList().GetRange(ViewIndexOffset, Math.Min(MaxItemsOnScreen, Size - ViewIndexOffset)); return items; } - } } @@ -371,7 +371,25 @@ private List VisibleMenuItems public int Size => filterActive ? FilterItems.Count : MenuItems.Count; - public bool Visible { get; set; } = false; + public bool Visible + { + get + { + return visible; + } + set + { + if (value) + { + MenuController.VisibleMenus.Add(this); + } + else + { + MenuController.VisibleMenus.Remove(this); + } + visible = value; + } + } #if FIVEM public bool LeftAligned => MenuController.MenuAlignment == MenuController.MenuAlignmentOption.Left; @@ -391,9 +409,28 @@ private List VisibleMenuItems public bool EnableInstructionalButtons { get; set; } = true; - public float[] WeaponStats { get; private set; } - public float[] WeaponComponentStats { get; private set; } + /// + /// Should contain 4 floats. + /// + public float[] WeaponStats { get; private set; } = new float[4] { 0f, 0f, 0f, 0f }; + /// + /// Should contain 4 floats. + /// + public float[] WeaponComponentStats { get; private set; } = new float[4] { 0f, 0f, 0f, 0f }; + /// + /// Should contain 4 floats. + /// + public float[] VehicleStats { get; private set; } = new float[4] { 0f, 0f, 0f, 0f }; + /// + /// Should contain 4 floats. + /// + public float[] VehicleUpgradeStats { get; private set; } = new float[4] { 0f, 0f, 0f, 0f }; + public bool ShowWeaponStatsPanel { get; set; } = false; + public bool ShowVehicleStatsPanel { get; set; } = false; + + private readonly string[] weaponStatNames = new string[4] { "PM_DAMAGE", "PM_FIRERATE", "PM_ACCURACY", "PM_RANGE" }; + private readonly string[] vehicleStatNames = new string[4] { "FMMC_VEHST_0", "FMMC_VEHST_1", "FMMC_VEHST_2", "FMMC_VEHST_3" }; private bool filterActive = false; @@ -403,9 +440,31 @@ private List VisibleMenuItems public List CustomInstructionalButtons = new List(); #endif #if REDM - public static string GetLocalizedText(string label) => Call((CitizenFX.Core.Native.Hash)0xCFEDCCAD3C5BA90D, label); // GetLabelText - public List InstructionalButtons = new List() { new InstructionalButton(new Control[1] { Control.FrontendAccept }, GetLocalizedText("INPUT_FRONTEND_SELECT")), new InstructionalButton(new Control[1] { Control.FrontendCancel }, "Back"), new InstructionalButton(new Control[2] { Control.FrontendUp, Control.FrontendDown }, "Up / Down") }; + public List InstructionalButtons = new List() { + new InstructionalButton( + new Control[1] + { + Control.FrontendAccept + }, + GetLabelText("INPUT_FRONTEND_SELECT") + ), + new InstructionalButton( + new Control[1] + { + Control.FrontendCancel + }, + "Back" + ), + new InstructionalButton( + new Control[2] + { + Control.FrontendUp, + Control.FrontendDown + }, + "Up / Down" + ) + }; #endif #if FIVEM @@ -437,9 +496,8 @@ public class InstructionalButton public InstructionalButton(Control[] controls, string text) { this.controls = controls; - //this.text = Call((CitizenFX.Core.Native.Hash)0xFA925AC00EB830B9, 10, "LITERAL_STRING", text); // CreateVarString - this.textString = text; - this.promptHandle = 0; + textString = text; + promptHandle = 0; } /// @@ -447,35 +505,33 @@ public InstructionalButton(Control[] controls, string text) /// public void Prepare() { - if (this.IsPrepared()) + if (IsPrepared) { return; - //this.Dispose(); } - this.promptHandle = Call((CitizenFX.Core.Native.Hash)0x04F97DE45A519419); // UipromptRegisterBegin - Call((CitizenFX.Core.Native.Hash)0x5DD02A8318420DD7, this.promptHandle, Call((CitizenFX.Core.Native.Hash)0xFA925AC00EB830B9, 10, "LITERAL_STRING", textString)); // UipromptSetText - foreach (var c in this.controls) + // 0x04F97DE45A519419 / UipromptRegisterBegin (new name, not yet in API) + promptHandle = PromptRegisterBegin(); + + // 0xFA925AC00EB830B9 / CreateVarString (Has incorrect parameter types in API) + long _text = Call((CitizenFX.Core.Native.Hash)0xFA925AC00EB830B9, 10, "LITERAL_STRING", textString); + + // 0x5DD02A8318420DD7 / UipromptSetText (Has incorrect parameter types in API) + Call((CitizenFX.Core.Native.Hash)0x5DD02A8318420DD7, promptHandle, _text); + foreach (var c in controls) { - Call((CitizenFX.Core.Native.Hash)0xB5352B7494A08258, this.promptHandle, c); // UipromptSetControlAction + // 0xB5352B7494A08258 / UipromptSetControlAction (Has incorrect parameter types in API) + Call((CitizenFX.Core.Native.Hash)0xB5352B7494A08258, this.promptHandle, c); } - //Call((CitizenFX.Core.Native.Hash)0xEA5CCF4EEB2F82D1, this.promptHandle); // UipromptSetHoldIndefinitelyMode - Call((CitizenFX.Core.Native.Hash)0xF7AA2696A22AD8B9, this.promptHandle); // UipromptRegisterEnd - this.SetEnabled(false, false); + PromptRegisterEnd(promptHandle); // UipromptRegisterEnd (new name, not yet in API) + SetEnabled(false, false); } /// /// Check if it is ready to be displayed. /// /// - public bool IsPrepared() - { - if (Call((CitizenFX.Core.Native.Hash)0x347469FBDD1589A9, this.promptHandle)) // UipromptIsValid - { - return true; - } - return false; - } + public bool IsPrepared => PromptIsValid(promptHandle); /// /// Enables or disables the prompt on screen. You must prepare the prompt first using . @@ -484,8 +540,8 @@ public bool IsPrepared() /// public void SetEnabled(bool visible, bool enabled) { - Call((CitizenFX.Core.Native.Hash)0x71215ACCFDE075EE, this.promptHandle, visible); // UipromptSetVisible - Call((CitizenFX.Core.Native.Hash)0x8A0FB4D03A630D21, this.promptHandle, enabled); // UipromptSetEnabled + PromptSetVisible(promptHandle, visible ? 1 : 0); + PromptSetEnabled(promptHandle, enabled ? 1 : 0); } /// @@ -493,12 +549,12 @@ public void SetEnabled(bool visible, bool enabled) /// public void Dispose() { - if (this.IsPrepared()) + if (IsPrepared) { - this.SetEnabled(false, false); - Call((CitizenFX.Core.Native.Hash)0x00EDE88D4D13CF59, this.promptHandle); // UipromptDelete + SetEnabled(false, false); + PromptDelete(promptHandle); } - this.promptHandle = 0; + promptHandle = 0; } //public long GetTextHandle() => text; @@ -555,8 +611,10 @@ public Menu(string name, string subtitle) MenuTitle = name; MenuSubtitle = subtitle; #if FIVEM - this.SetWeaponStats(0f, 0f, 0f, 0f); - this.SetWeaponComponentStats(0f, 0f, 0f, 0f); + SetWeaponStats(0f, 0f, 0f, 0f); + SetWeaponComponentStats(0f, 0f, 0f, 0f); + SetVehicleStats(0f, 0f, 0f, 0f); + SetVehicleUpgradeStats(0f, 0f, 0f, 0f); #endif } #endregion @@ -569,10 +627,7 @@ public Menu(string name, string subtitle) /// A value between 3 and 10 (inclusive). public void SetMaxItemsOnScreen(int max) { - if (max < 11 && max > 2) - { - MaxItemsOnScreen = max; - } + MaxItemsOnScreen = MathUtil.Clamp(max, 3, 10); } /// @@ -592,30 +647,12 @@ public List GetMenuItems() } /// - /// Retuns the currently selected menu item. Or null if there are no menu items, or the current menu index is out of range. + /// Gets the currently selected (highlighted) menu item. /// - /// + /// Retuns the currently selected menu item. Or null if there are no menu items, or the current menu index is out of range. public MenuItem GetCurrentMenuItem() { - var items = GetMenuItems(); - if (items.Count > CurrentIndex) - { - try - { - return items[CurrentIndex]; - } - catch (Exception e) - { - string itemsString = ""; - foreach (var d in items) - { - itemsString += d.Text + ", "; - } - itemsString = itemsString.Trim(',', ' '); - Debug.WriteLine($"[MenuAPI ({GetCurrentResourceName()})] Error: Could not get currrent menu item, error details: {e.Message}. Current index: {CurrentIndex}. Current menu size: {Size}. Current menu name: {MenuTitle}. List of menu items: {itemsString}."); - } - } - return null; + return GetMenuItems().ElementAtOrDefault(CurrentIndex); } /// @@ -628,6 +665,7 @@ public void ClearMenuItems() MenuItems.Clear(); FilterItems.Clear(); } + /// /// Removes all menu items. /// @@ -682,21 +720,22 @@ public void RemoveMenuItem(int itemIndex) /// public void RemoveMenuItem(MenuItem item) { - if (MenuItems.Contains(item)) + if (!MenuItems.Contains(item)) + { + return; + } + if (CurrentIndex >= item.Index) { - if (CurrentIndex >= item.Index) + if (Size > CurrentIndex) { - if (Size > CurrentIndex) - { - CurrentIndex--; - } - else - { - CurrentIndex = 0; - } + CurrentIndex--; + } + else + { + CurrentIndex = 0; } - MenuItems.Remove(item); } + MenuItems.Remove(item); } /// @@ -727,57 +766,38 @@ public void SelectItem(int index) /// public void SelectItem(MenuItem item) { - if (item != null && item.Enabled) + if (item == null) + { + return; + } + if (!item.Enabled) + { +#if FIVEM + PlaySoundFrontend(-1, "ERROR", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); +#endif +#if REDM + // Has invalid parameter types in API + Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_ERROR", "HUD_SHOP_SOUNDSET", 1); +#endif + } + else { - if (item is MenuCheckboxItem checkbox) - { - checkbox.Checked = !checkbox.Checked; - CheckboxChangedEvent(checkbox, item.Index, checkbox.Checked); - } - else if (item is MenuListItem listItem) - { - ListItemSelectEvent(this, listItem, listItem.ListIndex, listItem.Index); - } - else if (item is MenuDynamicListItem dynamicListItem) - { - DynamicListItemSelectEvent(this, dynamicListItem, dynamicListItem.CurrentItem); - } #if FIVEM - else if (item is MenuSliderItem slider) - { - SliderSelectedEvent(this, slider, slider.Position, slider.Index); - } - else - { - ItemSelectedEvent(item, item.Index); - } PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); #endif #if REDM - else - { - ItemSelectedEvent(item, item.Index); - Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "SELECT", "HUD_SHOP_SOUNDSET", 1); - } + // Has invalid parameter types in API. + Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "SELECT", "HUD_SHOP_SOUNDSET", 1); #endif + item.Select(); if (MenuController.MenuButtons.ContainsKey(item)) { // this updates the parent menu. MenuController.AddSubmenu(MenuController.GetCurrentMenu(), MenuController.MenuButtons[item]); - MenuController.GetCurrentMenu().CloseMenu(); MenuController.MenuButtons[item].OpenMenu(); } } - else if (item != null && !item.Enabled) - { -#if FIVEM - PlaySoundFrontend(-1, "ERROR", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); -#endif -#if REDM - Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_ERROR", "HUD_SHOP_SOUNDSET", 1); -#endif - } } /// @@ -789,7 +809,7 @@ public void GoBack() PlaySoundFrontend(-1, "BACK", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); #endif #if REDM - + // Has invalid parameter types in API Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "Back", "HUD_SHOP_SOUNDSET", 1); #endif CloseMenu(); @@ -806,7 +826,6 @@ public void CloseMenu() { Visible = false; MenuCloseEvent(this); - #if REDM foreach (var v in InstructionalButtons) { @@ -823,7 +842,6 @@ public void OpenMenu() { Visible = true; MenuOpenEvent(this); - #if REDM if (EnableInstructionalButtons) { @@ -841,47 +859,49 @@ public void OpenMenu() /// public void GoUp() { - if (Visible && Size > 1) + if (!Visible || Size < 2) { - MenuItem oldItem; + return; + } + MenuItem oldItem; - if (filterActive) - { - oldItem = FilterItems[CurrentIndex]; - } - else - { - oldItem = MenuItems[CurrentIndex]; - } + if (filterActive) + { + oldItem = FilterItems[CurrentIndex]; + } + else + { + oldItem = MenuItems[CurrentIndex]; + } - if (CurrentIndex == 0) - { - CurrentIndex = Size - 1; - } - else - { - CurrentIndex--; - } + if (CurrentIndex == 0) + { + CurrentIndex = Size - 1; + } + else + { + CurrentIndex--; + } - var currItem = GetCurrentMenuItem(); + var currItem = GetCurrentMenuItem(); - if (currItem == null || !VisibleMenuItems.Contains(currItem)) + if (currItem == null || !VisibleMenuItems.Contains(currItem)) + { + ViewIndexOffset--; + if (ViewIndexOffset < 0) { - ViewIndexOffset--; - if (ViewIndexOffset < 0) - { - ViewIndexOffset = Math.Max(Size - MaxItemsOnScreen, 0); - } + ViewIndexOffset = Math.Max(Size - MaxItemsOnScreen, 0); } + } - IndexChangeEvent(this, oldItem, currItem, oldItem.Index, CurrentIndex); + IndexChangeEvent(this, oldItem, currItem, oldItem.Index, CurrentIndex); #if FIVEM - PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); + PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); #endif #if REDM - Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_UP", "HUD_SHOP_SOUNDSET", 1); + // Has invalid parameter types in API + Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_UP", "HUD_SHOP_SOUNDSET", 1); #endif - } } /// @@ -889,45 +909,48 @@ public void GoUp() /// public void GoDown() { - if (Visible && Size > 1) + if (!Visible || Size < 2) { - MenuItem oldItem; + return; + } - if (filterActive) - { - oldItem = FilterItems[CurrentIndex]; - } - else - { - oldItem = MenuItems[CurrentIndex]; - } + MenuItem oldItem; - if (CurrentIndex > 0 && CurrentIndex >= Size - 1) - { - CurrentIndex = 0; - } - else - { - CurrentIndex++; - } + if (filterActive) + { + oldItem = FilterItems[CurrentIndex]; + } + else + { + oldItem = MenuItems[CurrentIndex]; + } + + if (CurrentIndex > 0 && CurrentIndex >= Size - 1) + { + CurrentIndex = 0; + } + else + { + CurrentIndex++; + } - var currItem = GetCurrentMenuItem(); - if (currItem == null || !VisibleMenuItems.Contains(currItem)) + var currItem = GetCurrentMenuItem(); + if (currItem == null || !VisibleMenuItems.Contains(currItem)) + { + ViewIndexOffset++; + if (CurrentIndex == 0) { - ViewIndexOffset++; - if (CurrentIndex == 0) - { - ViewIndexOffset = 0; - } + ViewIndexOffset = 0; } - IndexChangeEvent(this, oldItem, currItem, oldItem.Index, CurrentIndex); + } + IndexChangeEvent(this, oldItem, currItem, oldItem.Index, CurrentIndex); #if FIVEM - PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); + PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); #endif #if REDM - Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_DOWN", "HUD_SHOP_SOUNDSET", 1); + // Has invalid parameter types in API. + Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_DOWN", "HUD_SHOP_SOUNDSET", 1); #endif - } } /// @@ -935,74 +958,19 @@ public void GoDown() /// public void GoLeft() { - if (MenuController.AreMenuButtonsEnabled) + if (!MenuController.AreMenuButtonsEnabled) { - var item = GetCurrentMenuItem(); - if (item != null && item.Enabled && item is MenuListItem listItem) - { - if (listItem.ItemsCount > 0) - { - int oldIndex = listItem.ListIndex; - int newIndex = oldIndex; - if (listItem.ListIndex < 1) - { - newIndex = listItem.ItemsCount - 1; - } - else - { - newIndex--; - } - listItem.ListIndex = newIndex; - - ListItemIndexChangeEvent(this, listItem, oldIndex, newIndex, listItem.Index); -#if FIVEM - PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); -#endif -#if REDM - Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_LEFT", "HUD_SHOP_SOUNDSET", 1); -#endif - } - } -#if FIVEM - else if (item.Enabled && item is MenuSliderItem slider) - { - if (slider.Position > slider.Min) - { - SliderItemChangedEvent(this, slider, slider.Position, slider.Position - 1, slider.Index); - slider.Position--; - PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); - } - else - { - PlaySoundFrontend(-1, "ERROR", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); - } - } -#endif - else if (item.Enabled && item is MenuDynamicListItem dynList) - { - string oldValue = dynList.CurrentItem; - string newSelectedItem = dynList.Callback(dynList, true); - dynList.CurrentItem = newSelectedItem; - DynamicListItemCurrentItemChanged(this, dynList, oldValue, newSelectedItem); -#if FIVEM - PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); -#endif -#if REDM - Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_RIGHT", "HUD_SHOP_SOUNDSET", 1); -#endif - } -#if FIVEM - // If it's a checkbox, just trigger the box instead. - else if (item.Enabled && item is MenuCheckboxItem checkbox) - { - SelectItem(checkbox); - } -#endif - // If the item is enabled and it's not any of the above, just select it. - else if (item.Enabled) - { - SelectItem(item); - } + return; + } + var item = GetCurrentMenuItem(); + if (item != null) + { + item.GoLeft(); + } + // If the item is not any of the above, return to parent menu. + else if (MenuController.NavigateMenuUsingArrows && !MenuController.DisableBackButton && !(MenuController.PreventExitingMenu && ParentMenu == null)) + { + GoBack(); } } @@ -1011,78 +979,19 @@ public void GoLeft() /// public void GoRight() { - if (MenuController.AreMenuButtonsEnabled) + if (!MenuController.AreMenuButtonsEnabled) { - var item = GetCurrentMenuItem(); - if (item != null && item.Enabled && item is MenuListItem listItem) - { - if (listItem.ItemsCount > 0) - { - int oldIndex = listItem.ListIndex; - int newIndex = oldIndex; - if (listItem.ListIndex >= listItem.ItemsCount - 1) - { - newIndex = 0; - } - else - { - newIndex++; - } - listItem.ListIndex = newIndex; - ListItemIndexChangeEvent(this, listItem, oldIndex, newIndex, listItem.Index); -#if FIVEM - PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); -#endif -#if REDM - Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_RIGHT", "HUD_SHOP_SOUNDSET", 1); -#endif - } - } -#if FIVEM - else if (item.Enabled && item is MenuSliderItem slider) - { - if (slider.Position < slider.Max) - { - SliderItemChangedEvent(this, slider, slider.Position, slider.Position + 1, slider.Index); - slider.Position++; - PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); - } - else - { - PlaySoundFrontend(-1, "ERROR", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); - } - } -#endif - else if (item.Enabled && item is MenuDynamicListItem dynList) - { - string oldValue = dynList.CurrentItem; - string newSelectedItem = dynList.Callback(dynList, false); - dynList.CurrentItem = newSelectedItem; - DynamicListItemCurrentItemChanged(this, dynList, oldValue, newSelectedItem); -#if FIVEM - PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); -#endif -#if REDM - Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_RIGHT", "HUD_SHOP_SOUNDSET", 1); -#endif - } -#if FIVEM - // If it's a checkbox, just trigger the box instead. - else if (item.Enabled && item is MenuCheckboxItem checkbox) - { - SelectItem(checkbox); - } -#endif - // If the item is enabled and it's not any of the above, just select it. - else if (item.Enabled) - { - SelectItem(item); - } + return; + } + var item = GetCurrentMenuItem(); + if (item != null) + { + item.GoRight(); } } /// - /// Allows you to sort the menu items using your own compare function. + /// Sorts the menu items using the provided compare function. /// /// public void SortMenuItems(Comparison compare) @@ -1095,6 +1004,10 @@ public void SortMenuItems(Comparison compare) MenuItems.Sort(compare); } + /// + /// Filters menu items using the provided filter function. + /// + /// public void FilterMenuItems(Func predicate) { if (filterActive) @@ -1107,13 +1020,24 @@ public void FilterMenuItems(Func predicate) filterActive = true; } + /// + /// Clears the current menu items filter for this menu. + /// public void ResetFilter() { RefreshIndex(0, 0); filterActive = false; FilterItems.Clear(); } + #if FIVEM + /// + /// Values should be between 0 and 1. + /// + /// + /// + /// + /// public void SetWeaponStats(float damage, float fireRate, float accuracy, float range) { WeaponStats = new float[4] @@ -1125,6 +1049,13 @@ public void SetWeaponStats(float damage, float fireRate, float accuracy, float r }; } + /// + /// Values should be between 0 and 1. + /// + /// + /// + /// + /// public void SetWeaponComponentStats(float damage, float fireRate, float accuracy, float range) { WeaponComponentStats = new float[4] @@ -1135,891 +1066,899 @@ public void SetWeaponComponentStats(float damage, float fireRate, float accuracy MathUtil.Clamp(WeaponStats[3] + range, 0f, 1f) }; } -#endif - - #endregion - #region internal task functions /// - /// Draws the menu title + subtitle, calls all Draw functions for all menu items and draws the description for the selected item. + /// Values should be between 0 and 1. /// - /// - internal async void Draw() + /// + /// + /// + /// + public void SetVehicleStats(float topSpeed, float acceleration, float braking, float traction) { -#if FIVEM - if (!Game.IsPaused && IsScreenFadedIn() && !IsPlayerSwitchInProgress() && !Game.PlayerPed.IsDead) -#endif -#if REDM - if (!Call(IS_PAUSE_MENU_ACTIVE) && - Call(IS_SCREEN_FADED_IN) && - !Call(IS_ENTITY_DEAD, PlayerPedId())) -#endif + VehicleStats = new float[4] { - #region Listen for custom key presses. - if (ButtonPressHandlers.Count > 0) - { - if (!MenuController.DisableMenuButtons) - { - foreach (ButtonPressHandler handler in ButtonPressHandlers) - { - if (handler.disableControl) - { -#if FIVEM - Game.DisableControlThisFrame(0, handler.control); -#endif -#if REDM - Call(DISABLE_CONTROL_ACTION, 0, handler.control, true); -#endif - } + MathUtil.Clamp(topSpeed, 0f, 1f), + MathUtil.Clamp(acceleration, 0f, 1f), + MathUtil.Clamp(braking, 0f, 1f), + MathUtil.Clamp(traction, 0f, 1f) + }; + } - switch (handler.pressType) - { -#if FIVEM - case ControlPressCheckType.JUST_PRESSED: - if (Game.IsControlJustPressed(0, handler.control) || Game.IsDisabledControlJustPressed(0, handler.control)) - handler.function.Invoke(this, handler.control); - break; - case ControlPressCheckType.JUST_RELEASED: - if (Game.IsControlJustReleased(0, handler.control) || Game.IsDisabledControlJustReleased(0, handler.control)) - handler.function.Invoke(this, handler.control); - break; - case ControlPressCheckType.PRESSED: - if (Game.IsControlPressed(0, handler.control) || Game.IsDisabledControlPressed(0, handler.control)) - handler.function.Invoke(this, handler.control); - break; - case ControlPressCheckType.RELEASED: - if (!Game.IsControlPressed(0, handler.control) && !Game.IsDisabledControlPressed(0, handler.control)) - handler.function.Invoke(this, handler.control); - break; + /// + /// Each upgrade value gets added on top of the already existing vehicle stats. + /// So if the normal topspeed value is set to 0.5, and you provide 0.2 here, the total + /// top speed value will be 0.7, where the last section (0.2) will be colored in blue. + /// The bar can only show values between 0 and 1, so the total value will be clamped between 0 and 1. + /// + /// + /// + /// + /// + public void SetVehicleUpgradeStats(float topSpeed, float acceleration, float braking, float traction) + { + VehicleUpgradeStats = new float[4] + { + MathUtil.Clamp(VehicleStats[0] + topSpeed, 0f, 1f), + MathUtil.Clamp(VehicleStats[1] + acceleration, 0f, 1f), + MathUtil.Clamp(VehicleStats[2] + braking, 0f, 1f), + MathUtil.Clamp(VehicleStats[3] + traction, 0f, 1f) + }; + } +#endif + #endregion + + #region internal/private task functions + /// + /// Processes any custom button press handlers for this menu. + /// + private void ProcessButtonPressHandlers() + { + if (ButtonPressHandlers.Any()) + { + if (!MenuController.DisableMenuButtons) + { + foreach (ButtonPressHandler handler in ButtonPressHandlers) + { + if (handler.disableControl) + { +#if FIVEM + Game.DisableControlThisFrame(0, handler.control); #endif #if REDM - case ControlPressCheckType.JUST_PRESSED: - if (Call(IS_CONTROL_JUST_PRESSED, 0, handler.control) || Call(IS_DISABLED_CONTROL_JUST_PRESSED, 0, handler.control)) - handler.function.Invoke(this, handler.control); - break; - case ControlPressCheckType.JUST_RELEASED: - if (Call(IS_CONTROL_JUST_RELEASED, 0, handler.control) || Call(IS_DISABLED_CONTROL_JUST_RELEASED, 0, handler.control)) - handler.function.Invoke(this, handler.control); - break; - case ControlPressCheckType.PRESSED: - if (Call(IS_CONTROL_PRESSED, 0, handler.control) || Call(IS_DISABLED_CONTROL_PRESSED, 0, handler.control)) - handler.function.Invoke(this, handler.control); - break; - case ControlPressCheckType.RELEASED: - if (!Call(IS_CONTROL_PRESSED, 0, handler.control) && !Call(IS_DISABLED_CONTROL_PRESSED, 0, handler.control)) - handler.function.Invoke(this, handler.control); - break; + DisableControlAction(0, (uint)handler.control, true); #endif - default: - break; - } + } + + switch (handler.pressType) + { +#if FIVEM + case ControlPressCheckType.JUST_PRESSED: + if (Game.IsControlJustPressed(0, handler.control) || Game.IsDisabledControlJustPressed(0, handler.control)) + handler.function.Invoke(this, handler.control); + break; + case ControlPressCheckType.JUST_RELEASED: + if (Game.IsControlJustReleased(0, handler.control) || Game.IsDisabledControlJustReleased(0, handler.control)) + handler.function.Invoke(this, handler.control); + break; + case ControlPressCheckType.PRESSED: + if (Game.IsControlPressed(0, handler.control) || Game.IsDisabledControlPressed(0, handler.control)) + handler.function.Invoke(this, handler.control); + break; + case ControlPressCheckType.RELEASED: + if (!Game.IsControlPressed(0, handler.control) && !Game.IsDisabledControlPressed(0, handler.control)) + handler.function.Invoke(this, handler.control); + break; +#endif +#if REDM + case ControlPressCheckType.JUST_PRESSED: + if (IsControlJustPressed(0, (uint)handler.control) || IsDisabledControlJustPressed(0, (uint)handler.control)) + handler.function.Invoke(this, handler.control); + break; + case ControlPressCheckType.JUST_RELEASED: + if (IsControlJustReleased(0, (uint)handler.control) || IsDisabledControlJustReleased(0, (uint)handler.control)) + handler.function.Invoke(this, handler.control); + break; + case ControlPressCheckType.PRESSED: + if (IsControlPressed(0, (uint)handler.control) || IsDisabledControlPressed(0, (uint)handler.control)) + handler.function.Invoke(this, handler.control); + break; + case ControlPressCheckType.RELEASED: + if (!IsControlPressed(0, (uint)handler.control) && !IsDisabledControlPressed(0, (uint)handler.control)) + handler.function.Invoke(this, handler.control); + break; +#endif + default: + break; } } } - #endregion + } + } - MenuItemsYOffset = 0f; - if (MenuController.SetDrawOrder) - SetScriptGfxDrawOrder(1); - #region Draw Header - if (!string.IsNullOrEmpty(MenuTitle)) - { - #region Draw Header Background + /// + /// Draws the menu header. + /// + /// + /// The new menuItemsOffset value + private async Task DrawHeader(float menuItemsOffset) + { + if (!string.IsNullOrEmpty(MenuTitle)) + { + #region Draw Header Background #if FIVEM - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; - float y = (Position.Value + (headerSize.Value/ 2f)) / MenuController.ScreenHeight; - float width = headerSize.Key / MenuController.ScreenWidth; - float height = headerSize.Value / MenuController.ScreenHeight; + float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; + float y = (Position.Value + (headerSize.Value / 2f)) / MenuController.ScreenHeight; + float width = headerSize.Key / MenuController.ScreenWidth; + float height = headerSize.Value / MenuController.ScreenHeight; - if (!string.IsNullOrEmpty(HeaderTexture.Key) && !string.IsNullOrEmpty(HeaderTexture.Value)) + if (!string.IsNullOrEmpty(HeaderTexture.Key) && !string.IsNullOrEmpty(HeaderTexture.Value)) + { + if (!HasStreamedTextureDictLoaded(HeaderTexture.Key)) { - if (!HasStreamedTextureDictLoaded(HeaderTexture.Key)) + RequestStreamedTextureDict(HeaderTexture.Key, false); + while (!HasStreamedTextureDictLoaded(HeaderTexture.Key)) { - RequestStreamedTextureDict(HeaderTexture.Key, false); - while (!HasStreamedTextureDictLoaded(HeaderTexture.Key)) - { - await BaseScript.Delay(0); - } + await BaseScript.Delay(0); } - DrawSprite(HeaderTexture.Key, HeaderTexture.Value, x, y, width, height, 0f, 255, 255, 255, 255); - } - else - { - DrawSprite(MenuController._texture_dict, MenuController._header_texture, x, y, width, height, 0f, 255, 255, 255, 255); } + DrawSprite(HeaderTexture.Key, HeaderTexture.Value, x, y, width, height, 0f, 255, 255, 255, 255); + } + else + { + DrawSprite(MenuController._texture_dict, MenuController._header_texture, x, y, width, height, 0f, 255, 255, 255, 255); + } + ResetScriptGfxAlign(); +#endif +#if REDM + if (MenuController.SetDrawOrder) + SetScriptGfxDrawOrder(2); + float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; + float y = (Position.Value + (headerSize.Value / 2f)) / MenuController.ScreenHeight; + float width = headerSize.Key / MenuController.ScreenWidth; + float height = headerSize.Value / MenuController.ScreenHeight; + DrawSprite(MenuController._texture_dict, MenuController._header_texture, x, y, width, height, 0f, 181, 17, 18, 255, false); + if (MenuController.SetDrawOrder) + SetScriptGfxDrawOrder(1); +#endif + #endregion - ResetScriptGfxAlign(); + #region Draw Header Menu Title +#if FIVEM + int font = 1; + float size = (45f * 27f) / MenuController.ScreenHeight; + SetScriptGfxAlign(76, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + + BeginTextCommandDisplayText("STRING"); + SetTextFont(font); + SetTextColour(255, 255, 255, 255); + SetTextScale(size, size); + SetTextJustification(0); + AddTextComponentSubstringPlayerName(MenuTitle); + if (LeftAligned) + { + EndTextCommandDisplayText(((headerSize.Key / 2f) / MenuController.ScreenWidth), y - (GetTextScaleHeight(size, font) / 2f)); + } + else + { + EndTextCommandDisplayText(GetSafeZoneSize() - ((headerSize.Key / 2f) / MenuController.ScreenWidth), y - (GetTextScaleHeight(size, font) / 2f)); + } + ResetScriptGfxAlign(); + menuItemsOffset = headerSize.Value; #endif #if REDM - if (MenuController.SetDrawOrder) - SetScriptGfxDrawOrder(2); - float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; - float y = (Position.Value + (headerSize.Value / 2f)) / MenuController.ScreenHeight; - float width = headerSize.Key / MenuController.ScreenWidth; - float height = headerSize.Value / MenuController.ScreenHeight; - Call(DRAW_SPRITE, MenuController._texture_dict, MenuController._header_texture, x, y, width, height, 0f, 181, 17, 18, 255); - if (MenuController.SetDrawOrder) - SetScriptGfxDrawOrder(1); + SetTextCentre(true); + float size = (45f * 27f) / MenuController.ScreenHeight; + SetTextScale(size, size); + + if (MenuController.SetDrawOrder) + SetScriptGfxDrawOrder(3); + int font = 10; + Call((CitizenFX.Core.Native.Hash)0xADA9255D, font); + long _text = Call(_CREATE_VAR_STRING, 10, "LITERAL_STRING", MenuTitle ?? "N/A"); + float textX = (headerSize.Key / 2f) / MenuController.ScreenWidth; + float textY = y - (45f / MenuController.ScreenHeight); + DisplayText(_text, textX, textY); + if (MenuController.SetDrawOrder) + SetScriptGfxDrawOrder(1); + menuItemsOffset = headerSize.Value; +#endif + #endregion + } + else + { +#if REDM + menuItemsOffset = 40f; #endif - #endregion + } + await Task.FromResult(0); + return menuItemsOffset; + } - #region Draw Header Menu Title + /// + /// Draws the menu subtitle. + /// + /// + /// The new menuItemsOffset value + private float DrawSubtitle(float menuItemsOffset) + { #if FIVEM - int font = 1; - float size = (45f * 27f) / MenuController.ScreenHeight; - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + #region draw subtitle background + SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - BeginTextCommandDisplayText("STRING"); - SetTextFont(font); - SetTextColour(255, 255, 255, 255); - SetTextScale(size, size); - SetTextJustification(0); - AddTextComponentSubstringPlayerName(MenuTitle); - if (LeftAligned) - { - EndTextCommandDisplayText(((headerSize.Key / 2f) / MenuController.ScreenWidth), y - (GetTextScaleHeight(size, font) / 2f)); - } - else - { - EndTextCommandDisplayText(GetSafeZoneSize() - ((headerSize.Key / 2f) / MenuController.ScreenWidth), y - (GetTextScaleHeight(size, font) / 2f)); - } + float bgHeight = 38f; - ResetScriptGfxAlign(); + float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; + float y = ((Position.Value + menuItemsOffset + (bgHeight / 2f)) / MenuController.ScreenHeight); + float width = headerSize.Key / MenuController.ScreenWidth; + float height = bgHeight / MenuController.ScreenHeight; - MenuItemsYOffset = headerSize.Value; + DrawRect(x, y, width, height, 0, 0, 0, 250); + ResetScriptGfxAlign(); + #endregion #endif - #if REDM - Call(SET_TEXT_CENTRE, true); - float size = (45f * 27f) / MenuController.ScreenHeight; - Call(SET_TEXT_SCALE, size, size); - - if (MenuController.SetDrawOrder) - SetScriptGfxDrawOrder(3); - //SetTextWrap(textMinX, textMaxX); - int font = 10; - Call((CitizenFX.Core.Native.Hash)0xADA9255D, font); - Call(_DISPLAY_TEXT, Call(_CREATE_VAR_STRING, 10, "LITERAL_STRING", MenuTitle ?? "N/A"), ((headerSize.Key / 2f) / MenuController.ScreenWidth), y - (45f / MenuController.ScreenHeight)); - if (MenuController.SetDrawOrder) - SetScriptGfxDrawOrder(1); - MenuItemsYOffset = headerSize.Value; + float bgHeight = 38f; + float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; + float y = ((Position.Value + menuItemsOffset + (bgHeight / 2f)) / MenuController.ScreenHeight); + float width = headerSize.Key / MenuController.ScreenWidth; + float height = bgHeight / MenuController.ScreenHeight; #endif - #endregion +#if FIVEM + #region draw subtitle text + if (!string.IsNullOrEmpty(MenuSubtitle)) + { + int font = 0; + float size = (14f * 27f) / MenuController.ScreenHeight; + + SetScriptGfxAlign(76, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + + BeginTextCommandDisplayText("STRING"); + SetTextFont(font); + SetTextScale(size, size); + SetTextJustification(1); + // Don't make the text blue if another color is used in the string. + if (MenuSubtitle.Contains("~") || string.IsNullOrEmpty(MenuTitle)) + { + AddTextComponentSubstringPlayerName(MenuSubtitle.ToUpper()); } else { -#if REDM - MenuItemsYOffset = 40f; -#endif + AddTextComponentSubstringPlayerName("~HUD_COLOUR_FREEMODE~" + MenuSubtitle.ToUpper()); } - #endregion - #region Draw Subtitle + if (LeftAligned) + { + EndTextCommandDisplayText(10f / MenuController.ScreenWidth, y - (GetTextScaleHeight(size, font) / 2f + (4f / MenuController.ScreenHeight))); + } + else { + EndTextCommandDisplayText(GetSafeZoneSize() - ((headerSize.Key - 10f) / MenuController.ScreenWidth), y - (GetTextScaleHeight(size, font) / 2f + (4f / MenuController.ScreenHeight))); + } + ResetScriptGfxAlign(); + } + #endregion +#endif +#if REDM + if (!string.IsNullOrEmpty(MenuSubtitle)) + { + if (MenuController.SetDrawOrder) + SetScriptGfxDrawOrder(3); + float size = (14f * 27f) / MenuController.ScreenHeight; + SetTextScale(size, size); + SetTextCentre(true); + int font = 9; + Call((CitizenFX.Core.Native.Hash)0xADA9255D, font); + long _text = Call(_CREATE_VAR_STRING, 10, "LITERAL_STRING", MenuSubtitle ?? "N/A"); + DisplayText(_text, x, y - (52f / MenuController.ScreenHeight)); + if (MenuController.SetDrawOrder) + SetScriptGfxDrawOrder(1); + } +#endif + #region draw counter + pre-counter text #if FIVEM - #region draw subtitle background - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + string counterText = $"{CounterPreText ?? ""}{CurrentIndex + 1} / {Size}"; + if (!string.IsNullOrEmpty(CounterPreText) || MaxItemsOnScreen < Size) + { + int font = 0; + float size = (14f * 27f) / MenuController.ScreenHeight; - float bgHeight = 38f; + SetScriptGfxAlign(76, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; - float y = ((Position.Value + MenuItemsYOffset + (bgHeight / 2f)) / MenuController.ScreenHeight); - float width = headerSize.Key / MenuController.ScreenWidth; - float height = bgHeight / MenuController.ScreenHeight; + BeginTextCommandDisplayText("STRING"); + SetTextFont(font); + SetTextScale(size, size); + SetTextJustification(2); + if ((MenuSubtitle ?? "").Contains("~") || (CounterPreText ?? "").Contains("~") || string.IsNullOrEmpty(MenuTitle)) + { + AddTextComponentSubstringPlayerName(counterText.ToUpper()); + } + else + { + AddTextComponentSubstringPlayerName("~HUD_COLOUR_FREEMODE~" + counterText.ToUpper()); + } + if (LeftAligned) + { + SetTextWrap(0f, (485f / MenuController.ScreenWidth)); + EndTextCommandDisplayText(10f / MenuController.ScreenWidth, y - (GetTextScaleHeight(size, font) / 2f + (4f / MenuController.ScreenHeight))); + } + else + { + SetTextWrap(0f, GetSafeZoneSize() - (10f / MenuController.ScreenWidth)); + EndTextCommandDisplayText(0f, y - (GetTextScaleHeight(size, font) / 2f + (4f / MenuController.ScreenHeight))); + } - DrawRect(x, y, width, height, 0, 0, 0, 250); - ResetScriptGfxAlign(); - #endregion + ResetScriptGfxAlign(); + } + if (!string.IsNullOrEmpty(MenuSubtitle) || (CounterPreText != null || MaxItemsOnScreen < Size)) + { + menuItemsOffset += bgHeight - 1f; + } #endif - #if REDM - float bgHeight = 38f; - float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; - float y = ((Position.Value + MenuItemsYOffset + (bgHeight / 2f)) / MenuController.ScreenHeight); - float width = headerSize.Key / MenuController.ScreenWidth; - float height = bgHeight / MenuController.ScreenHeight; - + if (Size > 0) + { + float textSize = (12f * 27f) / MenuController.ScreenHeight; + SetTextScale(textSize, textSize); + SetTextColor(135, 135, 135, 255); + SetTextCentre(true); + float textMinX = (headerSize.Key / 2f) / MenuController.ScreenWidth; + float textMaxX = (Width - 10f) / MenuController.ScreenWidth; + float textY = (menuItemsOffset + 38f * (MathUtil.Clamp(Size, 0, MaxItemsOnScreen) + 1) - 11f) / MenuController.ScreenHeight; + int font = 23; + Call((CitizenFX.Core.Native.Hash)0xADA9255D, font); + long _text = Call(_CREATE_VAR_STRING, 10, "LITERAL_STRING", $"{CurrentIndex + 1} of {Size}"); + DisplayText(_text, textMinX, textY); + } #endif + #endregion + return menuItemsOffset; + } + /// + /// Draws the background for all visible menu item's as one large rectangle. + /// + /// + /// The new menuItemsOffset value + private float DrawBackgroundGradient(float menuItemsOffset) + { + if (Size < 1) + { + return menuItemsOffset; + } #if FIVEM - #region draw subtitle text - if (!string.IsNullOrEmpty(MenuSubtitle)) - { - int font = 0; - float size = (14f * 27f) / MenuController.ScreenHeight; - //float size = 0.34f; - - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - - BeginTextCommandDisplayText("STRING"); - SetTextFont(font); - SetTextScale(size, size); - SetTextJustification(1); - // Don't make the text blue if another color is used in the string. - if (MenuSubtitle.Contains("~") || string.IsNullOrEmpty(MenuTitle)) - { - AddTextComponentSubstringPlayerName(MenuSubtitle.ToUpper()); - } - else - { - AddTextComponentSubstringPlayerName("~HUD_COLOUR_FREEMODE~" + MenuSubtitle.ToUpper()); - } - - if (LeftAligned) - { - EndTextCommandDisplayText(10f / MenuController.ScreenWidth, y - (GetTextScaleHeight(size, font) / 2f + (4f / MenuController.ScreenHeight))); - } - else - { - EndTextCommandDisplayText(GetSafeZoneSize() - ((headerSize.Key - 10f) / MenuController.ScreenWidth), y - (GetTextScaleHeight(size, font) / 2f + (4f / MenuController.ScreenHeight))); - } - ResetScriptGfxAlign(); - } - #endregion + SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); #endif - -#if REDM - if (!string.IsNullOrEmpty(MenuSubtitle)) - { - if (MenuController.SetDrawOrder) - SetScriptGfxDrawOrder(3); - float size = (14f * 27f) / MenuController.ScreenHeight; - Call(SET_TEXT_SCALE, size, size); - Call(SET_TEXT_CENTRE, true); - int font = 9; - Call((CitizenFX.Core.Native.Hash)0xADA9255D, font); - Call(_DISPLAY_TEXT, Call(_CREATE_VAR_STRING, 10, "LITERAL_STRING", MenuSubtitle ?? "N/A"), x, y - (52f / MenuController.ScreenHeight)); - if (MenuController.SetDrawOrder) - SetScriptGfxDrawOrder(1); - } -#endif - - #region draw counter + pre-counter text #if FIVEM - string counterText = $"{CounterPreText ?? ""}{CurrentIndex + 1} / {Size}"; - if (!string.IsNullOrEmpty(CounterPreText) || MaxItemsOnScreen < Size) - { - int font = 0; - float size = (14f * 27f) / MenuController.ScreenHeight; - //float size = 0.34f; - - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - - BeginTextCommandDisplayText("STRING"); - SetTextFont(font); - SetTextScale(size, size); - SetTextJustification(2); - if ((MenuSubtitle ?? "").Contains("~") || (CounterPreText ?? "").Contains("~") || string.IsNullOrEmpty(MenuTitle)) - { - AddTextComponentSubstringPlayerName(counterText.ToUpper()); - } - else - { - AddTextComponentSubstringPlayerName("~HUD_COLOUR_FREEMODE~" + counterText.ToUpper()); - } - if (LeftAligned) - { - SetTextWrap(0f, (485f / MenuController.ScreenWidth)); - EndTextCommandDisplayText(10f / MenuController.ScreenWidth, y - (GetTextScaleHeight(size, font) / 2f + (4f / MenuController.ScreenHeight))); - } - else - { - SetTextWrap(0f, GetSafeZoneSize() - (10f / MenuController.ScreenWidth)); - EndTextCommandDisplayText(0f, y - (GetTextScaleHeight(size, font) / 2f + (4f / MenuController.ScreenHeight))); - } - - ResetScriptGfxAlign(); - } - if (!string.IsNullOrEmpty(MenuSubtitle) || (CounterPreText != null || MaxItemsOnScreen < Size)) - { - MenuItemsYOffset += bgHeight - 1f; - } + float bgHeight = 38f * MathUtil.Clamp(Size, 0, MaxItemsOnScreen); + float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; + float y = ((Position.Value + menuItemsOffset + ((bgHeight + 1f) / 2f)) / MenuController.ScreenHeight); + float width = headerSize.Key / MenuController.ScreenWidth; + float height = (bgHeight + 1f) / MenuController.ScreenHeight; + + DrawRect(x, y, width, height, 0, 0, 0, 180); + menuItemsOffset += bgHeight - 1f; + ResetScriptGfxAlign(); #endif #if REDM - if (Size > 0) - { - float textSize = (12f * 27f) / MenuController.ScreenHeight; - Call(SET_TEXT_SCALE, textSize, textSize); - Call((CitizenFX.Core.Native.Hash)0x50A41AD966910F03, 135, 135, 135, 255); // _SET_TEXT_COLOUR / 0x50A41AD966910F03 - Call(SET_TEXT_CENTRE, true); - float textMinX = (headerSize.Key / 2f) / MenuController.ScreenWidth; - float textMaxX = (Width - 10f) / MenuController.ScreenWidth; - float textY = (MenuItemsYOffset + 38f * (MathUtil.Clamp(Size, 0, MaxItemsOnScreen) + 1) - 11f) / MenuController.ScreenHeight; - int font = 23; - Call((CitizenFX.Core.Native.Hash)0xADA9255D, font); - //SetTextWrap(textMinX, textMaxX); - - Call(_DISPLAY_TEXT, Call(_CREATE_VAR_STRING, 10, "LITERAL_STRING", $"{CurrentIndex + 1} of {Size}"), textMinX, textY); - } -#endif - - #endregion + //float x = (Position.Key + ((headerSize.Key) / 2f)) / MenuController.ScreenWidth; + //float y = ((Position.Value + menuItemsOffset + ((bgHeight + 1f) / 2f) /) / MenuController.ScreenHeight); + //float width = (headerSize.Key + 16f) / MenuController.ScreenWidth; + //float height = (bgHeight + 17f) / MenuController.ScreenHeight; + float bgHeight = 38f * MathUtil.Clamp(Size, 0, MaxItemsOnScreen); + var currentMenuItem = GetCurrentMenuItem(); + float descriptionBoxHeight = 0f; + if (currentMenuItem != null && !string.IsNullOrEmpty(currentMenuItem.Description)) + { + int count = (currentMenuItem.Description.Count((a => { return a == '\n'; })) - 1); + if (count < 1) + { + descriptionBoxHeight = 42f; } - #endregion - - #region Draw menu items background gradient - if (Size > 0) + else { -#if FIVEM - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + descriptionBoxHeight = (38f * count) + 30f; + } + + bgHeight += descriptionBoxHeight; + } + float actualBgYLocation = ((38f + (38f / 2f) + (bgHeight / 2f)) / MenuController.ScreenHeight); + float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; + float y = ((Position.Value + menuItemsOffset + ((bgHeight + 1f - (headerSize.Value)) / 2f) + 19f) / MenuController.ScreenHeight); + float width = headerSize.Key / MenuController.ScreenWidth; + float height = (headerSize.Value + bgHeight + 33f + 38f) / MenuController.ScreenHeight; + DrawSprite(MenuController._texture_dict, MenuController._header_texture, x, y, width, height, 0f, 0, 0, 0, 240, false); + DrawSprite(MenuController._texture_dict, MenuController._header_texture, x, y + actualBgYLocation - (descriptionBoxHeight / MenuController.ScreenHeight), width, 38f / MenuController.ScreenHeight, 0f, 55, 55, 55, 255, false); + menuItemsOffset += bgHeight - descriptionBoxHeight - 1f; #endif + return menuItemsOffset; + } + + /// + /// Draw menu items that are visible in the current view. + /// + private void DrawActiveMenuItems() + { + if (Size < 1) + { + return; + } + foreach (var item in VisibleMenuItems) + { + item.Draw(ViewIndexOffset); + } + } - //DrawSprite(MenuController._texture_dict, "gradient_bgd", x, y, width, height, 0f, 255, 255, 255, 255); #if FIVEM - float bgHeight = 38f * MathUtil.Clamp(Size, 0, MaxItemsOnScreen); + /// + /// Draws the up/down arrow indicators whenever the menu contains more items that are not visible in the current view. + /// + /// + private float DrawUpDownOverflowIndicators() + { + float descriptionYOffset = 0f; + if (Size < 1 || Size <= MaxItemsOnScreen) + { + return descriptionYOffset; + } + #region background + float width = Width / MenuController.ScreenWidth; + float height = 60f / MenuController.ScreenWidth; + float x = (Position.Key + (Width / 2f)) / MenuController.ScreenWidth; + float y = (MenuItemsYOffset / MenuController.ScreenHeight) + (height / 2f) + (6f / MenuController.ScreenHeight); + + SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + + DrawRect(x, y, width, height, 0, 0, 0, 180); + descriptionYOffset = height; + ResetScriptGfxAlign(); + #endregion + + #region up/down icons + SetScriptGfxAlign(76, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + float xMin = 0f; + float xMax = Width / MenuController.ScreenWidth; + float xCenter = 250f / MenuController.ScreenWidth; + float yTop = y - (20f / MenuController.ScreenHeight); + float yBottom = y - (10f / MenuController.ScreenHeight); + + BeginTextCommandDisplayText("STRING"); + AddTextComponentSubstringPlayerName("↑"); + + SetTextFont(0); + SetTextScale(1f, (14f * 27f) / MenuController.ScreenHeight); + SetTextJustification(0); + if (LeftAligned) + { + SetTextWrap(xMin, xMax); + EndTextCommandDisplayText(xCenter, yTop); + } + else + { + xMin = GetSafeZoneSize() - ((Width - 10f) / MenuController.ScreenWidth); + xMax = GetSafeZoneSize() - (10f / MenuController.ScreenWidth); + xCenter = GetSafeZoneSize() - (250f / MenuController.ScreenWidth); + SetTextWrap(xMin, xMax); + EndTextCommandDisplayText(xCenter, yTop); + } - float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; - float y = ((Position.Value + MenuItemsYOffset + ((bgHeight + 1f) / 2f)) / MenuController.ScreenHeight); - float width = headerSize.Key / MenuController.ScreenWidth; - float height = (bgHeight + 1f) / MenuController.ScreenHeight; + BeginTextCommandDisplayText("STRING"); + AddTextComponentSubstringPlayerName("↓"); - DrawRect(x, y, width, height, 0, 0, 0, 180); - MenuItemsYOffset += bgHeight - 1f; - ResetScriptGfxAlign(); -#endif -#if REDM - //float x = (Position.Key + ((headerSize.Key) / 2f)) / MenuController.ScreenWidth; - //float y = ((Position.Value + MenuItemsYOffset + ((bgHeight + 1f) / 2f) /) / MenuController.ScreenHeight); - //float width = (headerSize.Key + 16f) / MenuController.ScreenWidth; - //float height = (bgHeight + 17f) / MenuController.ScreenHeight; - float bgHeight = 38f * MathUtil.Clamp(Size, 0, MaxItemsOnScreen); - var currentMenuItem = GetCurrentMenuItem(); - float descriptionBoxHeight = 0f; - if (currentMenuItem != null && !string.IsNullOrEmpty(currentMenuItem.Description)) - { - int count = (currentMenuItem.Description.Count((a => { return a == '\n'; })) - 1); - if (count < 1) - { - descriptionBoxHeight = 42f; - } - else - { - descriptionBoxHeight = (38f * count) + 30f; - } + SetTextFont(0); + SetTextScale(1f, (14f * 27f) / MenuController.ScreenHeight); + SetTextJustification(0); + if (LeftAligned) + { + SetTextWrap(xMin, xMax); + EndTextCommandDisplayText(xCenter, yBottom); + } + else + { + SetTextWrap(xMin, xMax); + EndTextCommandDisplayText(xCenter, yBottom); + } - bgHeight += descriptionBoxHeight; - } - float actualBgYLocation = ((38f + (38f / 2f) + (bgHeight / 2f)) / MenuController.ScreenHeight); - float x = (Position.Key + (headerSize.Key / 2f)) / MenuController.ScreenWidth; - float y = ((Position.Value + MenuItemsYOffset + ((bgHeight + 1f - (headerSize.Value)) / 2f) + 19f) / MenuController.ScreenHeight); - float width = headerSize.Key / MenuController.ScreenWidth; - float height = (headerSize.Value + bgHeight + 33f + 38f) / MenuController.ScreenHeight; - Call(DRAW_SPRITE, MenuController._texture_dict, MenuController._header_texture, x, y, width, height, 0f, 0, 0, 0, 240); - Call(DRAW_SPRITE, MenuController._texture_dict, MenuController._header_texture, x, y + actualBgYLocation - (descriptionBoxHeight / MenuController.ScreenHeight), width, 38f / MenuController.ScreenHeight, 0f, 55, 55, 55, 255); - MenuItemsYOffset += bgHeight - descriptionBoxHeight - 1f; + ResetScriptGfxAlign(); + #endregion + return descriptionYOffset; + } #endif - } - #endregion + /// + /// Draws the menu item description. + /// + /// + /// + /// The new descriptionYOffset value + private float DrawDescription(float menuItemsOffset, float descriptionYOffset) + { + if (Size < 1) + { + return descriptionYOffset; + } + var currentMenuItem = GetCurrentMenuItem(); + if (currentMenuItem != null && !string.IsNullOrEmpty(currentMenuItem.Description)) + { + #region description text + int font = 0; + float textSize = (14f * 27f) / MenuController.ScreenHeight; - #region Draw menu items that are visible in the current view. - if (Size > 0) +#if FIVEM + float textMinX = 0f + (10f / MenuController.ScreenWidth); + float textMaxX = Width / MenuController.ScreenWidth - (10f / MenuController.ScreenWidth); + float textY = menuItemsOffset / MenuController.ScreenHeight + (16f / MenuController.ScreenHeight) + descriptionYOffset; + SetScriptGfxAlign(76, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + + BeginTextCommandDisplayText("CELL_EMAIL_BCON"); + SetTextFont(font); + SetTextScale(textSize, textSize); + SetTextJustification(1); + string text = currentMenuItem.Description; + foreach (string s in CitizenFX.Core.UI.Screen.StringToArray(text)) { - foreach (var item in VisibleMenuItems) - { - item.Draw(ViewIndexOffset); - } + AddTextComponentSubstringPlayerName(s); } - #endregion - float descriptionYOffset = 0f; - -#if FIVEM - #region Up Down overflow Indicator - if (Size > 0) + float textHeight = GetTextScaleHeight(textSize, font); + if (LeftAligned) { - if (Size > MaxItemsOnScreen) - { - #region background - float width = Width / MenuController.ScreenWidth; - float height = 60f / MenuController.ScreenWidth; - float x = (Position.Key + (Width / 2f)) / MenuController.ScreenWidth; - float y = MenuItemsYOffset / MenuController.ScreenHeight + (height / 2f) + (6f / MenuController.ScreenHeight); + SetTextWrap(textMinX, textMaxX); + EndTextCommandDisplayText(textMinX, textY); + } + else + { + textMinX = GetSafeZoneSize() - ((Width - 10f) / MenuController.ScreenWidth); + textMaxX = GetSafeZoneSize() - (10f / MenuController.ScreenWidth); + SetTextWrap(textMinX, textMaxX); + EndTextCommandDisplayText(textMinX, textY); + } - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + ResetScriptGfxAlign(); - DrawRect(x, y, width, height, 0, 0, 0, 180); - descriptionYOffset = height; - ResetScriptGfxAlign(); - #endregion + SetScriptGfxAlign(76, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - #region up/down icons - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - float xMin = 0f; - float xMax = Width / MenuController.ScreenWidth; - float xCenter = 250f / MenuController.ScreenWidth; - float yTop = y - (20f / MenuController.ScreenHeight); - float yBottom = y - (10f / MenuController.ScreenHeight); - - BeginTextCommandDisplayText("STRING"); - AddTextComponentSubstringPlayerName("↑"); - - SetTextFont(0); - SetTextScale(1f, (14f * 27f) / MenuController.ScreenHeight); - SetTextJustification(0); - if (LeftAligned) - { - SetTextWrap(xMin, xMax); - EndTextCommandDisplayText(xCenter, yTop); - } - else - { - xMin = GetSafeZoneSize() - ((Width - 10f) / MenuController.ScreenWidth); - xMax = GetSafeZoneSize() - (10f / MenuController.ScreenWidth); - xCenter = GetSafeZoneSize() - (250f / MenuController.ScreenWidth); - SetTextWrap(xMin, xMax); - EndTextCommandDisplayText(xCenter, yTop); - } - - BeginTextCommandDisplayText("STRING"); - AddTextComponentSubstringPlayerName("↓"); - - SetTextFont(0); - SetTextScale(1f, (14f * 27f) / MenuController.ScreenHeight); - SetTextJustification(0); - if (LeftAligned) - { - SetTextWrap(xMin, xMax); - EndTextCommandDisplayText(xCenter, yBottom); - } - else - { - SetTextWrap(xMin, xMax); - EndTextCommandDisplayText(xCenter, yBottom); - } - - ResetScriptGfxAlign(); - #endregion - } + BeginTextCommandLineCount("CELL_EMAIL_BCON"); + SetTextScale(textSize, textSize); + SetTextJustification(1); + SetTextFont(font); + int lineCount; + foreach (string s in CitizenFX.Core.UI.Screen.StringToArray(text)) + { + AddTextComponentSubstringPlayerName(s); + } + if (LeftAligned) + { + SetTextWrap(textMinX, textMaxX); + lineCount = GetTextScreenLineCount(textMinX, textY); + } + else + { + SetTextWrap(textMinX, textMaxX); + lineCount = GetTextScreenLineCount(textMinX, textY); } - #endregion + ResetScriptGfxAlign(); #endif - #region Draw Description - if (Size > 0) - { - var currentMenuItem = GetCurrentMenuItem(); - if (currentMenuItem != null && !string.IsNullOrEmpty(currentMenuItem.Description)) - { - #region description text - int font = 0; - float textSize = (14f * 27f) / MenuController.ScreenHeight; +#if REDM + SetTextScale(textSize, textSize); + SetTextCentre(true); + float textMinX = (headerSize.Key / 2f) / MenuController.ScreenWidth; + float textMaxX = (Width - 10f) / MenuController.ScreenWidth; + float textY = menuItemsOffset / MenuController.ScreenHeight + (18f / MenuController.ScreenHeight) + (48f / MenuController.ScreenHeight); + font = 23; + Call((CitizenFX.Core.Native.Hash)0xADA9255D, font); + long _text = Call(_CREATE_VAR_STRING, 10, "LITERAL_STRING", $"{currentMenuItem.Description}"); + DisplayText(_text, textMinX, textY); +#endif + #endregion #if FIVEM - float textMinX = 0f + (10f / MenuController.ScreenWidth); - float textMaxX = Width / MenuController.ScreenWidth - (10f / MenuController.ScreenWidth); - float textY = MenuItemsYOffset / MenuController.ScreenHeight + (16f / MenuController.ScreenHeight) + descriptionYOffset; - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - - BeginTextCommandDisplayText("CELL_EMAIL_BCON"); - SetTextFont(font); - SetTextScale(textSize, textSize); - SetTextJustification(1); - string text = currentMenuItem.Description; - foreach (string s in CitizenFX.Core.UI.Screen.StringToArray(text)) - { - AddTextComponentSubstringPlayerName(s); - } - float textHeight = GetTextScaleHeight(textSize, font); - if (LeftAligned) - { - SetTextWrap(textMinX, textMaxX); - EndTextCommandDisplayText(textMinX, textY); - } - else - { - textMinX = GetSafeZoneSize() - ((Width - 10f) / MenuController.ScreenWidth); - textMaxX = GetSafeZoneSize() - (10f / MenuController.ScreenWidth); - SetTextWrap(textMinX, textMaxX); - EndTextCommandDisplayText(textMinX, textY); - } + #region background + float descWidth = Width / MenuController.ScreenWidth; + float descHeight = (textHeight + 0.005f) * lineCount + (8f / MenuController.ScreenHeight) + (2.5f / MenuController.ScreenHeight); + float descX = (Position.Key + (Width / 2f)) / MenuController.ScreenWidth; + float descY = textY - (6f / MenuController.ScreenHeight) + (descHeight / 2f); - ResetScriptGfxAlign(); + SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + DrawRect(descX, descY - (descHeight / 2f) + (2f / MenuController.ScreenHeight), descWidth, 4f / MenuController.ScreenHeight, 0, 0, 0, 200); + DrawRect(descX, descY, descWidth, descHeight, 0, 0, 0, 180); - BeginTextCommandLineCount("CELL_EMAIL_BCON"); - SetTextScale(textSize, textSize); - SetTextJustification(1); - SetTextFont(font); - int lineCount; - foreach (string s in CitizenFX.Core.UI.Screen.StringToArray(text)) - { - AddTextComponentSubstringPlayerName(s); - } - if (LeftAligned) - { - SetTextWrap(textMinX, textMaxX); - lineCount = GetTextScreenLineCount(textMinX, textY); - } - else - { - SetTextWrap(textMinX, textMaxX); - lineCount = GetTextScreenLineCount(textMinX, textY); - } + ResetScriptGfxAlign(); + #endregion - ResetScriptGfxAlign(); + descriptionYOffset += descY + (descHeight / 2f) - (4f / MenuController.ScreenHeight); #endif -#if REDM - - Call(SET_TEXT_SCALE, textSize, textSize); - Call(SET_TEXT_CENTRE, true); - float textMinX = (headerSize.Key / 2f) / MenuController.ScreenWidth; - float textMaxX = (Width - 10f) / MenuController.ScreenWidth; - float textY = MenuItemsYOffset / MenuController.ScreenHeight + (18f / MenuController.ScreenHeight) + (48f / MenuController.ScreenHeight); - font = 23; - Call((CitizenFX.Core.Native.Hash)0xADA9255D, font); - Call(_DISPLAY_TEXT, Call(_CREATE_VAR_STRING, 10, "LITERAL_STRING", $"{currentMenuItem.Description}"), textMinX, textY); + } + else + { + descriptionYOffset += menuItemsOffset / MenuController.ScreenHeight + (2f / MenuController.ScreenHeight) + descriptionYOffset; + } + return descriptionYOffset; + } -#endif +#if FIVEM + /// + /// Draws the weapon or vehicle stats panel. + /// + /// + private void DrawWeaponOrVehicleStatsPanel(float descriptionYOffset) + { + if (Size < 1) + { + return; + } + var currentItem = GetCurrentMenuItem(); + if (currentItem == null) + { + return; + } + if (currentItem is MenuListItem listItem) + { + if (listItem.ShowColorPanel || listItem.ShowOpacityPanel) + { + return; + } + } + if (!ShowWeaponStatsPanel && !ShowVehicleStatsPanel) + { + return; + } - #endregion + float textSize = (14f * 27f) / MenuController.ScreenHeight; + float width = Width / MenuController.ScreenWidth; + float height = (140f) / MenuController.ScreenHeight; + float x = ((Width / 2f) / MenuController.ScreenWidth); + float y = descriptionYOffset + (height / 2f) + (8f / MenuController.ScreenHeight); + if (Size > MaxItemsOnScreen) + { + y -= (30f / MenuController.ScreenHeight); + } + #region background + SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + DrawRect(x, y, width, height, 0, 0, 0, 180); + ResetScriptGfxAlign(); + #endregion -#if FIVEM - #region background - float descWidth = Width / MenuController.ScreenWidth; - float descHeight = (textHeight + 0.005f) * lineCount + (8f / MenuController.ScreenHeight) + (2.5f / MenuController.ScreenHeight); - float descX = (Position.Key + (Width / 2f)) / MenuController.ScreenWidth; - float descY = textY - (6f / MenuController.ScreenHeight) + (descHeight / 2f); + float bgStatBarWidth = (Width / 2f) / MenuController.ScreenWidth; + float bgStatBarX = x + (bgStatBarWidth / 2f) - (10f / MenuController.ScreenWidth); - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + if (!LeftAligned) + { + bgStatBarX = x - (bgStatBarWidth / 2f) - (10f / MenuController.ScreenWidth); + } + float barWidth; + float componentBarWidth; + float barY = y - (height / 2f) + (25f / MenuController.ScreenHeight); + float bgStatBarHeight = 10f / MenuController.ScreenHeight; + float barX; + float componentBarX; + + for (int i = 0; i < 4; i++) + { + int[] color = new int[3] { 93, 182, 229 }; + barWidth = bgStatBarWidth * (ShowWeaponStatsPanel ? WeaponStats[i] : VehicleStats[i]); + componentBarWidth = bgStatBarWidth * (ShowWeaponStatsPanel ? WeaponComponentStats[i] : VehicleUpgradeStats[i]); + if (componentBarWidth < barWidth) + { + float diff = barWidth - componentBarWidth; + barWidth -= diff; + componentBarWidth += diff; + color = new int[3] { 224, 50, 50 }; + } + if (LeftAligned) + { + barX = bgStatBarX - (bgStatBarWidth / 2f) + (barWidth / 2f); + componentBarX = bgStatBarX - (bgStatBarWidth / 2f) + (componentBarWidth / 2f); + } + else + { + barX = (barWidth * 1.5f) - bgStatBarWidth - (10f / MenuController.ScreenWidth); + componentBarX = (componentBarWidth * 1.5f) - bgStatBarWidth - (10f / MenuController.ScreenWidth); + } + SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + // bar bg + DrawRect(bgStatBarX, barY, bgStatBarWidth, bgStatBarHeight, 100, 100, 100, 180); + // component stats + DrawRect(componentBarX, barY, componentBarWidth, bgStatBarHeight, color[0], color[1], color[2], 255); + // real bar + DrawRect(barX, barY, barWidth, bgStatBarHeight, 255, 255, 255, 255); + ResetScriptGfxAlign(); + barY += 30f / MenuController.ScreenHeight; + } - DrawRect(descX, descY - (descHeight / 2f) + (2f / MenuController.ScreenHeight), descWidth, 4f / MenuController.ScreenHeight, 0, 0, 0, 200); - DrawRect(descX, descY, descWidth, descHeight, 0, 0, 0, 180); + #region weapon stats text + float textX = LeftAligned ? x - (width / 2f) + (10f / MenuController.ScreenWidth) : GetSafeZoneSize() - ((Width - 10f) / MenuController.ScreenWidth); + float textY = y - (height / 2f) + (10f / MenuController.ScreenHeight); - ResetScriptGfxAlign(); - #endregion + for (int i = 0; i < 4; i++) + { + SetScriptGfxAlign(76, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + BeginTextCommandDisplayText(ShowWeaponStatsPanel ? weaponStatNames[i] : vehicleStatNames[i]); + SetTextJustification(1); + SetTextScale(textSize, textSize); + + EndTextCommandDisplayText(textX, textY); + ResetScriptGfxAlign(); + textY += 30f / MenuController.ScreenHeight; + } + #endregion + } - descriptionYOffset += descY + (descHeight / 2f) - (4f / MenuController.ScreenHeight); -#endif - } - else + /// + /// Draws the Opacity and Color panels on MenuListItems. + /// + /// + private void DrawColorAndOpacityPanel(float descriptionYOffset) + { + if (Size < 1) + { + return; + } + var currentItem = GetCurrentMenuItem(); + if (currentItem == null) + { + return; + } + if (currentItem is MenuListItem listItem) + { + // OPACITY PANEL + if (listItem.ShowOpacityPanel) + { + BeginScaleformMovieMethod(OpacityPanelScaleform, "SET_TITLE"); + PushScaleformMovieMethodParameterString("Opacity"); + PushScaleformMovieMethodParameterString(""); + ScaleformMovieMethodAddParamInt(listItem.ListIndex * 10); // opacity percent + EndScaleformMovieMethod(); + + float width = Width / MenuController.ScreenWidth; + float height = ((700f / 500f) * Width) / MenuController.ScreenHeight; + float x = ((Width / 2f) / MenuController.ScreenWidth); + float y = descriptionYOffset + (height / 2f) + (4f / MenuController.ScreenHeight); + if (Size > MaxItemsOnScreen) { - descriptionYOffset += MenuItemsYOffset / MenuController.ScreenHeight + (2f / MenuController.ScreenHeight) + descriptionYOffset; + y -= (30f / MenuController.ScreenHeight); } + SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + DrawScaleformMovie(OpacityPanelScaleform, x, y, width, height, 255, 255, 255, 255, 0); + ResetScriptGfxAlign(); } - #endregion -#if FIVEM - #region Draw Weapon Stats + // COLOR PALLETE + else if (listItem.ShowColorPanel) { - if (Size > 0) + BeginScaleformMovieMethod(ColorPanelScaleform, "SET_TITLE"); + PushScaleformMovieMethodParameterString("Opacity"); + BeginTextCommandScaleformString("FACE_COLOUR"); + AddTextComponentInteger(listItem.ListIndex + 1); + AddTextComponentInteger(listItem.ItemsCount); + EndTextCommandScaleformString(); + ScaleformMovieMethodAddParamInt(0); // opacity percent unused + ScaleformMovieMethodAddParamBool(true); + EndScaleformMovieMethod(); + + BeginScaleformMovieMethod(ColorPanelScaleform, "SET_DATA_SLOT_EMPTY"); + EndScaleformMovieMethod(); + + for (int i = 0; i < 64; i++) { - var currentItem = GetCurrentMenuItem(); - if (currentItem != null) + var r = 0; + var g = 0; + var b = 0; + if (listItem.ColorPanelColorType == MenuListItem.ColorPanelType.Hair) { - if (currentItem is MenuListItem listItem) - { - if (listItem.ShowColorPanel || listItem.ShowOpacityPanel) - { - goto SKIP_WEAPON_STATS; - } - } + GetHairRgbColor(i, ref r, ref g, ref b); // _GetHairRgbColor } else { - goto SKIP_WEAPON_STATS; + GetMakeupRgbColor(i, ref r, ref g, ref b); // _GetMakeupRgbColor } - } - if (ShowWeaponStatsPanel) - { - float textSize = (14f * 27f) / MenuController.ScreenHeight; - float width = Width / MenuController.ScreenWidth; - float height = (140f) / MenuController.ScreenHeight; - float x = ((Width / 2f) / MenuController.ScreenWidth); - float y = descriptionYOffset + (height / 2f) + (8f / MenuController.ScreenHeight); - if (Size > MaxItemsOnScreen) - { - y -= (30f / MenuController.ScreenHeight); - } + BeginScaleformMovieMethod(ColorPanelScaleform, "SET_DATA_SLOT"); + ScaleformMovieMethodAddParamInt(i); // index + ScaleformMovieMethodAddParamInt(r); // r + ScaleformMovieMethodAddParamInt(g); // g + ScaleformMovieMethodAddParamInt(b); // b + EndScaleformMovieMethod(); + } + BeginScaleformMovieMethod(ColorPanelScaleform, "DISPLAY_VIEW"); + EndScaleformMovieMethod(); - #region background - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - DrawRect(x, y, width, height, 0, 0, 0, 180); - ResetScriptGfxAlign(); - #endregion + BeginScaleformMovieMethod(ColorPanelScaleform, "SET_HIGHLIGHT"); + ScaleformMovieMethodAddParamInt(listItem.ListIndex); + EndScaleformMovieMethod(); - float bgStatBarWidth = (Width / 2f) / MenuController.ScreenWidth; - float bgStatBarX = x + (bgStatBarWidth / 2f) - (10f / MenuController.ScreenWidth); + BeginScaleformMovieMethod(ColorPanelScaleform, "SHOW_OPACITY"); + ScaleformMovieMethodAddParamBool(false); + ScaleformMovieMethodAddParamBool(true); + EndScaleformMovieMethod(); - if (!LeftAligned) - { - bgStatBarX = x - (bgStatBarWidth / 2f) - (10f / MenuController.ScreenWidth); - } - float barWidth; - float componentBarWidth; - float barY = y - (height / 2f) + (25f / MenuController.ScreenHeight); - float bgStatBarHeight = 10f / MenuController.ScreenHeight; - float barX; - float componentBarX; - #region damage bar - barWidth = bgStatBarWidth * WeaponStats[0]; - componentBarWidth = bgStatBarWidth * WeaponComponentStats[0]; - if (LeftAligned) - { - barX = bgStatBarX - (bgStatBarWidth / 2f) + (barWidth / 2f); - componentBarX = bgStatBarX - (bgStatBarWidth / 2f) + (componentBarWidth / 2f); - } - else - { - barX = (barWidth * 1.5f) - bgStatBarWidth - (10f / MenuController.ScreenWidth); - componentBarX = (componentBarWidth * 1.5f) - bgStatBarWidth - (10f / MenuController.ScreenWidth); - } - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - // bar bg - DrawRect(bgStatBarX, barY, bgStatBarWidth, bgStatBarHeight, 100, 100, 100, 180); - // component stats - DrawRect(componentBarX, barY, componentBarWidth, bgStatBarHeight, 93, 182, 229, 255); - // real bar - DrawRect(barX, barY, barWidth, bgStatBarHeight, 255, 255, 255, 255); - ResetScriptGfxAlign(); - #endregion - - #region fire rate bar - barWidth = bgStatBarWidth * WeaponStats[1]; - componentBarWidth = bgStatBarWidth * WeaponComponentStats[1]; - barY += 30f / MenuController.ScreenHeight; - if (LeftAligned) - { - barX = bgStatBarX - (bgStatBarWidth / 2f) + (barWidth / 2f); - componentBarX = bgStatBarX - (bgStatBarWidth / 2f) + (componentBarWidth / 2f); - } - else - { - barX = (barWidth * 1.5f) - bgStatBarWidth - (10f / MenuController.ScreenWidth); - componentBarX = (componentBarWidth * 1.5f) - bgStatBarWidth - (10f / MenuController.ScreenWidth); - } - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - // bar bg - DrawRect(bgStatBarX, barY, bgStatBarWidth, bgStatBarHeight, 100, 100, 100, 180); - // component stats - DrawRect(componentBarX, barY, componentBarWidth, bgStatBarHeight, 93, 182, 229, 255); - // real bar - DrawRect(barX, barY, barWidth, bgStatBarHeight, 255, 255, 255, 255); - ResetScriptGfxAlign(); - #endregion - - #region accuracy bar - barWidth = bgStatBarWidth * WeaponStats[2]; - componentBarWidth = bgStatBarWidth * WeaponComponentStats[2]; - barY += 30f / MenuController.ScreenHeight; - if (LeftAligned) - { - barX = bgStatBarX - (bgStatBarWidth / 2f) + (barWidth / 2f); - componentBarX = bgStatBarX - (bgStatBarWidth / 2f) + (componentBarWidth / 2f); - } - else - { - barX = (barWidth * 1.5f) - bgStatBarWidth - (10f / MenuController.ScreenWidth); - componentBarX = (componentBarWidth * 1.5f) - bgStatBarWidth - (10f / MenuController.ScreenWidth); - } - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - // bar bg - DrawRect(bgStatBarX, barY, bgStatBarWidth, bgStatBarHeight, 100, 100, 100, 180); - // component stats - DrawRect(componentBarX, barY, componentBarWidth, bgStatBarHeight, 93, 182, 229, 255); - // real bar - DrawRect(barX, barY, barWidth, bgStatBarHeight, 255, 255, 255, 255); - ResetScriptGfxAlign(); - #endregion + float width = Width / MenuController.ScreenWidth; + float height = ((700f / 500f) * Width) / MenuController.ScreenHeight; + float x = ((Width / 2f) / MenuController.ScreenWidth); + float y = descriptionYOffset + (height / 2f) + (4f / MenuController.ScreenHeight); + if (Size > MaxItemsOnScreen) + { + y -= (30f / MenuController.ScreenHeight); + } - #region range bar - barWidth = bgStatBarWidth * WeaponStats[3]; - componentBarWidth = bgStatBarWidth * WeaponComponentStats[3]; - barY += 30f / MenuController.ScreenHeight; - if (LeftAligned) - { - barX = bgStatBarX - (bgStatBarWidth / 2f) + (barWidth / 2f); - componentBarX = bgStatBarX - (bgStatBarWidth / 2f) + (componentBarWidth / 2f); - } - else - { - barX = (barWidth * 1.5f) - bgStatBarWidth - (10f / MenuController.ScreenWidth); - componentBarX = (componentBarWidth * 1.5f) - bgStatBarWidth - (10f / MenuController.ScreenWidth); - } - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - // bar bg - DrawRect(bgStatBarX, barY, bgStatBarWidth, bgStatBarHeight, 100, 100, 100, 180); - // component stats - DrawRect(componentBarX, barY, componentBarWidth, bgStatBarHeight, 93, 182, 229, 255); - // real bar - DrawRect(barX, barY, barWidth, bgStatBarHeight, 255, 255, 255, 255); - ResetScriptGfxAlign(); - #endregion + SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + DrawScaleformMovie(ColorPanelScaleform, x, y, width, height, 255, 255, 255, 255, 0); + ResetScriptGfxAlign(); + } + } + } +#endif - #region weapon stats text - float textX = LeftAligned ? x - (width / 2f) + (10f / MenuController.ScreenWidth) : GetSafeZoneSize() - ((Width - 10f) / MenuController.ScreenWidth); - float textY = y - (height / 2f) + (10f / MenuController.ScreenHeight); - - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - BeginTextCommandDisplayText("PM_DAMAGE"); - SetTextJustification(1); - SetTextScale(textSize, textSize); - - EndTextCommandDisplayText(textX, textY); - ResetScriptGfxAlign(); - - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - BeginTextCommandDisplayText("PM_FIRERATE"); - SetTextJustification(1); - SetTextScale(textSize, textSize); - textY += 30f / MenuController.ScreenHeight; - EndTextCommandDisplayText(textX, textY); - ResetScriptGfxAlign(); - - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - BeginTextCommandDisplayText("PM_ACCURACY"); - SetTextJustification(1); - SetTextScale(textSize, textSize); - textY += 30f / MenuController.ScreenHeight; - EndTextCommandDisplayText(textX, textY); - ResetScriptGfxAlign(); - - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - BeginTextCommandDisplayText("PM_RANGE"); - SetTextJustification(1); - SetTextScale(textSize, textSize); - textY += 30f / MenuController.ScreenHeight; - EndTextCommandDisplayText(textX, textY); - ResetScriptGfxAlign(); - #endregion + /// + /// Calls all Draw functions for all visible menu components. + /// + internal async Task Draw() + { + if (!( + IsScreenFadedIn() && +#if FIVEM + !Game.IsPaused && + !Game.PlayerPed.IsDead && + !IsPlayerSwitchInProgress() +#endif +#if REDM + !IsPauseMenuActive() && + !IsEntityDead(PlayerPedId()) +#endif + )) + { + return; + } + ProcessButtonPressHandlers(); - } - } + MenuItemsYOffset = 0f; + if (MenuController.SetDrawOrder) + { + SetScriptGfxDrawOrder(1); + } + MenuItemsYOffset = await DrawHeader(MenuItemsYOffset); - SKIP_WEAPON_STATS: - #endregion + MenuItemsYOffset = DrawSubtitle(MenuItemsYOffset); - #region Draw Color and opacity palletes - if (Size > 0) - { - var currentItem = GetCurrentMenuItem(); - if (currentItem != null) - if (currentItem is MenuListItem listItem) - { - /// OPACITY PANEL - if (listItem.ShowOpacityPanel) - { - BeginScaleformMovieMethod(OpacityPanelScaleform, "SET_TITLE"); - PushScaleformMovieMethodParameterString("Opacity"); - PushScaleformMovieMethodParameterString(""); - ScaleformMovieMethodAddParamInt(listItem.ListIndex * 10); // opacity percent - EndScaleformMovieMethod(); - - float width = Width / MenuController.ScreenWidth; - float height = ((700f / 500f) * Width) / MenuController.ScreenHeight; - float x = ((Width / 2f) / MenuController.ScreenWidth); - float y = descriptionYOffset + (height / 2f) + (4f / MenuController.ScreenHeight); - if (Size > MaxItemsOnScreen) - { - y -= (30f / MenuController.ScreenHeight); - } - - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - DrawScaleformMovie(OpacityPanelScaleform, x, y, width, height, 255, 255, 255, 255, 0); - ResetScriptGfxAlign(); - } - - /// COLOR PALLETE - else if (listItem.ShowColorPanel) - { - BeginScaleformMovieMethod(ColorPanelScaleform, "SET_TITLE"); - PushScaleformMovieMethodParameterString("Opacity"); - BeginTextCommandScaleformString("FACE_COLOUR"); - AddTextComponentInteger(listItem.ListIndex + 1); - AddTextComponentInteger(listItem.ItemsCount); - EndTextCommandScaleformString(); - ScaleformMovieMethodAddParamInt(0); // opacity percent unused - ScaleformMovieMethodAddParamBool(true); - EndScaleformMovieMethod(); - - BeginScaleformMovieMethod(ColorPanelScaleform, "SET_DATA_SLOT_EMPTY"); - EndScaleformMovieMethod(); - - for (int i = 0; i < 64; i++) - { - var r = 0; - var g = 0; - var b = 0; - if (listItem.ColorPanelColorType == MenuListItem.ColorPanelType.Hair) - { - GetHairRgbColor(i, ref r, ref g, ref b); // _GetHairRgbColor - } - else - { - GetMakeupRgbColor(i, ref r, ref g, ref b); // _GetMakeupRgbColor - } - - BeginScaleformMovieMethod(ColorPanelScaleform, "SET_DATA_SLOT"); - ScaleformMovieMethodAddParamInt(i); // index - ScaleformMovieMethodAddParamInt(r); // r - ScaleformMovieMethodAddParamInt(g); // g - ScaleformMovieMethodAddParamInt(b); // b - EndScaleformMovieMethod(); - } - - BeginScaleformMovieMethod(ColorPanelScaleform, "DISPLAY_VIEW"); - EndScaleformMovieMethod(); - - BeginScaleformMovieMethod(ColorPanelScaleform, "SET_HIGHLIGHT"); - ScaleformMovieMethodAddParamInt(listItem.ListIndex); - EndScaleformMovieMethod(); - - BeginScaleformMovieMethod(ColorPanelScaleform, "SHOW_OPACITY"); - ScaleformMovieMethodAddParamBool(false); - ScaleformMovieMethodAddParamBool(true); - EndScaleformMovieMethod(); - - float width = Width / MenuController.ScreenWidth; - float height = ((700f / 500f) * Width) / MenuController.ScreenHeight; - float x = ((Width / 2f) / MenuController.ScreenWidth); - float y = descriptionYOffset + (height / 2f) + (4f / MenuController.ScreenHeight); - if (Size > MaxItemsOnScreen) - { - y -= (30f / MenuController.ScreenHeight); - } - - SetScriptGfxAlign(LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - DrawScaleformMovie(ColorPanelScaleform, x, y, width, height, 255, 255, 255, 255, 0); - ResetScriptGfxAlign(); - } - } - } + MenuItemsYOffset = DrawBackgroundGradient(MenuItemsYOffset); - #endregion + DrawActiveMenuItems(); + float descriptionYOffset = 0f; +#if FIVEM + descriptionYOffset = DrawUpDownOverflowIndicators(); #endif - if (MenuController.SetDrawOrder) - SetScriptGfxDrawOrder(0); + descriptionYOffset = DrawDescription(MenuItemsYOffset, descriptionYOffset); +#if FIVEM + DrawWeaponOrVehicleStatsPanel(descriptionYOffset); + DrawColorAndOpacityPanel(descriptionYOffset); +#endif + if (MenuController.SetDrawOrder) + { + SetScriptGfxDrawOrder(0); } - await Task.FromResult(0); } #endregion } diff --git a/MenuAPI/MenuAPI.csproj b/MenuAPI/MenuAPI.csproj index 17febc4..71928ef 100644 --- a/MenuAPI/MenuAPI.csproj +++ b/MenuAPI/MenuAPI.csproj @@ -11,23 +11,30 @@ Tom Grobbe An advanced Menu API for FiveM C# resources. An advanced Menu API for RedM C# resources. - Copyright Tom Grobbe 2018-2020 + Copyright Tom Grobbe 2018-2026 https://github.com/tomgrobbe/menuapi/ https://github.com/tomgrobbe/menuapi/ FiveM MenuAPI RedM MenuAPI - license.txt - + FIVEM - + + FIVEM;DEBUG + + + REDM + + REDM;DEBUG + + @@ -35,7 +42,7 @@ - + runtime diff --git a/MenuAPI/MenuAPI.nuspec b/MenuAPI/MenuAPI.nuspec index c426e41..c8c911b 100644 --- a/MenuAPI/MenuAPI.nuspec +++ b/MenuAPI/MenuAPI.nuspec @@ -8,7 +8,7 @@ Tom Grobbe https://github.com/tomgrobbe/menuapi $description$ - Copyright Tom Grobbe 2018-2020. + Copyright Tom Grobbe 2018-2026. $title$ diff --git a/MenuAPI/MenuController.cs b/MenuAPI/MenuController.cs index 6ae6b96..6b1a913 100644 --- a/MenuAPI/MenuController.cs +++ b/MenuAPI/MenuController.cs @@ -1,18 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using CitizenFX.Core; using static CitizenFX.Core.Native.API; -using static CitizenFX.Core.Native.Function; -using static CitizenFX.Core.Native.Hash; namespace MenuAPI { public class MenuController : BaseScript { public static List Menus { get; protected set; } = new List(); + internal static HashSet VisibleMenus { get; } = new HashSet(); #if FIVEM public const string _texture_dict = "commonmenu"; public const string _header_texture = "interaction_bgd"; @@ -33,6 +31,7 @@ public class MenuController : BaseScript "mprankbadge", "mpcarhud", "mpcarhud2", + "shared" #endif #if REDM "menu_textures", @@ -51,30 +50,38 @@ public class MenuController : BaseScript public static float ScreenHeight => 1080; public static bool DisableMenuButtons { get; set; } = false; #if FIVEM - public static bool AreMenuButtonsEnabled => Menus.Any((m) => m.Visible) && !Game.IsPaused && CitizenFX.Core.UI.Screen.Fading.IsFadedIn && !IsPlayerSwitchInProgress() && !DisableMenuButtons && !Game.Player.IsDead; + public static bool AreMenuButtonsEnabled => IsAnyMenuOpen() && !Game.IsPaused && CitizenFX.Core.UI.Screen.Fading.IsFadedIn && !IsPlayerSwitchInProgress() && !DisableMenuButtons && !Game.Player.IsDead; #endif #if REDM public static bool AreMenuButtonsEnabled => - Menus.Any((m) => m.Visible) && - !Call(IS_PAUSE_MENU_ACTIVE) && - Call(IS_SCREEN_FADED_IN) && + IsAnyMenuOpen() && + IsScreenFadedIn() && + !IsPauseMenuActive() && !DisableMenuButtons && - !Call(IS_ENTITY_DEAD, PlayerPedId()); + !IsEntityDead(PlayerPedId()); #endif + public static bool NavigateMenuUsingArrows { get; set; } = true; public static bool EnableManualGCs { get; set; } = true; public static bool DontOpenAnyMenu { get; set; } = false; public static bool PreventExitingMenu { get; set; } = false; public static bool DisableBackButton { get; set; } = false; public static bool SetDrawOrder { get; set; } = true; + public static bool MenuToggleKeyIsValid + { + get + { + int keyInt = (int)MenuToggleKey; + return keyInt >= 0 && keyInt <= 402; // 402 is max control value allowed after TU3788 + } + } public static Control MenuToggleKey { get; set; } #if FIVEM - = Control.InteractionMenu + = Control.InteractionMenu; #endif #if REDM - = Control.PlayerMenu + = Control.PlayerMenu; #endif - ; public static bool EnableMenuToggleKeyOnController { get; set; } = true; @@ -92,7 +99,6 @@ public class MenuController : BaseScript private static MenuAlignmentOption _alignment = MenuAlignmentOption.Left; public static MenuAlignmentOption MenuAlignment { - get { return _alignment; @@ -188,14 +194,12 @@ public static void AddSubmenu(Menu parent, Menu child) child.ParentMenu = parent; } - /// /// Loads the texture dict for the common menu sprites. /// /// private static async Task LoadAssets() { -#if FIVEM menuTextureAssets.ForEach(asset => { if (!HasStreamedTextureDictLoaded(asset)) @@ -207,20 +211,6 @@ private static async Task LoadAssets() { await Delay(0); } -#endif -#if REDM - menuTextureAssets.ForEach(asset => - { - if (!Call(HAS_STREAMED_TEXTURE_DICT_LOADED, asset)) - { - Call(REQUEST_STREAMED_TEXTURE_DICT, asset, false); - } - }); - while (menuTextureAssets.Any(asset => { return !Call(HAS_STREAMED_TEXTURE_DICT_LOADED, asset); })) - { - await Delay(0); - } -#endif } /// @@ -228,24 +218,16 @@ private static async Task LoadAssets() /// private static void UnloadAssets() { -#if FIVEM - menuTextureAssets.ForEach(asset => - { - if (HasStreamedTextureDictLoaded(asset)) - { - SetStreamedTextureDictAsNoLongerNeeded(asset); - } - }); -#endif -#if REDM menuTextureAssets.ForEach(asset => { - if (Call(HAS_STREAMED_TEXTURE_DICT_LOADED, asset)) + if (!string.IsNullOrEmpty(asset)) { - Call(SET_STREAMED_TEXTURE_DICT_AS_NO_LONGER_NEEDED, asset); + if (HasStreamedTextureDictLoaded(asset)) + { + SetStreamedTextureDictAsNoLongerNeeded(asset); + } } }); -#endif } /// @@ -254,8 +236,10 @@ private static void UnloadAssets() /// public static Menu GetCurrentMenu() { - if (Menus.Any((m) => m.Visible)) - return Menus.Find((m) => m.Visible); + if (IsAnyMenuOpen()) + { + return VisibleMenus.FirstOrDefault(); + } return null; } @@ -263,7 +247,7 @@ public static Menu GetCurrentMenu() /// Returns true if any menu is currently open. /// /// - public static bool IsAnyMenuOpen() => Menus.Any((m) => m.Visible); + public static bool IsAnyMenuOpen() => VisibleMenus.Any(); #region Process Menu Buttons @@ -273,84 +257,96 @@ public static Menu GetCurrentMenu() /// private async Task ProcessMainButtons() { - if (IsAnyMenuOpen()) + if (!IsAnyMenuOpen()) { -#if REDM - if (Call(IS_PAUSE_MENU_ACTIVE)) - { - return; - } -#endif - var currentMenu = GetCurrentMenu(); - if (currentMenu != null && !DontOpenAnyMenu) - { - if (PreventExitingMenu) - { + return; + } + if (IsPauseMenuActive()) + { + return; + } + var currentMenu = GetCurrentMenu(); + if (currentMenu == null || DontOpenAnyMenu) + { + return; + } #if FIVEM - Game.DisableControlThisFrame(0, Control.FrontendPause); - Game.DisableControlThisFrame(0, Control.FrontendPauseAlternate); + Game.DisableControlThisFrame(0, Control.MultiplayerInfo); #endif -#if REDM - Call(DISABLE_CONTROL_ACTION, 0, Control.FrontendPause, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.FrontendPauseAlternate, true); -#endif - } + HandlePreventExit(); + if (!currentMenu.Visible || !AreMenuButtonsEnabled) + { + return; + } + await HandleMainNavigationButtons(currentMenu); + } - if (currentMenu.Visible && AreMenuButtonsEnabled) - { - // Select / Enter - if ( + private async Task HandleMainNavigationButtons(Menu currentMenu) + { + // Select / Enter + if ( #if FIVEM - Game.IsDisabledControlJustReleased(0, Control.FrontendAccept) || - Game.IsControlJustReleased(0, Control.FrontendAccept) || - Game.IsDisabledControlJustReleased(0, Control.VehicleMouseControlOverride) || - Game.IsControlJustReleased(0, Control.VehicleMouseControlOverride) + Game.IsDisabledControlJustReleased(0, Control.FrontendAccept) || + Game.IsControlJustReleased(0, Control.FrontendAccept) || + Game.IsDisabledControlJustReleased(0, Control.VehicleMouseControlOverride) || + Game.IsControlJustReleased(0, Control.VehicleMouseControlOverride) #endif #if REDM - Call(IS_DISABLED_CONTROL_JUST_RELEASED, 0, Control.FrontendAccept) || - Call(IS_CONTROL_JUST_RELEASED, 0, Control.FrontendAccept) + IsDisabledControlJustReleased(0, (uint)Control.FrontendAccept) || + IsControlJustReleased(0, (uint)Control.FrontendAccept) #endif - ) - { - if (currentMenu.Size > 0) - { - currentMenu.SelectItem(currentMenu.CurrentIndex); - } - } - // Cancel / Go Back - else if ( + ) + { + if (currentMenu.Size > 0) + { + currentMenu.SelectItem(currentMenu.CurrentIndex); + } + } + // Cancel / Go Back + else if ( + !DisableBackButton && #if FIVEM - Game.IsDisabledControlJustReleased(0, Control.PhoneCancel) + Game.IsDisabledControlJustReleased(0, Control.PhoneCancel) #endif #if REDM - Call(IS_DISABLED_CONTROL_JUST_RELEASED, 0, Control.FrontendCancel) + IsDisabledControlJustReleased(0, (uint)Control.FrontendCancel) #endif - && !DisableBackButton) - { - // Wait for the next frame to make sure the "cinematic camera" button doesn't get "re-enabled" before the menu gets closed. - await Delay(0); - currentMenu.GoBack(); - } - else if ( + ) + { + // Wait for the next frame to make sure the "cinematic camera" button doesn't get "re-enabled" before the menu gets closed. + await Delay(0); + currentMenu.GoBack(); + } + else if ( + PreventExitingMenu && !DisableBackButton && #if FIVEM - Game.IsDisabledControlJustReleased(0, Control.PhoneCancel) + Game.IsDisabledControlJustReleased(0, Control.PhoneCancel) #endif #if REDM - Call(IS_DISABLED_CONTROL_JUST_RELEASED, 0, Control.CellphoneCancel) + IsDisabledControlJustReleased(0, (uint)Control.CellphoneCancel) #endif - && PreventExitingMenu && !DisableBackButton) - { - // if there's a parent menu, allow going back to that, but don't allow a 'top-level' menu to be closed. - if (currentMenu.ParentMenu != null) - { - currentMenu.GoBack(); - } - await Delay(0); - } - } + ) + { + // if there's a parent menu, allow going back to that, but don't allow a 'top-level' menu to be closed. + if (currentMenu.ParentMenu != null) + { + currentMenu.GoBack(); } + await Delay(0); + } + } + + private void HandlePreventExit() + { + if (PreventExitingMenu) + { #if FIVEM - Game.DisableControlThisFrame(0, Control.MultiplayerInfo); + Game.DisableControlThisFrame(0, Control.FrontendPause); + Game.DisableControlThisFrame(0, Control.FrontendPauseAlternate); +#endif +#if REDM + DisableControlAction(0, (uint)Control.FrontendPause, true); + DisableControlAction(0, (uint)Control.FrontendPauseAlternate, true); #endif } } @@ -361,7 +357,6 @@ private async Task ProcessMainButtons() /// private bool IsUpPressed() { - // Return false if the buttons are not currently enabled. if (!AreMenuButtonsEnabled) { return false; @@ -389,16 +384,16 @@ private bool IsUpPressed() } #endif #if REDM - if (Call(IS_CONTROL_PRESSED, 0, Control.FrontendUp) || - Call(IS_DISABLED_CONTROL_PRESSED, 0, Control.FrontendUp) || - Call(IS_CONTROL_PRESSED, 0, Control.CellphoneScrollBackward) || - Call(IS_DISABLED_CONTROL_PRESSED, 0, Control.CellphoneScrollBackward) - ) + if ( + IsControlPressed(0, (uint)Control.FrontendUp) || + IsDisabledControlPressed(0, (uint)Control.FrontendUp) || + IsControlPressed(0, (uint)Control.CellphoneScrollBackward) || + IsDisabledControlPressed(0, (uint)Control.CellphoneScrollBackward) + ) { return true; } #endif - // return false if none of the conditions matched. return false; } @@ -408,7 +403,6 @@ private bool IsUpPressed() /// private bool IsDownPressed() { - // Return false if the buttons are not currently enabled. if (!AreMenuButtonsEnabled) { return false; @@ -436,17 +430,16 @@ private bool IsDownPressed() } #endif #if REDM - if (Call(IS_CONTROL_PRESSED, 0, Control.FrontendDown) || - Call(IS_DISABLED_CONTROL_PRESSED, 0, Control.FrontendDown) || - Call(IS_CONTROL_PRESSED, 0, Control.CellphoneScrollForward) || - Call(IS_DISABLED_CONTROL_PRESSED, 0, Control.CellphoneScrollForward) - ) + if ( + IsControlPressed(0, (uint)Control.FrontendDown) || + IsDisabledControlPressed(0, (uint)Control.FrontendDown) || + IsControlPressed(0, (uint)Control.CellphoneScrollForward) || + IsDisabledControlPressed(0, (uint)Control.CellphoneScrollForward) + ) { return true; } #endif - - // return false if none of the conditions matched. return false; } @@ -456,24 +449,52 @@ private bool IsDownPressed() /// private async Task ProcessToggleMenuButton() { + if (!MenuToggleKeyIsValid) + { + await Delay(1_500); + return; + } #if FIVEM + await ProcessToggleMenuButtonFiveM(); +#endif +#if REDM + ProcessToggleMenuButtonRedM(); + await Task.FromResult(0); +#endif + } +#if REDM + private void ProcessToggleMenuButtonRedM() + { + DisableControlAction(0, (uint)MenuToggleKey, true); + if ( + !IsPauseMenuActive() && + IsScreenFadedIn() && + !IsAnyMenuOpen() && + !DisableMenuButtons && + !IsEntityDead(PlayerPedId()) && + IsDisabledControlJustReleased(0, (uint)MenuToggleKey) + ) + { + if (MainMenu != null) + { + MainMenu.OpenMenu(); + } + else + { + Debug.WriteLine($"[ERROR] [{GetCurrentResourceName()}] [MenuAPI] MainMenu is null, so we can't open it! Make sure that MenuController.MainMenu is set to a valid Menu which is not null!"); + } + } + } +#endif +#if FIVEM + private async Task ProcessToggleMenuButtonFiveM() + { if (!Game.IsPaused && !IsPauseMenuRestarting() && IsScreenFadedIn() && !IsPlayerSwitchInProgress() && !Game.Player.IsDead && !DisableMenuButtons) { if (IsAnyMenuOpen()) { - Game.DisableControlThisFrame(0, MenuToggleKey); - if (Game.CurrentInputMode == InputMode.MouseAndKeyboard) - { - if ((Game.IsControlJustPressed(0, MenuToggleKey) || Game.IsDisabledControlJustPressed(0, MenuToggleKey)) && !PreventExitingMenu) - { - var menu = GetCurrentMenu(); - if (menu != null) - { - menu.CloseMenu(); - } - } - } + DisableMenuKeyThisFrame(); } else { @@ -482,56 +503,16 @@ private async Task ProcessToggleMenuButton() if (!EnableMenuToggleKeyOnController) return; - int tmpTimer = GetGameTimer(); - while ((Game.IsControlPressed(0, Control.InteractionMenu) || Game.IsDisabledControlPressed(0, Control.InteractionMenu)) && !Game.IsPaused && IsScreenFadedIn() && !Game.Player.IsDead && !IsPlayerSwitchInProgress() && !DontOpenAnyMenu) - { - if (GetGameTimer() - tmpTimer > 400) - { - if (MainMenu != null) - { - MainMenu.OpenMenu(); - } - else - { - if (Menus.Count > 0) - { - Menus[0].OpenMenu(); - } - } - break; - } - await Delay(0); - } + await HandleMenuToggleKeyForController(); } else { - if ((Game.IsControlJustPressed(0, MenuToggleKey) || Game.IsDisabledControlJustPressed(0, MenuToggleKey)) && !Game.IsPaused && IsScreenFadedIn() && !Game.Player.IsDead && !IsPlayerSwitchInProgress() && !DontOpenAnyMenu) - { - if (Menus.Count > 0) - { - if (MainMenu != null) - { - MainMenu.OpenMenu(); - } - else - { - Menus[0].OpenMenu(); - } - } - } + HandleMenuToggleKeyForKeyboard(); } } } -#endif -#if REDM - Call(DISABLE_CONTROL_ACTION, 0, MenuToggleKey, true); - if (!Call(IS_PAUSE_MENU_ACTIVE) && Call(IS_SCREEN_FADED_IN) && !IsAnyMenuOpen() && !DisableMenuButtons && !Call(IS_ENTITY_DEAD, PlayerPedId()) && Call(IS_DISABLED_CONTROL_JUST_RELEASED, 0, MenuToggleKey)) - { - MainMenu.OpenMenu(); - } -#endif - await Task.FromResult(0); } +#endif /// /// Process left/right/up/down buttons (also holding down buttons will speed up after 3 iterations) @@ -548,208 +529,319 @@ private async Task ProcessDirectionalButtons() // Get the currently open menu. var currentMenu = GetCurrentMenu(); // If it exists. - if (currentMenu != null && !DontOpenAnyMenu && currentMenu.Size > 0) + if (currentMenu == null || DontOpenAnyMenu || currentMenu.Size < 1 || !currentMenu.Visible) { - if (currentMenu.Visible) - { - // Check if the Go Up controls are pressed. - if (IsUpPressed()) - { - // Update the currently selected item to the new one. - currentMenu.GoUp(); - - // Get the current game time. - var time = GetGameTimer(); - var times = 0; - var delay = 200; - - // Do the following as long as the controls are being pressed. - while (IsUpPressed() && IsAnyMenuOpen() && GetCurrentMenu() != null) - { - // Update the current menu. - currentMenu = GetCurrentMenu(); - - // Check if the game time has changed by "delay" amount. - if (GetGameTimer() - time > delay) - { - // Increment the "changed indexes" counter - times++; - - // If the controls are still being held down after moving 3 indexes, reduce the delay between index changes. - if (times > 2) - { - delay = 150; - } - if (times > 5) - { - delay = 100; - } - if (times > 25) - { - delay = 50; - } - if (times > 60) - { - delay = 25; - } - - // Update the currently selected item to the new one. - currentMenu.GoUp(); - - // Reset the time to the current game timer. - time = GetGameTimer(); - } - - // Wait for the next game tick. - await Delay(0); - } - } - - // Check if the Go Down controls are pressed. - else if (IsDownPressed()) - { - currentMenu.GoDown(); - - var time = GetGameTimer(); - var times = 0; - var delay = 200; - while (IsDownPressed() && GetCurrentMenu() != null) - { - currentMenu = GetCurrentMenu(); - if (GetGameTimer() - time > delay) - { - times++; - if (times > 2) - { - delay = 150; - } - if (times > 5) - { - delay = 100; - } - if (times > 25) - { - delay = 50; - } - if (times > 60) - { - delay = 25; - } - - currentMenu.GoDown(); - - time = GetGameTimer(); - } - await Delay(0); - } - } + return; + } + if (IsUpPressed()) + { + await HandleUpNavigation(currentMenu); + } + else if (IsDownPressed()) + { + await HandleDownNavigation(currentMenu); + } - // Check if the Go Left controls are pressed. + // Check if the Go Left controls are pressed. + else if ( + AreMenuButtonsEnabled && ( #if FIVEM - else if (Game.IsDisabledControlJustPressed(0, Control.PhoneLeft) || Game.IsControlJustPressed(0, Control.PhoneLeft)) + Game.IsDisabledControlJustPressed(0, Control.PhoneLeft) || + Game.IsControlJustPressed(0, Control.PhoneLeft) #endif #if REDM - else if (Call(IS_DISABLED_CONTROL_JUST_PRESSED, 0, Control.FrontendLeft) || Call(IS_CONTROL_JUST_PRESSED, 0, Control.FrontendLeft)) + IsDisabledControlJustPressed(0, (uint)Control.FrontendLeft) || + IsControlJustPressed(0, (uint)Control.FrontendLeft) #endif - { - var item = currentMenu.GetMenuItems()[currentMenu.CurrentIndex]; - if (item.Enabled) - { - currentMenu.GoLeft(); - var time = GetGameTimer(); - var times = 0; - var delay = 200; + ) + ) + { + await HandleLeftNavigation(currentMenu); + } + + // Check if the Go Right controls are pressed. + else if ( + AreMenuButtonsEnabled && ( #if FIVEM - while ((Game.IsDisabledControlPressed(0, Control.PhoneLeft) || Game.IsControlPressed(0, Control.PhoneLeft)) && GetCurrentMenu() != null && AreMenuButtonsEnabled) + Game.IsDisabledControlJustPressed(0, Control.PhoneRight) || + Game.IsControlJustPressed(0, Control.PhoneRight) #endif #if REDM - while ((Call(IS_DISABLED_CONTROL_PRESSED, 0, Control.FrontendLeft) || Call(IS_CONTROL_PRESSED, 0, Control.FrontendLeft)) && GetCurrentMenu() != null && AreMenuButtonsEnabled) + IsDisabledControlJustPressed(0, (uint)Control.FrontendRight) || + IsControlJustPressed(0, (uint)Control.FrontendRight) #endif - { - currentMenu = GetCurrentMenu(); - if (GetGameTimer() - time > delay) - { - times++; - if (times > 2) - { - delay = 150; - } - if (times > 5) - { - delay = 100; - } - if (times > 25) - { - delay = 50; - } - if (times > 60) - { - delay = 25; - } - currentMenu.GoLeft(); - time = GetGameTimer(); - } - await Delay(0); - } - } - } + ) + ) + { + await HandleRightNavigation(currentMenu); + } + } - // Check if the Go Right controls are pressed. + private async Task HandleRightNavigation(Menu currentMenu) + { + var item = currentMenu.GetMenuItems()[currentMenu.CurrentIndex]; + if (item.Enabled) + { + currentMenu.GoRight(); + var time = GetGameTimer(); + var times = 0; + var delay = 200; #if FIVEM - else if (Game.IsDisabledControlJustPressed(0, Control.PhoneRight) || Game.IsControlJustPressed(0, Control.PhoneRight)) + while ((Game.IsDisabledControlPressed(0, Control.PhoneRight) || Game.IsControlPressed(0, Control.PhoneRight)) && GetCurrentMenu() != null && AreMenuButtonsEnabled) #endif #if REDM - else if (AreMenuButtonsEnabled && Call(IS_DISABLED_CONTROL_JUST_PRESSED, 0, Control.FrontendRight) || Call(IS_CONTROL_JUST_PRESSED, 0, Control.FrontendRight)) + while ( + GetCurrentMenu() != null && + AreMenuButtonsEnabled && ( + IsDisabledControlPressed(0, (uint)Control.FrontendRight) || + IsControlPressed(0, (uint)Control.FrontendRight) + ) + ) #endif + { + currentMenu = GetCurrentMenu(); + if (GetGameTimer() - time > delay) { - var item = currentMenu.GetMenuItems()[currentMenu.CurrentIndex]; - if (item.Enabled) + times++; + if (times > 2) + { + delay = 150; + } + if (times > 5) { - currentMenu.GoRight(); - var time = GetGameTimer(); - var times = 0; - var delay = 200; + delay = 100; + } + if (times > 25) + { + delay = 50; + } + if (times > 60) + { + delay = 25; + } + currentMenu.GoRight(); + time = GetGameTimer(); + } + await Delay(0); + } + } + } + + private async Task HandleLeftNavigation(Menu currentMenu) + { + if (currentMenu.GetCurrentMenuItem() is MenuItem item && item.Enabled) + { + currentMenu.GoLeft(); + var time = GetGameTimer(); + var times = 0; + var delay = 200; + while ( + GetCurrentMenu() != null && + AreMenuButtonsEnabled && ( #if FIVEM - while ((Game.IsDisabledControlPressed(0, Control.PhoneRight) || Game.IsControlPressed(0, Control.PhoneRight)) && GetCurrentMenu() != null && AreMenuButtonsEnabled) + Game.IsDisabledControlPressed(0, Control.PhoneLeft) || + Game.IsControlPressed(0, Control.PhoneLeft) #endif #if REDM - while ((Call(IS_DISABLED_CONTROL_PRESSED, 0, Control.FrontendRight) || Call(IS_CONTROL_PRESSED, 0, Control.FrontendRight)) && GetCurrentMenu() != null && AreMenuButtonsEnabled) + IsDisabledControlPressed(0, (uint)Control.FrontendLeft) || + IsControlPressed(0, (uint)Control.FrontendLeft) #endif - { - currentMenu = GetCurrentMenu(); - if (GetGameTimer() - time > delay) - { - times++; - if (times > 2) - { - delay = 150; - } - if (times > 5) - { - delay = 100; - } - if (times > 25) - { - delay = 50; - } - if (times > 60) - { - delay = 25; - } - currentMenu.GoRight(); - time = GetGameTimer(); - } - await Delay(0); - } + ) + ) + { + currentMenu = GetCurrentMenu(); + if (GetGameTimer() - time > delay) + { + times++; + if (times > 2) + { + delay = 150; + } + if (times > 5) + { + delay = 100; + } + if (times > 25) + { + delay = 50; + } + if (times > 60) + { + delay = 25; } + currentMenu.GoLeft(); + time = GetGameTimer(); } + await Delay(0); } } } - private async Task MenuButtonsDisableChecks() + private async Task HandleDownNavigation(Menu currentMenu) + { + currentMenu.GoDown(); + + var time = GetGameTimer(); + var times = 0; + var delay = 200; + while (IsDownPressed() && GetCurrentMenu() != null) + { + currentMenu = GetCurrentMenu(); + if (GetGameTimer() - time > delay) + { + times++; + if (times > 2) + { + delay = 150; + } + if (times > 5) + { + delay = 100; + } + if (times > 25) + { + delay = 50; + } + if (times > 60) + { + delay = 25; + } + + currentMenu.GoDown(); + + time = GetGameTimer(); + } + await Delay(0); + } + } + +#if FIVEM + private void HandleMenuToggleKeyForKeyboard() + { + if ( + (Game.IsControlJustPressed(0, MenuToggleKey) || Game.IsDisabledControlJustPressed(0, MenuToggleKey)) && + !Game.IsPaused && + !Game.Player.IsDead && + !IsPlayerSwitchInProgress() && + !DontOpenAnyMenu && + IsScreenFadedIn() + ) + { + if (!Menus.Any()) + { + return; + } + if (MainMenu != null) + { + MainMenu.OpenMenu(); + } + else + { + Menus.First().OpenMenu(); + } + } + } + + private async Task HandleMenuToggleKeyForController() { + int tmpTimer = GetGameTimer(); + while ((Game.IsControlPressed(0, Control.InteractionMenu) || Game.IsDisabledControlPressed(0, Control.InteractionMenu)) && !Game.IsPaused && IsScreenFadedIn() && !Game.Player.IsDead && !IsPlayerSwitchInProgress() && !DontOpenAnyMenu) + { + if (GetGameTimer() - tmpTimer > 400) + { + if (MainMenu != null) + { + MainMenu.OpenMenu(); + } + else + { + if (Menus.Count > 0) + { + Menus[0].OpenMenu(); + } + } + break; + } + await Delay(0); + } + } + private void DisableMenuKeyThisFrame() + { + if (!MenuToggleKeyIsValid) + { + return; + } + + Game.DisableControlThisFrame(0, MenuToggleKey); + if (Game.CurrentInputMode == InputMode.MouseAndKeyboard) + { + if ((Game.IsControlJustPressed(0, MenuToggleKey) || Game.IsDisabledControlJustPressed(0, MenuToggleKey)) && !PreventExitingMenu) + { + var menu = GetCurrentMenu(); + if (menu != null) + { + menu.CloseMenu(); + } + } + } + } +#endif + + private async Task HandleUpNavigation(Menu currentMenu) + { + // Update the currently selected item to the new one. + currentMenu.GoUp(); + + // Get the current game time. + var time = GetGameTimer(); + var times = 0; + var delay = 200; + + // Do the following as long as the controls are being pressed. + while (IsUpPressed() && IsAnyMenuOpen() && GetCurrentMenu() != null) + { + // Update the current menu. + currentMenu = GetCurrentMenu(); + + // Check if the game time has changed by "delay" amount. + if (GetGameTimer() - time > delay) + { + // Increment the "changed indexes" counter + times++; + + // If the controls are still being held down after moving 3 indexes, reduce the delay between index changes. + if (times > 2) + { + delay = 150; + } + if (times > 5) + { + delay = 100; + } + if (times > 25) + { + delay = 50; + } + if (times > 60) + { + delay = 25; + } + + // Update the currently selected item to the new one. + currentMenu.GoUp(); + + // Reset the time to the current game timer. + time = GetGameTimer(); + } + + // Wait for the next game tick. + await Delay(0); + } + } + + private async Task MenuButtonsDisableChecks() + { bool isInputVisible() => UpdateOnscreenKeyboard() == 0; if (isInputVisible()) { @@ -783,267 +875,302 @@ public static void CloseAllMenus() /// private static void DisableControls() { - #region Disable Inputs when any menu is open. - if (IsAnyMenuOpen()) - { - var currMenu = GetCurrentMenu(); - if (currMenu != null) - { - var currentItem = currMenu.GetCurrentMenuItem(); - if (currentItem != null) - { -#if FIVEM - if (currentItem is MenuSliderItem || currentItem is MenuListItem || currentItem is MenuDynamicListItem) - { - if (Game.CurrentInputMode == InputMode.GamePad) - Game.DisableControlThisFrame(0, Control.SelectWeapon); - } -#endif - } - - // Close all menus when the player dies. + if (!IsAnyMenuOpen()) + return; + var currMenu = GetCurrentMenu(); + if (currMenu == null) + return; + if ( #if FIVEM - if (Game.PlayerPed.IsDead) + Game.PlayerPed.IsDead #endif #if REDM - if (Call(IS_ENTITY_DEAD, PlayerPedId())) + IsEntityDead(PlayerPedId()) #endif - { - CloseAllMenus(); - } - + ) + { + // Close all menus when the player dies. + CloseAllMenus(); + } #if FIVEM - // Disable Gamepad/Controller Specific controls: - if (Game.CurrentInputMode == InputMode.GamePad) - { - Game.DisableControlThisFrame(0, Control.MultiplayerInfo); - // when in a vehicle. - if (Game.PlayerPed.IsInVehicle()) - { - Game.DisableControlThisFrame(0, Control.VehicleHeadlight); - Game.DisableControlThisFrame(0, Control.VehicleDuck); + DisableGenericControls(currMenu); + DisableRadioInputs(); + DisablePhoneAndArrowKeysInputs(); + DisableAttackControls(); - // toggles boost in some dlc vehicles, hence it's disabled for controllers only (pressing select in the menu would trigger this). - Game.DisableControlThisFrame(0, Control.VehicleFlyTransform); - } - } - else // when not using a controller. - { - Game.DisableControlThisFrame(0, Control.FrontendPauseAlternate); // disable the escape key opening the pause menu, pressing P still works. - - // Disable the scrollwheel button changing weapons while the menu is open. - // Only if you press TAB (to show the weapon wheel) then it will allow you to change weapons. - if (!Game.IsControlPressed(0, Control.SelectWeapon)) - { - Game.DisableControlThisFrame(24, Control.SelectNextWeapon); - Game.DisableControlThisFrame(24, Control.SelectPrevWeapon); - } - } + // When in a vehicle + if (Game.PlayerPed.IsInVehicle()) + { + Game.DisableControlThisFrame(0, Control.VehicleSelectNextWeapon); + Game.DisableControlThisFrame(0, Control.VehicleSelectPrevWeapon); + Game.DisableControlThisFrame(0, Control.VehicleCinCam); + } #endif #if REDM - if (Call(_IS_INPUT_DISABLED, 2)) - { - Call(DISABLE_CONTROL_ACTION, 0, Control.FrontendPauseAlternate, true); - } + DisableControlsRedM(); #endif - // Disable Shared Controls + } -#if FIVEM - // Radio Inputs - Game.DisableControlThisFrame(0, Control.RadioWheelLeftRight); - Game.DisableControlThisFrame(0, Control.RadioWheelUpDown); - Game.DisableControlThisFrame(0, Control.VehicleNextRadio); - Game.DisableControlThisFrame(0, Control.VehicleRadioWheel); - Game.DisableControlThisFrame(0, Control.VehiclePrevRadio); - - // Phone / Arrows Inputs - Game.DisableControlThisFrame(0, Control.Phone); - Game.DisableControlThisFrame(0, Control.PhoneCancel); - Game.DisableControlThisFrame(0, Control.PhoneDown); - Game.DisableControlThisFrame(0, Control.PhoneLeft); - Game.DisableControlThisFrame(0, Control.PhoneRight); - - // Attack Controls - Game.DisableControlThisFrame(0, Control.Attack); - Game.DisableControlThisFrame(0, Control.Attack2); - Game.DisableControlThisFrame(0, Control.MeleeAttack1); - Game.DisableControlThisFrame(0, Control.MeleeAttack2); - Game.DisableControlThisFrame(0, Control.MeleeAttackAlternate); - Game.DisableControlThisFrame(0, Control.MeleeAttackHeavy); - Game.DisableControlThisFrame(0, Control.MeleeAttackLight); - Game.DisableControlThisFrame(0, Control.VehicleAttack); - Game.DisableControlThisFrame(0, Control.VehicleAttack2); - Game.DisableControlThisFrame(0, Control.VehicleFlyAttack); - Game.DisableControlThisFrame(0, Control.VehiclePassengerAttack); - Game.DisableControlThisFrame(0, Control.Aim); - Game.DisableControlThisFrame(0, Control.VehicleAim); // fires vehicle specific weapons when using right click on the mouse sometimes. - - // When in a vehicle - if (Game.PlayerPed.IsInVehicle()) - { - Game.DisableControlThisFrame(0, Control.VehicleSelectNextWeapon); - Game.DisableControlThisFrame(0, Control.VehicleSelectPrevWeapon); - Game.DisableControlThisFrame(0, Control.VehicleCinCam); - } -#endif #if REDM - Call(DISABLE_CONTROL_ACTION, 0, Control.Attack, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.Attack2, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.HorseAim, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.HorseAttack, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.HorseAttack2, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.HorseMelee, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeAttack, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeBlock, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeGrapple, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeGrappleAttack, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeGrappleBreakout, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeGrappleChoke, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeGrappleMountSwitch, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeGrappleReversal, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeGrappleStandSwitch, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeHorseAttackPrimary, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeHorseAttackSecondary, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.MeleeModifier, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.VehAttack, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.VehAttack2, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.VehBoatAttack, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.VehBoatAttack2, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.VehCarAttack, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.VehCarAttack2, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.VehDraftAttack, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.VehDraftAttack2, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.VehFlyAttack, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.VehFlyAttack2, true); - Call(DISABLE_CONTROL_ACTION, 0, Control.VehPassengerAttack, true); + private static void DisableControlsRedM() + { + DisableControlAction(0, (uint)Control.Attack, true); + DisableControlAction(0, (uint)Control.Attack2, true); + DisableControlAction(0, (uint)Control.HorseAim, true); + DisableControlAction(0, (uint)Control.HorseAttack, true); + DisableControlAction(0, (uint)Control.HorseAttack2, true); + DisableControlAction(0, (uint)Control.HorseMelee, true); + DisableControlAction(0, (uint)Control.MeleeAttack, true); + DisableControlAction(0, (uint)Control.MeleeBlock, true); + DisableControlAction(0, (uint)Control.MeleeGrapple, true); + DisableControlAction(0, (uint)Control.MeleeGrappleAttack, true); + DisableControlAction(0, (uint)Control.MeleeGrappleBreakout, true); + DisableControlAction(0, (uint)Control.MeleeGrappleChoke, true); + DisableControlAction(0, (uint)Control.MeleeGrappleMountSwitch, true); + DisableControlAction(0, (uint)Control.MeleeGrappleReversal, true); + DisableControlAction(0, (uint)Control.MeleeGrappleStandSwitch, true); + DisableControlAction(0, (uint)Control.MeleeHorseAttackPrimary, true); + DisableControlAction(0, (uint)Control.MeleeHorseAttackSecondary, true); + DisableControlAction(0, (uint)Control.MeleeModifier, true); + DisableControlAction(0, (uint)Control.VehAttack, true); + DisableControlAction(0, (uint)Control.VehAttack2, true); + DisableControlAction(0, (uint)Control.VehBoatAttack, true); + DisableControlAction(0, (uint)Control.VehBoatAttack2, true); + DisableControlAction(0, (uint)Control.VehCarAttack, true); + DisableControlAction(0, (uint)Control.VehCarAttack2, true); + DisableControlAction(0, (uint)Control.VehDraftAttack, true); + DisableControlAction(0, (uint)Control.VehDraftAttack2, true); + DisableControlAction(0, (uint)Control.VehFlyAttack, true); + DisableControlAction(0, (uint)Control.VehFlyAttack2, true); + DisableControlAction(0, (uint)Control.VehPassengerAttack, true); + if (IsInputDisabled(2)) + { + DisableControlAction(0, (uint)Control.FrontendPauseAlternate, true); + } + } #endif +#if FIVEM + /// + /// Disable required game controls when the menu is open. + /// + /// + private static void DisableGenericControls(Menu currMenu) + { + // Disable Gamepad/Controller Specific controls: + if (Game.CurrentInputMode == InputMode.GamePad) + { + Game.DisableControlThisFrame(0, Control.MultiplayerInfo); + // when in a vehicle. + if (Game.PlayerPed.IsInVehicle()) + { + Game.DisableControlThisFrame(0, Control.VehicleHeadlight); + Game.DisableControlThisFrame(0, Control.VehicleDuck); + + // toggles boost in some dlc vehicles, hence it's disabled for controllers only (pressing select in the menu would trigger this). + Game.DisableControlThisFrame(0, Control.VehicleFlyTransform); } + } + else // when not using a controller. + { + Game.DisableControlThisFrame(0, Control.FrontendPauseAlternate); // disable the escape key opening the pause menu, pressing P still works. + // Disable the scrollwheel button changing weapons while the menu is open. + // Only if you press TAB (to show the weapon wheel) then it will allow you to change weapons. + if (!Game.IsControlPressed(0, Control.SelectWeapon)) + { + Game.DisableControlThisFrame(24, Control.SelectNextWeapon); + Game.DisableControlThisFrame(24, Control.SelectPrevWeapon); + } } - #endregion + var currentItem = currMenu.GetCurrentMenuItem(); + if (currentItem != null) + { + if (currentItem is MenuSliderItem || currentItem is MenuListItem || currentItem is MenuDynamicListItem) + { + if (Game.CurrentInputMode == InputMode.GamePad) + { + Game.DisableControlThisFrame(0, Control.SelectWeapon); + } + } + } + } + + /// + /// Disable conflicting Attack related game controls when the menu is open. + /// + private static void DisableAttackControls() + { + Game.DisableControlThisFrame(0, Control.Attack); + Game.DisableControlThisFrame(0, Control.Attack2); + Game.DisableControlThisFrame(0, Control.MeleeAttack1); + Game.DisableControlThisFrame(0, Control.MeleeAttack2); + Game.DisableControlThisFrame(0, Control.MeleeAttackAlternate); + Game.DisableControlThisFrame(0, Control.MeleeAttackHeavy); + Game.DisableControlThisFrame(0, Control.MeleeAttackLight); + Game.DisableControlThisFrame(0, Control.VehicleAttack); + Game.DisableControlThisFrame(0, Control.VehicleAttack2); + Game.DisableControlThisFrame(0, Control.VehicleFlyAttack); + Game.DisableControlThisFrame(0, Control.VehiclePassengerAttack); + Game.DisableControlThisFrame(0, Control.Aim); + // fires vehicle specific weapons when using right click on the mouse sometimes. + Game.DisableControlThisFrame(0, Control.VehicleAim); + } + + /// + /// Disable conflicting Phone/Navigation related game controls when the menu is open. + /// + private static void DisablePhoneAndArrowKeysInputs() + { + Game.DisableControlThisFrame(0, Control.Phone); + Game.DisableControlThisFrame(0, Control.PhoneCancel); + Game.DisableControlThisFrame(0, Control.PhoneDown); + Game.DisableControlThisFrame(0, Control.PhoneLeft); + Game.DisableControlThisFrame(0, Control.PhoneRight); } + /// + /// Disable conflicting Radio related game controls when the menu is open. + /// + private static void DisableRadioInputs() + { + Game.DisableControlThisFrame(0, Control.RadioWheelLeftRight); + Game.DisableControlThisFrame(0, Control.RadioWheelUpDown); + Game.DisableControlThisFrame(0, Control.VehicleNextRadio); + Game.DisableControlThisFrame(0, Control.VehicleRadioWheel); + Game.DisableControlThisFrame(0, Control.VehiclePrevRadio); + } +#endif + /// /// Draws all the menus that are visible on the screen. /// /// private static async Task ProcessMenus() { - - if (Menus.Count > 0 && + if (!( + Menus.Any() && IsAnyMenuOpen() && -#if FIVEM IsScreenFadedIn() && - !Game.IsPaused && - !Game.Player.IsDead && - !IsPlayerSwitchInProgress() -#endif -#if REDM - Call(IS_SCREEN_FADED_IN) && - !Call(IS_PAUSE_MENU_ACTIVE) && - !Call(IS_ENTITY_DEAD, PlayerPedId()) + !IsPauseMenuActive() && + !IsEntityDead(PlayerPedId()) +#if FIVEM + && !IsPlayerSwitchInProgress() #endif ) + ) { - await LoadAssets(); - - DisableControls(); + UnloadAssets(); + return; + } + await LoadAssets(); + DisableControls(); + await DrawMenus(); + PerformGC(); + } - Menu menu = GetCurrentMenu(); - if (menu != null) + private static void PerformGC() + { + if (EnableManualGCs) + { + // once a minute + if (GetGameTimer() - ManualTimerForGC > 60000) { - if (DontOpenAnyMenu) - { - if (menu.Visible && !menu.IgnoreDontOpenMenus) - { - menu.CloseMenu(); - } - } - else if (menu.Visible) - { - menu.Draw(); - } + GC.Collect(); + ManualTimerForGC = GetGameTimer(); } + } + } - if (EnableManualGCs) + private static async Task DrawMenus() + { + Menu menu = GetCurrentMenu(); + if (menu == null) + { + return; + } + if (DontOpenAnyMenu) + { + if (menu.Visible && !menu.IgnoreDontOpenMenus) { - // once a minute - if (GetGameTimer() - ManualTimerForGC > 60000) - { - GC.Collect(); - ManualTimerForGC = GetGameTimer(); - } + menu.CloseMenu(); } } - else + else if (menu.Visible) { - UnloadAssets(); + await menu.Draw(); } } #if FIVEM internal static async Task DrawInstructionalButtons() { - if (!Game.IsPaused && !Game.Player.IsDead && IsScreenFadedIn() && !IsPlayerSwitchInProgress() && !IsWarningMessageActive() && UpdateOnscreenKeyboard() != 0) + if ( + Game.IsPaused || + Game.Player.IsDead || + !IsScreenFadedIn() || + IsPlayerSwitchInProgress() || + IsWarningMessageActive() || + UpdateOnscreenKeyboard() == 0 + ) { - Menu menu = GetCurrentMenu(); - if (menu != null && menu.Visible && menu.EnableInstructionalButtons) - { - if (!HasScaleformMovieLoaded(_scale)) - { - _scale = RequestScaleformMovie("INSTRUCTIONAL_BUTTONS"); - } - while (!HasScaleformMovieLoaded(_scale)) - { - await Delay(0); - } - - BeginScaleformMovieMethod(_scale, "CLEAR_ALL"); - EndScaleformMovieMethod(); - + DisposeInstructionalButtonsScaleform(); + return; + } + Menu menu = GetCurrentMenu(); + if (menu == null || !menu.Visible || !menu.EnableInstructionalButtons) + { + DisposeInstructionalButtonsScaleform(); + return; + } + if (!HasScaleformMovieLoaded(_scale)) + { + _scale = RequestScaleformMovie("INSTRUCTIONAL_BUTTONS"); + } + while (!HasScaleformMovieLoaded(_scale)) + { + await Delay(0); + } + DrawScaleformMovieFullscreen(_scale, 255, 255, 255, 0, 0); + BeginScaleformMovieMethod(_scale, "CLEAR_ALL"); + EndScaleformMovieMethod(); - for (int i = 0; i < menu.InstructionalButtons.Count; i++) - { - string text = menu.InstructionalButtons.ElementAt(i).Value; - Control control = menu.InstructionalButtons.ElementAt(i).Key; - - BeginScaleformMovieMethod(_scale, "SET_DATA_SLOT"); - ScaleformMovieMethodAddParamInt(i); - string buttonName = GetControlInstructionalButton(0, (int)control, 1); - PushScaleformMovieMethodParameterString(buttonName); - PushScaleformMovieMethodParameterString(text); - EndScaleformMovieMethod(); - } - // Use custom instructional buttons FIRST if they're present. - if (menu.CustomInstructionalButtons.Count > 0) - { - for (int i = 0; i < menu.CustomInstructionalButtons.Count; i++) - { - Menu.InstructionalButton button = menu.CustomInstructionalButtons[i]; - BeginScaleformMovieMethod(_scale, "SET_DATA_SLOT"); - ScaleformMovieMethodAddParamInt(i + menu.InstructionalButtons.Count); - PushScaleformMovieMethodParameterString(button.controlString); - PushScaleformMovieMethodParameterString(button.instructionText); - EndScaleformMovieMethod(); - } - } + for (int i = 0; i < menu.InstructionalButtons.Count; i++) + { + string text = menu.InstructionalButtons.ElementAt(i).Value; + Control control = menu.InstructionalButtons.ElementAt(i).Key; + + BeginScaleformMovieMethod(_scale, "SET_DATA_SLOT"); + ScaleformMovieMethodAddParamInt(i); + string buttonName = GetControlInstructionalButton(0, (int)control, 1); + PushScaleformMovieMethodParameterString(buttonName); + PushScaleformMovieMethodParameterString(text); + EndScaleformMovieMethod(); + } - BeginScaleformMovieMethod(_scale, "DRAW_INSTRUCTIONAL_BUTTONS"); - ScaleformMovieMethodAddParamInt(0); + // Use custom instructional buttons FIRST if they're present. + if (menu.CustomInstructionalButtons.Count > 0) + { + for (int i = 0; i < menu.CustomInstructionalButtons.Count; i++) + { + Menu.InstructionalButton button = menu.CustomInstructionalButtons[i]; + BeginScaleformMovieMethod(_scale, "SET_DATA_SLOT"); + ScaleformMovieMethodAddParamInt(i + menu.InstructionalButtons.Count); + PushScaleformMovieMethodParameterString(button.controlString); + PushScaleformMovieMethodParameterString(button.instructionText); EndScaleformMovieMethod(); - - DrawScaleformMovieFullscreen(_scale, 255, 255, 255, 255, 0); - return; } } - DisposeInstructionalButtonsScaleform(); + + BeginScaleformMovieMethod(_scale, "DRAW_INSTRUCTIONAL_BUTTONS"); + ScaleformMovieMethodAddParamInt(0); + EndScaleformMovieMethod(); + + DrawScaleformMovieFullscreen(_scale, 255, 255, 255, 255, 0); } -#endif -#if FIVEM private static void DisposeInstructionalButtonsScaleform() { if (HasScaleformMovieLoaded(_scale)) @@ -1052,13 +1179,13 @@ private static void DisposeInstructionalButtonsScaleform() } } #endif - +#if REDM /// /// Prevent the UI prompts getting stuck on screen if this resource is ever to be restarted while someone has any menu's open. /// /// [EventHandler("onResourceStop")] - private static void OnResourceStop(string name) + internal static void OnResourceStop(string name) { if (name == GetCurrentResourceName()) { @@ -1066,5 +1193,6 @@ private static void OnResourceStop(string name) CloseAllMenus(); } } +#endif } } diff --git a/MenuAPI/items/MenuCheckboxItem.cs b/MenuAPI/items/MenuCheckboxItem.cs index fa47c3d..69f15c9 100644 --- a/MenuAPI/items/MenuCheckboxItem.cs +++ b/MenuAPI/items/MenuCheckboxItem.cs @@ -1,13 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CitizenFX.Core; +using CitizenFX.Core; using static CitizenFX.Core.Native.API; -using static CitizenFX.Core.Native.Function; -using static CitizenFX.Core.Native.Hash; - namespace MenuAPI { @@ -51,14 +43,13 @@ public MenuCheckboxItem(string text, string description, bool _checked) : base(t Checked = _checked; } - - int GetSpriteColour() + private int GetSpriteColour() { return Enabled ? 255 : 109; } #if FIVEM - string GetSpriteName() + private string GetSpriteName() { if (Checked) { @@ -81,7 +72,7 @@ string GetSpriteName() } else { - if (base.Selected) + if (Selected) { return "shop_box_blankb"; } @@ -90,12 +81,33 @@ string GetSpriteName() } #endif - float GetSpriteX() + private float GetSpriteX() { #if FIVEM bool leftSide = false; bool leftAligned = ParentMenu.LeftAligned; - return leftSide ? (leftAligned ? (20f / MenuController.ScreenWidth) : GetSafeZoneSize() - ((Width - 20f) / MenuController.ScreenWidth)) : (leftAligned ? (Width - 20f) / MenuController.ScreenWidth : (GetSafeZoneSize() - (20f / MenuController.ScreenWidth))); + if (leftSide) + { + if (leftAligned) + { + return 20f / MenuController.ScreenWidth; + } + else + { + return GetSafeZoneSize() - ((Width - 20f) / MenuController.ScreenWidth); + } + } + else + { + if (leftAligned) + { + return (Width - 20f) / MenuController.ScreenWidth; + } + else + { + return GetSafeZoneSize() - (20f / MenuController.ScreenWidth); + } + } #endif #if REDM return (Width - 30f) / MenuController.ScreenWidth; @@ -106,9 +118,7 @@ internal override void Draw(int offset) { RightIcon = Icon.NONE; Label = null; - base.Draw(offset); - #if FIVEM SetScriptGfxAlign(76, 84); SetScriptGfxAlignParams(0f, 0f, 0f, 0f); @@ -126,22 +136,31 @@ internal override void Draw(int offset) float spriteWidth = 45f / MenuController.ScreenWidth; #endif int color = GetSpriteColour(); - #if FIVEM DrawSprite("commonmenu", name, spriteX, spriteY, spriteWidth, spriteHeight, 0f, color, color, color, 255); ResetScriptGfxAlign(); #endif - #if REDM float spriteHeight = 24f / MenuController.ScreenHeight; float spriteWidth = 16f / MenuController.ScreenWidth; - Call(DRAW_SPRITE, "menu_textures", "SELECTION_BOX_SQUARE", spriteX, spriteY, spriteWidth, spriteHeight, 0f, color, color, color, 255); + DrawSprite("menu_textures", "SELECTION_BOX_SQUARE", spriteX, spriteY, spriteWidth, spriteHeight, 0f, color, color, color, 255, false); if (Checked) { int[] sc = Enabled ? (Selected ? new int[3] { 255, 255, 255 } : new int[3] { 181, 17, 18 }) : (Selected ? new int[3] { 109, 109, 109 } : new int[3] { 110, 10, 10 }); - Call(DRAW_SPRITE, "generic_textures", "TICK", spriteX, spriteY, spriteWidth, spriteHeight, 0f, sc[0], sc[1], sc[2], 255); + DrawSprite("generic_textures", "TICK", spriteX, spriteY, spriteWidth, spriteHeight, 0f, sc[0], sc[1], sc[2], 255, false); } #endif } + + internal override void GoRight() + { + ParentMenu.SelectItem(this); + } + + internal override void Select() + { + Checked = !Checked; + ParentMenu.CheckboxChangedEvent(this, Index, Checked); + } } } diff --git a/MenuAPI/items/MenuDynamicListItem.cs b/MenuAPI/items/MenuDynamicListItem.cs index dc3b255..6c29cad 100644 --- a/MenuAPI/items/MenuDynamicListItem.cs +++ b/MenuAPI/items/MenuDynamicListItem.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CitizenFX.Core; +using static CitizenFX.Core.Native.Function; using static CitizenFX.Core.Native.API; namespace MenuAPI @@ -34,9 +29,42 @@ internal override void Draw(int indexOffset) { Label = $"~s~← {CurrentItem ?? "~r~N/A~s~"} ~s~→"; } - base.Draw(indexOffset); } + internal override void GoRight() + { + string oldValue = CurrentItem; + string newSelectedItem = Callback(this, false); + CurrentItem = newSelectedItem; + ParentMenu.DynamicListItemCurrentItemChanged(ParentMenu, this, oldValue, newSelectedItem); +#if FIVEM + PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); +#endif +#if REDM + // Has invalid parameter types in API. + Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_RIGHT", "HUD_SHOP_SOUNDSET", 1); +#endif + } + + internal override void GoLeft() + { + string oldValue = CurrentItem; + string newSelectedItem = Callback(this, true); + CurrentItem = newSelectedItem; + ParentMenu.DynamicListItemCurrentItemChanged(ParentMenu, this, oldValue, newSelectedItem); +#if FIVEM + PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); +#endif +#if REDM + // Has invalid parameter types in API. + Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_RIGHT", "HUD_SHOP_SOUNDSET", 1); +#endif + } + + internal override void Select() + { + ParentMenu.DynamicListItemSelectEvent(ParentMenu, this, CurrentItem); + } } } diff --git a/MenuAPI/items/MenuItem.cs b/MenuAPI/items/MenuItem.cs index ac7b807..a4ff375 100644 --- a/MenuAPI/items/MenuItem.cs +++ b/MenuAPI/items/MenuItem.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using CitizenFX.Core; using static CitizenFX.Core.Native.API; using static CitizenFX.Core.Native.Function; @@ -12,7 +9,6 @@ namespace MenuAPI { public class MenuItem { - public enum Icon { NONE, @@ -195,6 +191,7 @@ public enum Icon BRAND_WESTERNMOTORCYCLE, BRAND_WILLARD, BRAND_ZIRCONIUM, + INFO #endif #if REDM LOCK, @@ -208,7 +205,6 @@ public enum Icon SELECTION_BOX #endif } - public string Text { get; set; } public string Label { get; set; } public Icon LeftIcon { get; set; } @@ -281,6 +277,11 @@ public MenuItem(string text, string description) Description = description; } + /// + /// Get the sprite dictionary name for the given icon. + /// + /// + /// protected string GetSpriteDictionary(Icon icon) { switch (icon) @@ -444,6 +445,8 @@ protected string GetSpriteDictionary(Icon icon) case Icon.BRAND_PROGEN2: case Icon.BRAND_RUNE: return "mpcarhud2"; + case Icon.INFO: + return "shared"; default: return "commonmenu"; #endif @@ -462,9 +465,14 @@ protected string GetSpriteDictionary(Icon icon) return ""; #endif } - } + /// + /// Get the sprite name for the given icon depending on the selected state of the item. + /// + /// + /// + /// protected string GetSpriteName(Icon icon, bool selected) { switch (icon) @@ -649,6 +657,7 @@ protected string GetSpriteName(Icon icon, bool selected) case Icon.BRAND_LCC: return "lcc"; case Icon.BRAND_PROGEN2: return "progen"; case Icon.BRAND_RUNE: return "rune"; + case Icon.INFO: return "info_icon_32"; default: break; #endif @@ -676,6 +685,12 @@ protected string GetSpriteName(Icon icon, bool selected) return ""; } + /// + /// Get the sprite size for the given icon. + /// + /// + /// + /// protected float GetSpriteSize(Icon icon, bool width) { switch (icon) @@ -863,6 +878,12 @@ protected float GetSpriteSize(Icon icon, bool width) } } + /// + /// Get the sprite color for the provided icon depending on the current state of the item (Enabled & selected values). + /// + /// + /// + /// protected int[] GetSpriteColour(Icon icon, bool selected) { switch (icon) @@ -986,6 +1007,13 @@ protected int[] GetSpriteColour(Icon icon, bool selected) } } + /// + /// Get the sprite x position offset for the provided icon and alignment variables. + /// + /// + /// + /// + /// protected float GetSpriteX(Icon icon, bool leftAligned, bool leftSide) { #if FIVEM @@ -993,204 +1021,307 @@ protected float GetSpriteX(Icon icon, bool leftAligned, bool leftSide) { return 0f; } - return leftSide ? (leftAligned ? (20f / MenuController.ScreenWidth) : GetSafeZoneSize() - ((Width - 20f) / MenuController.ScreenWidth)) : (leftAligned ? (Width - 20f) / MenuController.ScreenWidth : (GetSafeZoneSize() - (20f / MenuController.ScreenWidth))); + if (leftSide) + { + if (leftAligned) + { + return 20f / MenuController.ScreenWidth; + } + else + { + return GetSafeZoneSize() - ((Width - 20f) / MenuController.ScreenWidth); + } + } + else + { + if (leftAligned) + { + return (Width - 20f) / MenuController.ScreenWidth; + } + else + { + return GetSafeZoneSize() - (20f / MenuController.ScreenWidth); + } + } #endif #if REDM - return leftSide ? 30f / MenuController.ScreenWidth : ((Width - 30f) / MenuController.ScreenWidth); + if (leftSide) + { + return 30f / MenuController.ScreenWidth; + } + return (Width - 30f) / MenuController.ScreenWidth; #endif } - protected float GetSpriteY(Icon icon) + /// + /// Handles menu navigation to the right for items that support it, otherwise the item will be selected. + /// + internal virtual void GoRight() { - return 0f; + if (Enabled) + { + ParentMenu.SelectItem(this); + } } + /// + /// Handles menu navigation to the left for items that support it, otherwise the menu will navigate to the parent menu. + /// + internal virtual void GoLeft() + { + if (MenuController.NavigateMenuUsingArrows && !MenuController.DisableBackButton && !(MenuController.PreventExitingMenu && ParentMenu == null)) + { + ParentMenu.GoBack(); + } + } + + /// + /// Handles item selection. + /// + internal virtual void Select() + { + ParentMenu.ItemSelectedEvent(this, Index); + } /// /// Draws the item on the screen. /// internal virtual void Draw(int indexOffset) { - if (ParentMenu != null) + if (ParentMenu == null) { - float yOffset = ParentMenu.MenuItemsYOffset + 1f - (RowHeight * MathUtil.Clamp(ParentMenu.Size, 0, ParentMenu.MaxItemsOnScreen)); - #region Background Rect + return; + } + + int font = 0; + float textSize = (14f * 27f) / MenuController.ScreenHeight; + int textColor = Selected ? (Enabled ? 0 : 50) : (Enabled ? 255 : 109); + + float yOffset = ParentMenu.MenuItemsYOffset + 1f - (RowHeight * MathUtil.Clamp(ParentMenu.Size, 0, ParentMenu.MaxItemsOnScreen)); + float textXOffset = 0f; + float rightTextIconOffset = 0f; + + DrawBackground(indexOffset, yOffset, out float x, out float y); + + float textMinX = (textXOffset / MenuController.ScreenWidth) + (10f / MenuController.ScreenWidth); + float textMaxX = (Width - 10f) / MenuController.ScreenWidth; + float textY = y - ((30f / 2f) / MenuController.ScreenHeight); + + textXOffset = DrawLeftIcon(textXOffset, y); + rightTextIconOffset = DrawRightIcon(rightTextIconOffset, y); #if FIVEM - SetScriptGfxAlign(ParentMenu.LeftAligned ? 76 : 82, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + DrawLabelText(textXOffset, rightTextIconOffset, y, font, textSize, textColor, textY); #endif + DrawItemText(font, textSize, textColor, textMinX, textMaxX, textY, textXOffset, y); + } - float x = (ParentMenu.Position.Key + (Width / 2f)) / MenuController.ScreenWidth; - float y = (ParentMenu.Position.Value + ((Index - indexOffset) * RowHeight) + (20f) + yOffset) / MenuController.ScreenHeight; - float width = Width / MenuController.ScreenWidth; - float height = (RowHeight) / MenuController.ScreenHeight; - - if (Selected) - { + /// + /// Drwa the item text + /// + /// + /// + /// + /// + /// + /// + /// + /// + private void DrawItemText(int font, float textSize, int textColor, float textMinX, float textMaxX, float textY, float textXOffset, float y) + { #if FIVEM - DrawRect(x, y, width, height, 255, 255, 255, 225); + SetScriptGfxAlign(76, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + SetTextFont(font); + SetTextScale(textSize, textSize); + SetTextJustification(1); + BeginTextCommandDisplayText("STRING"); + AddTextComponentSubstringPlayerName(Text ?? "N/A"); + if (Selected || !Enabled) + { + SetTextColour(textColor, textColor, textColor, 255); + } + if (ParentMenu.LeftAligned) + { + SetTextWrap(textMinX, textMaxX); + EndTextCommandDisplayText(textMinX, textY); + } + else + { + textMinX = (textXOffset / MenuController.ScreenWidth) + GetSafeZoneSize() - ((Width - 10f) / MenuController.ScreenWidth); + textMaxX = GetSafeZoneSize() - (10f / MenuController.ScreenWidth); + SetTextWrap(textMinX, textMaxX); + EndTextCommandDisplayText(textMinX, textY); + } + ResetScriptGfxAlign(); #endif #if REDM - Call(DRAW_SPRITE, MenuController._texture_dict, MenuController._header_texture, x, y, width, height, 0f, 181, 17, 18, 255); - //Call(DRAW_RECT, x, y, width, height, 74, 6, 7, 200); -#endif - } -#if FIVEM - ResetScriptGfxAlign(); + SetTextScale(textSize, textSize); + textColor = Enabled ? 255 : 109; + SetTextColor(textColor, textColor, textColor, 255); + textMinX = ((8f + textXOffset) / MenuController.ScreenWidth) + (10f / MenuController.ScreenWidth); + textMaxX = (Width - 10f) / MenuController.ScreenWidth; + textY = y - ((30f / 2f) / MenuController.ScreenHeight); + font = 23; + // Cfx native, undocumented. + Call((CitizenFX.Core.Native.Hash)0xADA9255D, font); + // API version has incorrect parameter types. + long _text = Call(_CREATE_VAR_STRING, 10, "LITERAL_STRING", (Text ?? "N/A") + (" " + Label ?? "")); + DisplayText(_text, textMinX, textY); #endif - #endregion + } - #region Left Icon - float textXOffset = 0f; - if (LeftIcon != Icon.NONE) - { - textXOffset = 25f; #if FIVEM - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - - string name = GetSpriteName(LeftIcon, Selected); - float spriteY = y;// GetSpriteY(LeftIcon); - float spriteX = GetSpriteX(LeftIcon, ParentMenu.LeftAligned, true); - float spriteHeight = GetSpriteSize(LeftIcon, false); - float spriteWidth = GetSpriteSize(LeftIcon, true); - int[] spriteColor = GetSpriteColour(LeftIcon, Selected); - string textureDictionary = GetSpriteDictionary(LeftIcon); + /// + /// Draw the item label text if it exists. + /// + /// + /// + /// + /// + /// + /// + /// + private void DrawLabelText(float textXOffset, float rightTextIconOffset, float y, int font, float textSize, int textColor, float textY) + { + if (string.IsNullOrEmpty(Label)) + { + return; + } + SetScriptGfxAlign(76, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - DrawSprite(textureDictionary, name, spriteX, spriteY, spriteWidth, spriteHeight, 0f, spriteColor[0], spriteColor[1], spriteColor[2], 255); - ResetScriptGfxAlign(); -#endif -#if REDM - string spriteName = GetSpriteName(LeftIcon, Selected); - string spriteDict = GetSpriteDictionary(LeftIcon); - float spriteX = GetSpriteX(LeftIcon, true, true); - float spriteY = y; - float spriteHeight = GetSpriteSize(LeftIcon, false); - float spriteWidth = GetSpriteSize(LeftIcon, true); - int[] spriteColor = GetSpriteColour(LeftIcon, Selected); - Call(DRAW_SPRITE, spriteDict, spriteName, spriteX, spriteY, spriteWidth, spriteHeight, 0f, spriteColor[0], spriteColor[1], spriteColor[2], 255); + BeginTextCommandDisplayText("STRING"); + SetTextFont(font); + SetTextScale(textSize, textSize); + SetTextJustification(2); + AddTextComponentSubstringPlayerName(Label); + if (Selected || !Enabled) + { + SetTextColour(textColor, textColor, textColor, 255); + } + if (ParentMenu.LeftAligned) + { + SetTextWrap(0f, ((490f - rightTextIconOffset) / MenuController.ScreenWidth)); + EndTextCommandDisplayText((10f + rightTextIconOffset) / MenuController.ScreenWidth, textY); + } + else + { + SetTextWrap(0f, GetSafeZoneSize() - ((10f + rightTextIconOffset) / MenuController.ScreenWidth)); + EndTextCommandDisplayText(0f, textY); + } + ResetScriptGfxAlign(); + } #endif - } - #endregion - - #region Right Icon - float rightTextIconOffset = 0f; - if (RightIcon != Icon.NONE) - { + /// + /// Draw the right icon if it exists. + /// + /// + /// + /// + private float DrawRightIcon(float rightTextIconOffset, float y) + { + if (RightIcon == Icon.NONE) + { + return rightTextIconOffset; + } #if FIVEM - rightTextIconOffset = 25f; + rightTextIconOffset = 25f; - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - string name = GetSpriteName(RightIcon, Selected); - float spriteY = y;// GetSpriteY(RightIcon); - float spriteX = GetSpriteX(RightIcon, ParentMenu.LeftAligned, false); - float spriteHeight = GetSpriteSize(RightIcon, false); - float spriteWidth = GetSpriteSize(RightIcon, true); - int[] spriteColor = GetSpriteColour(RightIcon, Selected); - string textureDictionary = GetSpriteDictionary(RightIcon); - DrawSprite(textureDictionary, name, spriteX, spriteY, spriteWidth, spriteHeight, 0f, spriteColor[0], spriteColor[1], spriteColor[2], 255); - ResetScriptGfxAlign(); + SetScriptGfxAlign(76, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + string name = GetSpriteName(RightIcon, Selected); + float spriteY = y; + float spriteX = GetSpriteX(RightIcon, ParentMenu.LeftAligned, false); + float spriteHeight = GetSpriteSize(RightIcon, false); + float spriteWidth = GetSpriteSize(RightIcon, true); + int[] spriteColor = GetSpriteColour(RightIcon, Selected); + string textureDictionary = GetSpriteDictionary(RightIcon); + DrawSprite(textureDictionary, name, spriteX, spriteY, spriteWidth, spriteHeight, 0f, spriteColor[0], spriteColor[1], spriteColor[2], 255); + ResetScriptGfxAlign(); #endif #if REDM - string spriteName = GetSpriteName(RightIcon, Selected); - string spriteDict = GetSpriteDictionary(RightIcon); - float spriteX = GetSpriteX(RightIcon, true, false); - float spriteY = y; - float spriteHeight = GetSpriteSize(RightIcon, false); - float spriteWidth = GetSpriteSize(RightIcon, true); - int[] spriteColor = GetSpriteColour(RightIcon, Selected); - Call(DRAW_SPRITE, spriteDict, spriteName, spriteX, spriteY, spriteWidth, spriteHeight, 0f, spriteColor[0], spriteColor[1], spriteColor[2], 255); + string spriteName = GetSpriteName(RightIcon, Selected); + string spriteDict = GetSpriteDictionary(RightIcon); + float spriteX = GetSpriteX(RightIcon, true, false); + float spriteY = y; + float spriteHeight = GetSpriteSize(RightIcon, false); + float spriteWidth = GetSpriteSize(RightIcon, true); + int[] spriteColor = GetSpriteColour(RightIcon, Selected); + DrawSprite(spriteDict, spriteName, spriteX, spriteY, spriteWidth, spriteHeight, 0f, spriteColor[0], spriteColor[1], spriteColor[2], 255, false); #endif - } - #endregion + return rightTextIconOffset; + } - #region Label - int font = 0; - float textSize = (14f * 27f) / MenuController.ScreenHeight; + /// + /// Draw the left icon if it exists. + /// + /// + /// + /// + private float DrawLeftIcon(float textXOffset, float y) + { + if (LeftIcon == Icon.NONE) + { + return textXOffset; + } + textXOffset = 25f; #if FIVEM - float textMinX = (textXOffset / MenuController.ScreenWidth) + (10f / MenuController.ScreenWidth); - float textMaxX = (Width - 10f) / MenuController.ScreenWidth; - //float textHeight = GetTextScaleHeight(textSize, font); - float textY = y - ((30f / 2f) / MenuController.ScreenHeight); - int textColor = Selected ? (Enabled ? 0 : 50) : (Enabled ? 255 : 109); - if (!string.IsNullOrEmpty(Label)) - { - SetScriptGfxAlign(76, 84); - SetScriptGfxAlignParams(0f, 0f, 0f, 0f); + SetScriptGfxAlign(76, 84); + SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - BeginTextCommandDisplayText("STRING"); - SetTextFont(font); - SetTextScale(textSize, textSize); - SetTextJustification(2); - AddTextComponentSubstringPlayerName(Label); - if (Selected || !Enabled) - { - SetTextColour(textColor, textColor, textColor, 255); - } - if (ParentMenu.LeftAligned) - { - SetTextWrap(0f, ((490f - rightTextIconOffset) / MenuController.ScreenWidth)); - EndTextCommandDisplayText((10f + rightTextIconOffset) / MenuController.ScreenWidth, textY); - } - else - { - SetTextWrap(0f, GetSafeZoneSize() - ((10f + rightTextIconOffset) / MenuController.ScreenWidth)); - EndTextCommandDisplayText(0f, textY); - } + string name = GetSpriteName(LeftIcon, Selected); + float spriteY = y; + float spriteX = GetSpriteX(LeftIcon, ParentMenu.LeftAligned, true); + float spriteHeight = GetSpriteSize(LeftIcon, false); + float spriteWidth = GetSpriteSize(LeftIcon, true); + int[] spriteColor = GetSpriteColour(LeftIcon, Selected); + string textureDictionary = GetSpriteDictionary(LeftIcon); - ResetScriptGfxAlign(); - } + DrawSprite(textureDictionary, name, spriteX, spriteY, spriteWidth, spriteHeight, 0f, spriteColor[0], spriteColor[1], spriteColor[2], 255); + ResetScriptGfxAlign(); #endif - #endregion +#if REDM + string spriteName = GetSpriteName(LeftIcon, Selected); + string spriteDict = GetSpriteDictionary(LeftIcon); + float spriteX = GetSpriteX(LeftIcon, true, true); + float spriteY = y; + float spriteHeight = GetSpriteSize(LeftIcon, false); + float spriteWidth = GetSpriteSize(LeftIcon, true); + int[] spriteColor = GetSpriteColour(LeftIcon, Selected); + DrawSprite(spriteDict, spriteName, spriteX, spriteY, spriteWidth, spriteHeight, 0f, spriteColor[0], spriteColor[1], spriteColor[2], 255, false); +#endif + return textXOffset; + } - #region Text + /// + /// Draws the background for the menu item if it is selected and output the x/y values for this item. + /// + /// + /// + /// + /// + private void DrawBackground(int indexOffset, float yOffset, out float x, out float y) + { + x = (ParentMenu.Position.Key + (Width / 2f)) / MenuController.ScreenWidth; + y = (ParentMenu.Position.Value + ((Index - indexOffset) * RowHeight) + (20f) + yOffset) / MenuController.ScreenHeight; + if (Selected) + { + float width = Width / MenuController.ScreenWidth; + float height = (RowHeight) / MenuController.ScreenHeight; #if FIVEM - SetScriptGfxAlign(76, 84); + SetScriptGfxAlign(ParentMenu.LeftAligned ? 76 : 82, 84); SetScriptGfxAlignParams(0f, 0f, 0f, 0f); - SetTextFont(font); - SetTextScale(textSize, textSize); - SetTextJustification(1); - BeginTextCommandDisplayText("STRING"); - AddTextComponentSubstringPlayerName(Text ?? "N/A"); - if (Selected || !Enabled) - { - SetTextColour(textColor, textColor, textColor, 255); - } - if (ParentMenu.LeftAligned) - { - SetTextWrap(textMinX, textMaxX); - EndTextCommandDisplayText(textMinX, textY); - } - else - { - textMinX = (textXOffset / MenuController.ScreenWidth) + GetSafeZoneSize() - ((Width - 10f) / MenuController.ScreenWidth); - textMaxX = GetSafeZoneSize() - (10f / MenuController.ScreenWidth); - SetTextWrap(textMinX, textMaxX); - EndTextCommandDisplayText(textMinX, textY); - } + DrawRect(x, y, width, height, 255, 255, 255, 225); ResetScriptGfxAlign(); #endif #if REDM - Call(SET_TEXT_SCALE, textSize, textSize); - - int textColor = Enabled ? 255 : 109; - Call((CitizenFX.Core.Native.Hash)0x50A41AD966910F03, textColor, textColor, textColor, 255); // _SET_TEXT_COLOUR / 0x50A41AD966910F03 - float textMinX = ((8f + textXOffset) / MenuController.ScreenWidth) + (10f / MenuController.ScreenWidth); - float textMaxX = (Width - 10f) / MenuController.ScreenWidth; - float textY = y - ((30f / 2f) / MenuController.ScreenHeight); - font = 23; - Call((CitizenFX.Core.Native.Hash)0xADA9255D, font); - - Call(_DISPLAY_TEXT, Call(_CREATE_VAR_STRING, 10, "LITERAL_STRING", (Text ?? "N/A") + (" " + Label ?? "")), textMinX, textY); + DrawSprite(MenuController._texture_dict, MenuController._header_texture, x, y, width, height, 0f, 181, 17, 18, 255, false); #endif - #endregion - - } +#if FIVEM +#endif } - } } diff --git a/MenuAPI/items/MenuListItem.cs b/MenuAPI/items/MenuListItem.cs index 4021188..e83ab37 100644 --- a/MenuAPI/items/MenuListItem.cs +++ b/MenuAPI/items/MenuListItem.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CitizenFX.Core; +using System.Collections.Generic; +using static CitizenFX.Core.Native.Function; using static CitizenFX.Core.Native.API; namespace MenuAPI { - public class MenuListItem : MenuItem { public int ListIndex { get; set; } = 0; @@ -69,6 +64,63 @@ internal override void Draw(int indexOffset) base.Draw(indexOffset); } - } + internal override void GoRight() + { + if (ItemsCount > 0) + { + int oldIndex = ListIndex; + int newIndex = oldIndex; + if (ListIndex >= ItemsCount - 1) + { + newIndex = 0; + } + else + { + newIndex++; + } + ListIndex = newIndex; + ParentMenu.ListItemIndexChangeEvent(ParentMenu, this, oldIndex, newIndex, Index); +#if FIVEM + PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); +#endif +#if REDM + // Has invalid parameter types in API. + Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_RIGHT", "HUD_SHOP_SOUNDSET", 1); +#endif + } + } + + internal override void GoLeft() + { + if (ItemsCount > 0) + { + int oldIndex = ListIndex; + int newIndex = oldIndex; + if (ListIndex < 1) + { + newIndex = ItemsCount - 1; + } + else + { + newIndex--; + } + ListIndex = newIndex; + + ParentMenu.ListItemIndexChangeEvent(ParentMenu, this, oldIndex, newIndex, Index); +#if FIVEM + PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); +#endif +#if REDM + // Has invalid parameter types in API. + Call((CitizenFX.Core.Native.Hash)0xCE5D0FFE83939AF1, -1, "NAV_LEFT", "HUD_SHOP_SOUNDSET", 1); +#endif + } + } + + internal override void Select() + { + ParentMenu.ListItemSelectEvent(ParentMenu, this, ListIndex, Index); + } + } } diff --git a/MenuAPI/items/MenuSliderItem.cs b/MenuAPI/items/MenuSliderItem.cs index ab0fba7..442fc4e 100644 --- a/MenuAPI/items/MenuSliderItem.cs +++ b/MenuAPI/items/MenuSliderItem.cs @@ -1,4 +1,5 @@ -using System; +#if FIVEM +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,7 +9,6 @@ namespace MenuAPI { -#if FIVEM public class MenuSliderItem : MenuItem { public int Min { get; private set; } = 0; @@ -49,7 +49,6 @@ private float Map(float val, float in_min, float in_max, float out_min, float ou internal override void Draw(int indexOffset) { - RightIcon = SliderRightIcon; Label = null; @@ -60,7 +59,6 @@ internal override void Draw(int indexOffset) Position = (Max - Min) / 2; } - float yOffset = ParentMenu.MenuItemsYOffset + 1f - (RowHeight * MathUtil.Clamp(ParentMenu.Size, 0, ParentMenu.MaxItemsOnScreen)); float width = 150f / MenuController.ScreenWidth; @@ -108,13 +106,24 @@ internal override void Draw(int indexOffset) // background DrawRect(x, y, width, height, BackgroundColor.R, BackgroundColor.G, BackgroundColor.B, BackgroundColor.A); - float xOffset = Map((float)Position, (float)Min, (float)Max, -((width / 4f) * MenuController.ScreenWidth), ((width / 4f) * MenuController.ScreenWidth)) / MenuController.ScreenWidth; + float xOffset = Map( + (float)Position, + (float)Min, + (float)Max, + -((width / 4f) * MenuController.ScreenWidth), + (width / 4f) * MenuController.ScreenWidth + ); + xOffset /= MenuController.ScreenWidth; // bar (foreground) if (!ParentMenu.LeftAligned) + { DrawRect(x - (width / 2f) + xOffset, y, width / 2f, height, BarColor.R, BarColor.G, BarColor.B, BarColor.A); + } else + { DrawRect(x + xOffset, y, width / 2f, height, BarColor.R, BarColor.G, BarColor.B, BarColor.A); + } #endregion @@ -122,16 +131,50 @@ internal override void Draw(int indexOffset) if (ShowDivider) { if (!ParentMenu.LeftAligned) + { DrawRect(x - width + (4f / MenuController.ScreenWidth), y, 4f / MenuController.ScreenWidth, RowHeight / MenuController.ScreenHeight / 2f, 255, 255, 255, 255); + } else + { DrawRect(x + (2f / MenuController.ScreenWidth), y, 4f / MenuController.ScreenWidth, RowHeight / MenuController.ScreenHeight / 2f, 255, 255, 255, 255); + } } #endregion ResetScriptGfxAlign(); + } + internal override void GoRight() + { + if (Position < Max) + { + Position++; + ParentMenu.SliderItemChangedEvent(ParentMenu, this, Position - 1, Position, Index); + PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); + } + else + { + PlaySoundFrontend(-1, "ERROR", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); + } + } + internal override void GoLeft() + { + if (Position > Min) + { + Position--; + ParentMenu.SliderItemChangedEvent(ParentMenu, this, Position + 1, Position, Index); + PlaySoundFrontend(-1, "NAV_LEFT_RIGHT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); + } + else + { + PlaySoundFrontend(-1, "ERROR", "HUD_FRONTEND_DEFAULT_SOUNDSET", false); + } + } + internal override void Select() + { + ParentMenu.SliderSelectedEvent(ParentMenu, this, Position, Index); } } -#endif } +#endif \ No newline at end of file diff --git a/README.md b/README.md index 34e3c38..49f6abd 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # MenuAPI -FiveM C# Menu API. +**FiveM & RedM C# Menu API** -Designed specifically as a replacement of NativeUI for vMenu with improved performance (somewhat), more features, less bugs, and easier to use functions (somewhat). +[![Discord](https://discordapp.com/api/guilds/285424882534187008/widget.png)](https://vespura.com/discord) [![CodeFactor](https://www.codefactor.io/repository/github/tomgrobbe/menuapi/badge)](https://www.codefactor.io/repository/github/tomgrobbe/menuapi) [![Patreon](https://img.shields.io/badge/donate-Patreon-orange.svg)](https://www.patreon.com/vespura) -Full safezone scaling supported, both left and right aligned menus supported. +Designed specifically as a replacement of **NativeUI**, MenuAPI features performance improvements, **RedM** _and_ **FiveM** support, improved stability, better features, less bugs, full safezone alignment support for both left and right algined menus (FiveM only) and less (in my opinion) unnecessary features. -This has been coded from the ground up. Using GTA V Decompiled scripts to figure out what some undocumented natives were used for. +This has been coded from the ground up. Using decompiled scripts from GTA V & RedM to figure out what some undocumented natives were used for. ## Installation @@ -17,25 +17,42 @@ You have 2 options: 1. Download the latest release zip and use the correct version (FiveM/RedM) for your resource. Simply include the DLL as a reference in your C# project and add `using MenuAPI;` to each file where you need to use MenuAPI. 2. Use the NuGet package, which can be found [here](https://www.nuget.org/packages/MenuAPI.FiveM/) for FiveM, and [here](https://www.nuget.org/packages/MenuAPI.RedM/) for RedM. -After doing either of the above and you're ready to build and publish your resource, add `files {'MenuAPI.dll'}` to your `fxmanifest.lua` or `__resource.lua` file, and make sure that you include the `MenuAPI.dll` file in the folder of your resource. +After doing either of the above and you're ready to build and publish your resource, add `files {'MenuAPI.dll'}` to your `fxmanifest.lua` file, and make sure that you include the `MenuAPI.dll` file in the folder of your resource. ## Documentation -Minimal documentation is available [here](https://docs.vespura.com/mapi). +Limited documentation is available [here](https://docs.vespura.com/mapi). -Documentation contents will be improved/expanded in the (near) future. +Feel like contributing to the documentation? The repository for the documentation site can be found [here](https://github.com/TomGrobbe/MenuAPI-Docs), thanks! -## Copyright +## Copyright / License -Copyright © Tom Grobbe 2018-2020. +Copyright © Tom Grobbe 2018-2021. -## License (custom license) +MenuAPI is a free resource, using a custom license. +Conditions are listed below. -You can use this API in your own resources, you can edit this API if you want to add features. Feel free to PR them. +### You are allowed to -You can host re-releases of this API, but ONLY as a FORK created via GitHub. If it's not a forked repo, then the release will be taken down by DMCA request. +- Include the pre-built files in your projects, for both commercial and non-commercial use +- Modify this code, feel free to create PR's :) -It's very simple, don't steal my stuff and try to take credit. That's all I ask. +### You are NOT allowed to -When creating a resource, you are **not** required to mention/link this API, as long as you do not claim it to be your own work. -If you feel like it you can link it just because you're nice, but there's no need to do this. I'd appreciate it if you just put the link to this repo somewhere in your credits/readme file.:) +- Sell this code or a modified version of it. +- If you release a paid resource that uses MenuAPI, you are not allowed to include MenuAPI in the resource. You will need to provide a free way for anyone to download the MenuAPI version of your resource. +- Re-release this code without using the Fork feature. + +### You must + +- Provide appropritate credits when including this in your project. +- State any changes you made if you want to re-release this code. +- Keep this license when editing the source code or using this code in your own projects. + +### In short + +It's very simple, don't steal my stuff, don't try to take credit for code that isn't yours and don't try to make money using my work. That's all I ask. + +If you'd like to do something that's not allowed per this license, contact me and we might be able to figure something out. + +Nothing is guaranteed to work, I do not take responsibility for any bugs or damages caused by this code. Use at your own risk. diff --git a/TestMenu/ExampleMenu.cs b/TestMenu/ExampleMenu.cs index d18b782..f693a6f 100644 --- a/TestMenu/ExampleMenu.cs +++ b/TestMenu/ExampleMenu.cs @@ -1,11 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CitizenFX.Core; -using static CitizenFX.Core.Native.API; using MenuAPI; +using CitizenFX.Core; namespace TestMenu { @@ -18,18 +14,22 @@ public ExampleMenu() // To test this, checkout one of the checkbox items in this example menu. Clicking it will toggle the menu alignment. MenuController.MenuAlignment = MenuController.MenuAlignmentOption.Right; #endif - // Creating the first menu. Menu menu = new Menu("Main Menu", "Subtitle"); MenuController.AddMenu(menu); - // Adding a new button by directly creating one inline. You could also just store it and then add it but we don't need to do that in this example. - menu.AddMenuItem(new MenuItem("Normal Button", "This is a simple button with a simple description. Scroll down for more button types!") - { - Enabled = false, - LeftIcon = MenuItem.Icon.TICK - }); - + // Adding a new button by directly creating one inline. + // You could also just store it and then add it but we don't need to do that in this example. + menu.AddMenuItem( + new MenuItem( + "Normal Button", + "This is a simple button with a simple description. Scroll down for more button types!" + ) + { + Enabled = false, + LeftIcon = MenuItem.Icon.TICK + } + ); #if FIVEM // Creating 3 sliders, showing off the 3 possible variations and custom colors. MenuSliderItem slider = new MenuSliderItem("Slider", 0, 10, 5, false); @@ -38,34 +38,51 @@ public ExampleMenu() BarColor = System.Drawing.Color.FromArgb(255, 73, 233, 111), BackgroundColor = System.Drawing.Color.FromArgb(255, 25, 100, 43) }; - MenuSliderItem slider3 = new MenuSliderItem("Slider + Bar + Icons", "The icons are currently male/female because that's probably the most common use. But any icon can be used!", 0, 10, 5, true) + MenuSliderItem slider3 = new MenuSliderItem( + "Slider + Bar + Icons", + "The icons are currently male/female because that's probably the most common use. But any icon can be used!", + 0, + 10, + 5, + true + ) { BarColor = System.Drawing.Color.FromArgb(255, 255, 0, 0), BackgroundColor = System.Drawing.Color.FromArgb(255, 100, 0, 0), - SliderLeftIcon = MenuItem.Icon.MALE, SliderRightIcon = MenuItem.Icon.FEMALE }; + // adding the sliders to the menu. menu.AddMenuItem(slider); menu.AddMenuItem(slider2); menu.AddMenuItem(slider3); - #endif - #if FIVEM // Creating 3 checkboxs, 2 different styles and one has a locked icon and it's 'not enabled' (not enabled meaning you can't toggle it). - MenuCheckboxItem box = new MenuCheckboxItem("Checkbox - Style 1 (click me!)", "This checkbox can toggle the menu position! Try it out.", !menu.LeftAligned) + MenuCheckboxItem box = new MenuCheckboxItem( + "Checkbox - Style 1 (click me!)", + "This checkbox can toggle the menu position! Try it out.", + !menu.LeftAligned + ) { Style = MenuCheckboxItem.CheckboxStyle.Cross }; #endif - MenuCheckboxItem box2 = new MenuCheckboxItem("Checkbox - Style 2", "This checkbox does nothing right now.", true) + MenuCheckboxItem box2 = new MenuCheckboxItem( + "Checkbox - Style 2", + "This checkbox does nothing right now.", + true + ) { Style = MenuCheckboxItem.CheckboxStyle.Tick }; - MenuCheckboxItem box3 = new MenuCheckboxItem("Checkbox (unchecked + locked)", "Make this menu right aligned. If you set this to false, then the menu will move to the left.", false) + MenuCheckboxItem box3 = new MenuCheckboxItem( + "Checkbox (unchecked + locked)", + "Make this menu right aligned. If you set this to false, then the menu will move to the left.", + false + ) { Enabled = false, LeftIcon = MenuItem.Icon.LOCK @@ -85,7 +102,12 @@ string ChangeCallback(MenuDynamicListItem item, bool left) return (int.Parse(item.CurrentItem) - 1).ToString(); return (int.Parse(item.CurrentItem) + 1).ToString(); } - MenuDynamicListItem dynList = new MenuDynamicListItem("Dynamic list item.", "0", new MenuDynamicListItem.ChangeItemCallback(ChangeCallback), "Description for this dynamic item. Pressing left will make the value smaller, pressing right will make the value bigger."); + MenuDynamicListItem dynList = new MenuDynamicListItem( + "Dynamic list item.", + "0", + new MenuDynamicListItem.ChangeItemCallback(ChangeCallback), + "Description for this dynamic item. Pressing left will make the value smaller, pressing right will make the value bigger." + ); menu.AddMenuItem(dynList); #if FIVEM // List items (first the 3 special variants, then a normal one) @@ -94,7 +116,12 @@ string ChangeCallback(MenuDynamicListItem item, bool left) { colorList.Add($"Color #{i}"); } - MenuListItem hairColors = new MenuListItem("Hair Color", colorList, 0, "Hair color pallete.") + MenuListItem hairColors = new MenuListItem( + "Hair Color", + colorList, + 0, + "Hair color pallete." + ) { ShowColorPanel = true }; @@ -128,7 +155,12 @@ string ChangeCallback(MenuDynamicListItem item, bool left) #endif // Normal List normalList = new List() { "Item #1", "Item #2", "Item #3" }; - MenuListItem normalListItem = new MenuListItem("Normal List Item", normalList, 0, "And another simple description for yet another simple (list) item. Nothing special about this one."); + MenuListItem normalListItem = new MenuListItem( + "Normal List Item", + normalList, + 0, + "And another simple description for yet another simple (list) item. Nothing special about this one." + ); // Adding the lists to the menu. menu.AddMenuItem(normalListItem); @@ -137,7 +169,10 @@ string ChangeCallback(MenuDynamicListItem item, bool left) Menu submenu = new Menu("Submenu", "Secondary Menu"); MenuController.AddSubmenu(menu, submenu); - MenuItem menuButton = new MenuItem("Submenu", "This button is bound to a submenu. Clicking it will take you to the submenu.") + MenuItem menuButton = new MenuItem( + "Submenu", + "This button is bound to a submenu. Clicking it will take you to the submenu." + ) { #if FIVEM Label = "→→→" @@ -152,7 +187,10 @@ string ChangeCallback(MenuDynamicListItem item, bool left) // Adding items with sprites left & right to the submenu. for (var i = 0; i < Enum.GetValues(typeof(MenuItem.Icon)).Length; i++) { - var tmpItem = new MenuItem($"Icon.{Enum.GetName(typeof(MenuItem.Icon), ((MenuItem.Icon)i))}", "This menu item has a left and right sprite. Press ~r~HOME~s~ to toggle the 'enabled' state on these items.") + var tmpItem = new MenuItem( + $"Icon.{Enum.GetName(typeof(MenuItem.Icon), ((MenuItem.Icon)i))}", + "This menu item has a left and right sprite. Press ~r~HOME~s~ to toggle the 'enabled' state on these items." + ) { Label = $"(#{i})", #if FIVEM @@ -161,19 +199,16 @@ string ChangeCallback(MenuDynamicListItem item, bool left) LeftIcon = (MenuItem.Icon)i }; - //var tmpItem2 = new MenuItem($"Icon.{Enum.GetName(typeof(MenuItem.Icon), ((MenuItem.Icon)i))}", "This menu item has a left and right sprite, and it's ~h~disabled~h~."); - //tmpItem2.LeftIcon = (MenuItem.Icon)i; - //tmpItem2.RightIcon = (MenuItem.Icon)i; - //tmpItem2.Enabled = false; - submenu.AddMenuItem(tmpItem); - //submenu.AddMenuItem(tmpItem2); } - submenu.ButtonPressHandlers.Add(new Menu.ButtonPressHandler(Control.FrontendSocialClubSecondary, Menu.ControlPressCheckType.JUST_RELEASED, new Action((m, c) => - { - m.GetMenuItems().ForEach(a => a.Enabled = !a.Enabled); - }), true)); - + submenu.ButtonPressHandlers.Add( + new Menu.ButtonPressHandler(Control.FrontendSocialClubSecondary, + Menu.ControlPressCheckType.JUST_RELEASED, + new Action((m, c) => + { + m.GetMenuItems().ForEach(a => a.Enabled = !a.Enabled); + }), true) + ); #if FIVEM // Instructional buttons setup for the second (submenu) menu. submenu.InstructionalButtons.Add(Control.CharacterWheel, "Right?!"); @@ -182,19 +217,21 @@ string ChangeCallback(MenuDynamicListItem item, bool left) submenu.InstructionalButtons.Add(Control.Cover, "This"); submenu.InstructionalButtons.Add(Control.Context, "Check"); #endif - // Create a third menu without a banner. Menu menu3 = new Menu(null, "Only a subtitle, no banner."); // you can use AddSubmenu or AddMenu, both will work but if you want to link this menu from another menu, // you should use AddSubmenu. MenuController.AddSubmenu(menu, menu3); - MenuItem thirdSubmenuBtn = new MenuItem("Another submenu", "This is just a submenu without a banner. No big deal. This also has a very long description to test multiple lines and see if they work properly. Let's find out if it works as intended.") + MenuItem thirdSubmenuBtn = new MenuItem( + "Another submenu", + "This is just a submenu without a banner. No big deal. This also has a very long description to test multiple " + + "lines and see if they work properly. Let's find out if it works as intended." + ) { #if FIVEM Label = "→→→" #endif - #if REDM RightIcon = MenuItem.Icon.ARROW_RIGHT #endif @@ -211,12 +248,30 @@ string ChangeCallback(MenuDynamicListItem item, bool left) menu.AddMenuItem(new MenuItem($"Item #{i + 1}.", "With an invisible description.")); } - /* -######################################################## - Event handlers -######################################################## - */ - +#if FIVEM + // Create menu with weapon stats panel + Menu menu4 = new Menu("Weapon Stats", "Weapon Stats Panel") { ShowWeaponStatsPanel = true }; + menu4.AddMenuItem(new MenuItem("dummy item", "You should add at least one item when using weapon stat panels")); + menu4.SetWeaponStats(0.2f, 0.4f, 0.7f, 0.8f); + menu4.SetWeaponComponentStats(0.4f, 0f, -0.05f, 0.1f); + MenuController.AddSubmenu(menu, menu4); + MenuItem weaponStats = new MenuItem("Weapon stats", "Demo menu for weapon stats components"); + menu.AddMenuItem(weaponStats); + MenuController.BindMenuItem(menu, menu4, weaponStats); + + // Create menu with vehicle stats panel + Menu menu5 = new Menu("Vehicle Stats", "Vehicle Stats Panel") { ShowVehicleStatsPanel = true }; + menu5.AddMenuItem(new MenuItem("dummy item", "You should add at least one item when using vehicle stat panels")); + menu5.SetVehicleStats(0.2f, 0.2f, 0.3f, 0.8f); + menu5.SetVehicleUpgradeStats(0.4f, -0.025f, 0.05f, 0.1f); + MenuController.AddSubmenu(menu, menu5); + MenuItem vehicleStats = new MenuItem("Vehicle stats", "Demo menu for vehicle stats components"); + menu.AddMenuItem(vehicleStats); + MenuController.BindMenuItem(menu, menu5, vehicleStats); +#endif + /*-------------- + Event handlers + --------------*/ menu.OnCheckboxChange += (_menu, _item, _index, _checked) => { @@ -299,15 +354,6 @@ Event handlers // Code in here would get executed whenever a dynamic list item is pressed. Debug.WriteLine($"OnDynamicListItemSelect: [{_menu}, {_dynamicListItem}, {_currentItem}]"); }; - - //DelayedConstructor(); } - - //private async void DelayedConstructor() - //{ - // await Delay(1000); - // MenuController.MainMenu.OpenMenu(); - //} - } } diff --git a/TestMenu/TestMenu.csproj b/TestMenu/TestMenu.csproj index da69f49..95d6e9d 100644 --- a/TestMenu/TestMenu.csproj +++ b/TestMenu/TestMenu.csproj @@ -20,7 +20,7 @@ - + runtime diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 239edc7..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,44 +0,0 @@ -pull_requests: - do_not_increment_build_number: false -image: Visual Studio 2019 -configuration: - - Release RedM - - Release FiveM -before_build: - - nuget restore - - cmd: if %APPVEYOR_REPO_TAG%==true (appveyor SetVariable -Name VERSION_NAME -Value %APPVEYOR_REPO_TAG_NAME%) else (appveyor SetVariable -Name VERSION_NAME -Value dev-%APPVEYOR_BUILD_VERSION%) - - cmd: if "%CONFIGURATION%"=="Release FiveM" (appveyor SetVariable -Name GAME -Value "FiveM") else if "%CONFIGURATION%"=="Release RedM" (appveyor SetVariable -Name GAME -Value "RedM") -build: - parallel: false - project: MenuAPI.sln - include_nuget_references: true - verbosity: minimal -after_build: - - cmd: copy "README.md" ".\MenuAPI\bin\README.md" - - cmd: 7z a MenuAPI-%VERSION_NAME%-%GAME%.zip -r ".\MenuAPI\bin\*" - - cmd: appveyor PushArtifact MenuAPI-%VERSION_NAME%-%GAME%.zip -deploy: - - provider: NuGet - api_key: $(NUGET_API_KEY) - skip_symbols: true - on: - APPVEYOR_REPO_TAG: true - - provider: GitHub - release: "[Release] MenuAPI $(VERSION_NAME)" - tag: $(VERSION_NAME) - artifact: MenuAPI-$(VERSION_NAME)-$(GAME).zip - draft: false - prerelease: false - auth_token: $(github_auth) - on: - APPVEYOR_REPO_TAG: true - description: "MenuAPI release, version $(VERSION_NAME). Download the **FiveM** or **RedM** versions below, by selecting the corresponding zip files.\n\nFor info check the [docs](https://docs.vespura.com/mapi) or checkout the recent commits on GitHub (the latter is your best bet if you want updated info about this version)." -before_deploy: - - ps: $env:NUGET_VERSION=(($env:VERSION_NAME).Substring(1)) - - cmd: if "%CONFIGURATION%"=="Release FiveM" (cd MenuAPI && nuget pack MenuAPI.csproj -Properties Configuration="Release FiveM";Id="MenuAPI.FiveM" -Version "%NUGET_VERSION%" && appveyor PushArtifact MenuAPI.FiveM.%NUGET_VERSION%.nupkg && cd ..) else (cd MenuAPI && nuget pack MenuAPI.csproj -Properties Configuration="Release RedM";Id="MenuAPI.RedM" -Version "%NUGET_VERSION%" && appveyor PushArtifact MenuAPI.RedM.%NUGET_VERSION%.nupkg && cd ..) -after_deploy: - - cmd: appveyor/after_deploy.cmd -on_success: - - cmd: appveyor/on_success.cmd -on_failure: - - cmd: appveyor/on_failure.cmd diff --git a/appveyor/after_deploy.cmd b/appveyor/after_deploy.cmd deleted file mode 100644 index 1788a04..0000000 --- a/appveyor/after_deploy.cmd +++ /dev/null @@ -1,6 +0,0 @@ -if not defined WEBHOOK_URL goto end -if not "%CONFIGURATION%"=="Release FiveM" goto end - -curl -H "Content-Type:application/json" -X POST -d "{\"content\":\"^<^@^&540562517806809109^>\",\"embeds\":[{\"title\":\"%APPVEYOR_PROJECT_NAME% (%VERSION_NAME%)\",\"description\":\"New version of **%APPVEYOR_PROJECT_NAME%** (%VERSION_NAME%) is available for both **FiveM** and **RedM** Download it by using the files in this chanel or by going to the GitHub Tag link!\",\"color\":3048181,\"author\":{\"name\":\"Committed by %APPVEYOR_ACCOUNT_NAME%\",\"url\":\"https://github.com/%APPVEYOR_ACCOUNT_NAME%/\"},\"fields\":[{\"name\":\"AppVeyor Build\",\"value\":\"[Here](%APPVEYOR_URL%/project/%APPVEYOR_ACCOUNT_NAME%/%APPVEYOR_PROJECT_SLUG%/builds/%APPVEYOR_BUILD_ID%)\"},{\"name\":\"GitHub Commit (%APPVEYOR_REPO_COMMIT%)\",\"value\":\"[%APPVEYOR_REPO_COMMIT%](https://github.com/%APPVEYOR_ACCOUNT_NAME%/%APPVEYOR_PROJECT_NAME%/commit/%APPVEYOR_REPO_COMMIT%) - %APPVEYOR_REPO_COMMIT_MESSAGE%%APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED%\"},{\"name\":\"GitHub Branch\",\"value\":\"[%APPVEYOR_REPO_BRANCH%](https://github.com/%APPVEYOR_ACCOUNT_NAME%/%APPVEYOR_PROJECT_NAME%/tree/%APPVEYOR_REPO_BRANCH%)\"},{\"name\":\"GitHub Tag\",\"value\":\"[%APPVEYOR_REPO_TAG_NAME%](https://github.com/%APPVEYOR_ACCOUNT_NAME%/%APPVEYOR_PROJECT_NAME%/releases/tag/%APPVEYOR_REPO_TAG_NAME%)\"}]}]}" %WEBHOOK_URL% - -:end \ No newline at end of file diff --git a/appveyor/on_failure.cmd b/appveyor/on_failure.cmd deleted file mode 100644 index 1aaaccc..0000000 --- a/appveyor/on_failure.cmd +++ /dev/null @@ -1,5 +0,0 @@ -if not defined WEBHOOK_URL goto end - -curl -H "Content-Type:application/json" -X POST -d "{\"embeds\":[{\"title\":\"%APPVEYOR_PROJECT_NAME% (%VERSION_NAME%-%GAME%)\",\"description\":\"Build FAILED! Ouch.\",\"color\":13632027,\"author\":{\"name\":\"Committed by %APPVEYOR_ACCOUNT_NAME%\",\"url\":\"https://github.com/%APPVEYOR_ACCOUNT_NAME%/\"},\"fields\":[{\"name\":\"AppVeyor Build\",\"value\":\"[Here](%APPVEYOR_URL%/project/%APPVEYOR_ACCOUNT_NAME%/%APPVEYOR_PROJECT_SLUG%/builds/%APPVEYOR_BUILD_ID%)\"},{\"name\":\"GitHub Commit (%APPVEYOR_REPO_COMMIT%)\",\"value\":\"[%APPVEYOR_REPO_COMMIT%](https://github.com/%APPVEYOR_ACCOUNT_NAME%/%APPVEYOR_PROJECT_NAME%/commit/%APPVEYOR_REPO_COMMIT%) - %APPVEYOR_REPO_COMMIT_MESSAGE%%APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED%\"},{\"name\":\"GitHub Branch\",\"value\":\"[%APPVEYOR_REPO_BRANCH%](https://github.com/%APPVEYOR_ACCOUNT_NAME%/%APPVEYOR_PROJECT_NAME%/tree/%APPVEYOR_REPO_BRANCH%)\"}]}]}" %WEBHOOK_URL% - -:end \ No newline at end of file diff --git a/appveyor/on_success.cmd b/appveyor/on_success.cmd deleted file mode 100644 index 4454420..0000000 --- a/appveyor/on_success.cmd +++ /dev/null @@ -1,7 +0,0 @@ -if not defined WEBHOOK_URL goto end -curl -s -o nul -F "file=@MenuAPI-%VERSION_NAME%-%GAME%.zip" %WEBHOOK_URL% - -if %APPVEYOR_REPO_TAG%==true goto end -curl -H "Content-Type:application/json" -X POST -d "{\"embeds\":[{\"title\":\"%APPVEYOR_PROJECT_NAME% (%VERSION_NAME%-%GAME%)\",\"description\":\"Build passed!\",\"color\":4502298,\"author\":{\"name\":\"Committed by %APPVEYOR_ACCOUNT_NAME%\",\"url\":\"https://github.com/%APPVEYOR_ACCOUNT_NAME%/\"},\"fields\":[{\"name\":\"AppVeyor Build\",\"value\":\"[Here](%APPVEYOR_URL%/project/%APPVEYOR_ACCOUNT_NAME%/%APPVEYOR_PROJECT_SLUG%/builds/%APPVEYOR_BUILD_ID%)\"},{\"name\":\"GitHub Commit (%APPVEYOR_REPO_COMMIT%)\",\"value\":\"[%APPVEYOR_REPO_COMMIT%](https://github.com/%APPVEYOR_ACCOUNT_NAME%/%APPVEYOR_PROJECT_NAME%/commit/%APPVEYOR_REPO_COMMIT%) - %APPVEYOR_REPO_COMMIT_MESSAGE%%APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED%\"},{\"name\":\"GitHub Branch\",\"value\":\"[%APPVEYOR_REPO_BRANCH%](https://github.com/%APPVEYOR_ACCOUNT_NAME%/%APPVEYOR_PROJECT_NAME%/tree/%APPVEYOR_REPO_BRANCH%)\"}]}]}" %WEBHOOK_URL% - -:end \ No newline at end of file diff --git a/dependencies/RedM/CitizenFX.Core.dll b/dependencies/RedM/CitizenFX.Core.dll index ad51a15..401e98f 100644 Binary files a/dependencies/RedM/CitizenFX.Core.dll and b/dependencies/RedM/CitizenFX.Core.dll differ diff --git a/dependencies/RedM/CitizenFX.Core.xml b/dependencies/RedM/CitizenFX.Core.xml index a34ec70..734fd34 100644 --- a/dependencies/RedM/CitizenFX.Core.xml +++ b/dependencies/RedM/CitizenFX.Core.xml @@ -6465,11 +6465,21 @@ Loads a minimap overlay from a GFx file in the current resource. + + + Loads a minimap overlay from a GFx file in the current resource. + + Experimental natives, please do not use in a live environment. + + + Experimental natives, please do not use in a live environment. + + Deletes the given context from the background scripts context map. @@ -6496,6 +6506,14 @@ + + This is similar to the PushScaleformMovieFunction natives, except it calls in the `TIMELINE` of a minimap overlay. + + + Cancels the currently executing event. + + + Cancels the currently executing event. @@ -6505,27 +6523,56 @@ Commits the backing pixels to the specified runtime texture. + + + Commits the backing pixels to the specified runtime texture. + + Creates a DUI browser. This can be used to draw on a runtime texture using CREATE_RUNTIME_TEXTURE_FROM_DUI_HANDLE. + + + Creates a DUI browser. This can be used to draw on a runtime texture using CREATE_RUNTIME_TEXTURE_FROM_DUI_HANDLE. + + Creates a blank runtime texture. + + Creates a blank runtime texture. + Creates a runtime texture from a DUI handle. + + Creates a runtime texture from a DUI handle. + Creates a runtime texture from the specified file in the current resource. + + Creates a runtime texture from the specified file in the current resource. + + + Creates a runtime texture dictionary with the specified name. + Example: + + ```lua + local txd = CreateRuntimeTxd('meow') + ``` + + + Creates a runtime texture dictionary with the specified name. Example: @@ -6540,6 +6587,22 @@ Destroys a DUI browser. + + + Destroys a DUI browser. + + + + + Returns a list of door system entries: a door system hash (see [ADD_DOOR_TO_SYSTEM](#_0x6F8838D03D1DC226)) and its object handle. + + The data returned adheres to the following layout: + + ``` + [{doorHash1, doorHandle1}, ..., {doorHashN, doorHandleN}] + ``` + + This native is not implemented. @@ -6549,16 +6612,35 @@ This native is not implemented. + + This native is not implemented. + This native is not implemented. + + This native is not implemented. + This native is not implemented. + + This native is not implemented. + + + + + This native is not implemented. + + + + + Forces the game snow pass to render. + @@ -6570,43 +6652,128 @@ ``` + + + Returns all player indices for 'active' physical players known to the client. + The data returned adheres to the following layout: + + ``` + [127, 42, 13, 37] + ``` + + + + + A getter for [SET_AMBIENT_PED_RANGE_MULTIPLIER_THIS_FRAME](#_0x0B919E1FB47CC4E0). + + + + + A getter for [SET_AMBIENT_VEHICLE_RANGE_MULTIPLIER_THIS_FRAME](#_0x90B6DA738A9A25DA). + + Returns the world matrix of the specified camera. To turn this into a view matrix, calculate the inverse. + + + Returns the world matrix of the specified camera. To turn this into a view matrix, calculate the inverse. + + Returns the name of the currently executing resource. + + + Returns the name of the currently executing resource. + + Returns the peer address of the remote game server that the user is currently connected to. + + + Returns the peer address of the remote game server that the user is currently connected to. + + Returns the NUI window handle for a specified DUI browser object. + + + Returns the NUI window handle for a specified DUI browser object. + + + + + Returns all pool handles for the given pool name; the data returned adheres to the following layout: + + ``` + [ 770, 1026, 1282, 1538, 1794, 2050, 2306, 2562, 2818, 3074, 3330, 3586, 3842, 4098, 4354, 4610, ...] + ``` + + ### Supported Pools + + **1**: CPed + **2**: CObject + **3**: CVehicle + **4**: CPickup + + Returns the zoom level data by index from mapzoomdata.meta file. + + + Returns the zoom level data by index from mapzoomdata.meta file. + + Gets the amount of metadata values with the specified key existing in the specified resource's manifest. See also: [Resource manifest](https://docs.fivem.net/resources/manifest/) + + + Gets the amount of metadata values with the specified key existing in the specified resource's manifest. + See also: [Resource manifest](https://docs.fivem.net/resources/manifest/) + + + + + A getter for [SET_PARKED_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME](#_0xEAE6DCC7EEE3DB1D). + + + + + A getter for [SET_PED_DENSITY_MULTIPLIER_THIS_FRAME](#_0x95E3D6257B166CF2). + + A getter for [\_SET_PED_EYE_COLOR](#_0x50B56988B170AFDF). Returns -1 if fails to get. + + A getter for [\_SET_PED_EYE_COLOR](#_0x50B56988B170AFDF). Returns -1 if fails to get. + + + A getter for [\_SET_PED_FACE_FEATURE](#_0x71A5C1DBA060049E). Returns 0.0 if fails to get. + + + A getter for [\_SET_PED_FACE_FEATURE](#_0x71A5C1DBA060049E). Returns 0.0 if fails to get. @@ -6620,12 +6787,64 @@ A getter for [\_SET_PED_HAIR_COLOR](#_0x4CFFC65454C93A49). Returns -1 if fails to get. + + A getter for [\_SET_PED_HAIR_COLOR](#_0x4CFFC65454C93A49). Returns -1 if fails to get. + + + A getter for [\_SET_PED_HAIR_COLOR](#_0x4CFFC65454C93A49). Returns -1 if fails to get. + A getter for [SET_PED_HEAD_OVERLAY](#_0x48F44967FA05CC1E) and [\_SET_PED_HEAD_OVERLAY_COLOR](#_0x497BF74A7B9CB952) natives. + + + A getter for [SET_PED_HEAD_OVERLAY](#_0x48F44967FA05CC1E) and [\_SET_PED_HEAD_OVERLAY_COLOR](#_0x497BF74A7B9CB952) natives. + + + + + A getter for [SET_PLAYER_MELEE_WEAPON_DAMAGE_MODIFIER](#_0x4A3DC7ECCC321032). + + + + + A getter for [SET_PLAYER_MELEE_WEAPON_DEFENSE_MODIFIER](#_0xAE540335B4ABC4E2). + + + + + A getter for [SET_PLAYER_VEHICLE_DAMAGE_MODIFIER](#_0xA50E117CDDF82F0C). + + + + + A getter for [SET_PLAYER_VEHICLE_DEFENSE_MODIFIER](#_0x4C60E6EFDAFF2462). + + + + + A getter for [SET_PLAYER_WEAPON_DAMAGE_MODIFIER](#_0xCE07B9F7817AADA3). + + + + + A getter for [SET_PLAYER_WEAPON_DEFENSE_MODIFIER](#_0x2D83BC011CA14A3C). + + + + + A getter for [\_SET_PLAYER_WEAPON_DEFENSE_MODIFIER_2](#_0xBCFDE9EDE4CF27DC). + + + + + A getter for [SET_RANDOM_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME](#_0xB3B3359379FE77D3). + Same as vehicle density multiplier. + + Returns all commands that are registered in the command system. @@ -6643,6 +6862,23 @@ ``` + + + Returns all commands that are registered in the command system. + The data returned adheres to the following layout: + + ``` + [ + { + "name": "cmdlist" + }, + { + "name": "command1" + } + ] + ``` + + Gets the metadata value at a specified key/index from a resource's manifest. @@ -6650,6 +6886,15 @@ + + Gets the metadata value at a specified key/index from a resource's manifest. + See also: [Resource manifest](https://docs.fivem.net/resources/manifest/) + + + Returns the current state of the specified resource. + + + Returns the current state of the specified resource. @@ -6660,15 +6905,51 @@ + + Gets the height of the specified runtime texture. + Gets the row pitch of the specified runtime texture, for use when creating data for `SET_RUNTIME_TEXTURE_ARGB_DATA`. + + Gets the row pitch of the specified runtime texture, for use when creating data for `SET_RUNTIME_TEXTURE_ARGB_DATA`. + + + Gets the width of the specified runtime texture. + + + Gets the width of the specified runtime texture. + + + A getter for [SET_SCENARIO_PED_DENSITY_MULTIPLIER_THIS_FRAME](#_0x7A556143A1C03898). + + + + + Returns the value of a state bag key. + + + + + A getter for [SET_VEHICLE_CHEAT_POWER_INCREASE](#_0xB59E4BD37AE292DB). + + + + + A getter for [SET_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME](#_0x245A6883D966D537). + + + + + Gets a vehicle's multiplier used with a wheel's GET_VEHICLE_WHEEL_STEERING_ANGLE to determine the angle the wheel is rendered. + + Returns the effective handling data of a vehicle as a floating-point value. @@ -6676,12 +6957,26 @@ + + Returns the effective handling data of a vehicle as a floating-point value. + Example: `local fSteeringLock = GetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fSteeringLock')` + Returns the effective handling data of a vehicle as an integer value. Example: `local modelFlags = GetVehicleHandlingInt(vehicle, 'CHandlingData', 'strModelFlags')` + + Returns the effective handling data of a vehicle as an integer value. + Example: `local modelFlags = GetVehicleHandlingInt(vehicle, 'CHandlingData', 'strModelFlags')` + + + Returns the effective handling data of a vehicle as a vector value. + Example: `local inertiaMultiplier = GetVehicleHandlingVector(vehicle, 'CHandlingData', 'vecInertiaMultiplier')` + + + Returns the effective handling data of a vehicle as a vector value. Example: `local inertiaMultiplier = GetVehicleHandlingVector(vehicle, 'CHandlingData', 'vecInertiaMultiplier')` @@ -6692,6 +6987,16 @@ Gets the vehicle indicator light state. 0 = off, 1 = left, 2 = right, 3 = both + + + Gets the vehicle indicator light state. 0 = off, 1 = left, 2 = right, 3 = both + + + + + A getter for [MODIFY_VEHICLE_TOP_SPEED](#_0x93A3996368C94158). Returns -1.0 if a modifier is not set. + + List of known states: @@ -6703,31 +7008,97 @@ ``` - + - Gets speed of a wheel at the tyre. + List of known states: + + ``` + 1: Not wheeling. + 65: Vehicle is ready to do wheelie (burnouting). + 129: Vehicle is doing wheelie. + ``` + + + Gets brake pressure of a wheel. Max number of wheels can be retrieved with the native GET_VEHICLE_NUMBER_OF_WHEELS. - + - Returns the offset of the specified wheel relative to the wheel's axle center. + Returns vehicle's wheels' size (size is the same for all the wheels, cannot get/set specific wheel of vehicle). + Only works on non-default wheels (returns 0 in case of default wheels). - + - Returns whether or not the specific minimap overlay has loaded. + Gets speed of a wheel at the tyre. + Max number of wheels can be retrieved with the native GET_VEHICLE_NUMBER_OF_WHEELS. - + - <!-- Native implemented by Disquse. 0xFFF65C63 --> - - Returns true if the minimap is currently expanded. False if it's the normal minimap state. - Use [`IsBigmapFull`](#_0x66EE14B2) to check if the full map is currently revealed on the minimap. + Gets speed of a wheel at the tyre. + Max number of wheels can be retrieved with the native GET_VEHICLE_NUMBER_OF_WHEELS. + + + Gets steering angle of a wheel. + Max number of wheels can be retrieved with the native GET_VEHICLE_NUMBER_OF_WHEELS. + + + + + Returns vehicle's wheels' width (width is the same for all the wheels, cannot get/set specific wheel of vehicle). + Only works on non-default wheels (returns 0 in case of default wheels). + + + + + Returns the offset of the specified wheel relative to the wheel's axle center. + + + + + Returns the offset of the specified wheel relative to the wheel's axle center. + + + + + A getter for [\_SET_WEAPON_DAMAGE_MODIFIER](#_0x4757F00BC6323CFE). + + + + + Returns whether or not the specific minimap overlay has loaded. + + + + + Returns whether or not the specific minimap overlay has loaded. + + + + + <!-- Native implemented by Disquse. 0xFFF65C63 --> + + Returns true if the minimap is currently expanded. False if it's the normal minimap state. + Use [`IsBigmapFull`](#_0x66EE14B2) to check if the full map is currently revealed on the minimap. + + <!-- Native implemented by Disquse. 0xFFF65C63 --> + + Returns true if the minimap is currently expanded. False if it's the normal minimap state. + Use [`IsBigmapFull`](#_0x66EE14B2) to check if the full map is currently revealed on the minimap. + + + <!-- Native implemented by Disquse. 0x66EE14B2 --> + + Returns true if the full map is currently revealed on the minimap. + Use [`IsBigmapActive`](#_0xFFF65C63) to check if the minimap is currently expanded or in it's normal state. + + + <!-- Native implemented by Disquse. 0x66EE14B2 --> @@ -6741,6 +7112,14 @@ + + Returns whether or not a browser is created for a specified DUI browser object. + + + Gets whether or not this is the CitizenFX server. + + + Gets whether or not this is the CitizenFX server. @@ -6752,6 +7131,13 @@ Returns whether an asynchronous streaming file registration completed. + + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + + Returns whether an asynchronous streaming file registration completed. + + Reads the contents of a text file in a specified resource. @@ -6759,27 +7145,121 @@ Example: `local data = LoadResourceFile("devtools", "data.json")` + + + Reads the contents of a text file in a specified resource. + If executed on the client, this file has to be included in `files` in the resource manifest. + Example: `local data = LoadResourceFile("devtools", "data.json")` + + + + + Starts listening to the specified channel, when available. + + + + + Adds the specified channel to the target list for the specified Mumble voice target ID. + + + + + Adds the specified player to the target list for the specified Mumble voice target ID. + + + + + Adds the specified player to the target list for the specified Mumble voice target ID. + + + + + Clears the target list for the specified Mumble voice target ID. + + + + + Clears channels from the target list for the specified Mumble voice target ID. + + + + + Clears players from the target list for the specified Mumble voice target ID. + + + + + Returns the mumble voice channel from a player's server id. + + + + + This native will return true if the user succesfully connected to the voice server. + If the user disabled the voice-chat setting it will return false. + + + + + Stops listening to the specified channel. + + + + + Changes the Mumble server address to connect to, and reconnects to the new address. + + + + + Sets the current Mumble voice target ID to broadcast voice to. + + + + + Overrides the output volume for a particular player on Mumble. This will also bypass 3D audio and distance calculations. -1.0 to reset the override. + + Set to -1.0 to reset the Volume override. + + + + + Overrides the output volume for a particular player with the specified server id and player name on Mumble. This will also bypass 3D audio and distance calculations. -1.0 to reset the override. + + Returns the owner ID of the specified entity. + + + Returns the owner ID of the specified entity. + + Scope entry for profiler. + + Scope entry for profiler. + Scope exit for profiler. + + Scope exit for profiler. + Returns true if the profiler is active. + + Returns true if the profiler is active. + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. @@ -6787,6 +7267,11 @@ + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + + Registers a set of archetypes with the game engine. These should match `CBaseArchetypeDef` class information from the game. + Registered commands can be executed by entering them in the client console (this works for client side and server side registered commands). Or by entering them in the server console/through an RCON client (only works for server side registered commands). Or if you use a supported chat resource, like the default one provided in the cfx-server-data repository, then you can enter the command in chat by prefixing it with a `/`. @@ -6800,6 +7285,17 @@ + + Registered commands can be executed by entering them in the client console (this works for client side and server side registered commands). Or by entering them in the server console/through an RCON client (only works for server side registered commands). Or if you use a supported chat resource, like the default one provided in the cfx-server-data repository, then you can enter the command in chat by prefixing it with a `/`. + + Commands registered using this function can also be executed by resources, using the [`ExecuteCommand` native](#_0x561C060B). + + The restricted bool is not used on the client side. Permissions can only be checked on the server side, so if you want to limit your command with an ace permission automatically, make it a server command (by registering it in a server script). + + **Example result**: + + ![](https://i.imgur.com/TaCnG09.png) + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. @@ -6808,21 +7304,48 @@ + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + + Registers a set of entities with the game engine. These should match `CEntityDef` class information from the game. + At this time, this function **should not be used in a live environment**. + Registers a specified .gfx file as GFx font library. The .gfx file has to be registered with the streamer already. + + Registers a specified .gfx file as GFx font library. + The .gfx file has to be registered with the streamer already. + + + Registers a specified font name for use with text draw commands. + + + Registers a specified font name for use with text draw commands. + + + Registers a key mapping for the current resource. + + See the related [cookbook post](https://cookbook.fivem.net/2020/01/06/using-the-new-console-key-bindings/) for more information. + + An internal function which allows the current resource's HLL script runtimes to receive state for the specified event. + + + An internal function which allows the current resource's HLL script runtimes to receive state for the specified event. + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. @@ -6834,6 +7357,11 @@ **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + Registers a dynamic streaming asset from the server with the GTA streaming module system. + + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + Registers a KVP value as an asset with the GTA streaming module system. This function currently won't work. @@ -6841,6 +7369,20 @@ **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + Registers a KVP value as an asset with the GTA streaming module system. This function currently won't work. + + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + + Registers a file from an URL as a streaming asset in the GTA streaming subsystem. This will asynchronously register the asset, and caching is done based on the URL itself - cache headers are ignored. + + Use `IS_STREAMING_FILE_READY` to check if the asset has been registered successfully. + + + + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + Registers a file from an URL as a streaming asset in the GTA streaming subsystem. This will asynchronously register the asset, and caching is done based on the URL itself - cache headers are ignored. Use `IS_STREAMING_FILE_READY` to check if the asset has been registered successfully. @@ -6851,37 +7393,67 @@ Experimental natives, please do not use in a live environment. + + + Experimental natives, please do not use in a live environment. + + Resets values from the zoom level data by index to defaults from mapzoomdata.meta. + + + Resets values from the zoom level data by index to defaults from mapzoomdata.meta. + + Sends a message to the specific DUI root page. This is similar to SEND_NUI_MESSAGE. + + Sends a message to the specific DUI root page. This is similar to SEND_NUI_MESSAGE. + Injects a 'mouse down' event for a DUI object. Coordinates are expected to be set using SEND_DUI_MOUSE_MOVE. + + Injects a 'mouse down' event for a DUI object. Coordinates are expected to be set using SEND_DUI_MOUSE_MOVE. + Injects a 'mouse move' event for a DUI object. Coordinates are in browser space. + + Injects a 'mouse move' event for a DUI object. Coordinates are in browser space. + Injects a 'mouse up' event for a DUI object. Coordinates are expected to be set using SEND_DUI_MOUSE_MOVE. + + Injects a 'mouse up' event for a DUI object. Coordinates are expected to be set using SEND_DUI_MOUSE_MOVE. + Injects a 'mouse wheel' event for a DUI object. + + Injects a 'mouse wheel' event for a DUI object. + + + Sends a message to the `loadingScreen` NUI frame, which contains the HTML page referenced in `loadscreen` resources. + + + Sends a message to the `loadingScreen` NUI frame, which contains the HTML page referenced in `loadscreen` resources. @@ -6892,21 +7464,38 @@ + + This native sets the app id for the discord rich presence implementation. + This native sets the image asset for the discord rich presence implementation. + + This native sets the image asset for the discord rich presence implementation. + This native sets the small image asset for the discord rich presence implementation. + + This native sets the small image asset for the discord rich presence implementation. + This native sets the hover text of the small image asset for the discord rich presence implementation. + + This native sets the hover text of the small image asset for the discord rich presence implementation. + + + This native sets the hover text of the image asset for the discord rich presence implementation. + + + This native sets the hover text of the image asset for the discord rich presence implementation. @@ -6916,6 +7505,11 @@ Navigates the specified DUI browser to a different URL. + + + Navigates the specified DUI browser to a different URL. + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. @@ -6923,17 +7517,34 @@ + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. + Example: `SetHandlingField('AIRTUG', 'CHandlingData', 'fSteeringLock', 360.0)` + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. Example: `SetHandlingFloat('AIRTUG', 'CHandlingData', 'fSteeringLock', 360.0)` + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. + Example: `SetHandlingFloat('AIRTUG', 'CHandlingData', 'fSteeringLock', 360.0)` + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. + + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. + Example: `SetHandlingVector('AIRTUG', 'CHandlingData', 'vecCentreOfMassOffset', vector3(0.0, 0.0, -5.0))` + + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. Example: `SetHandlingVector('AIRTUG', 'CHandlingData', 'vecCentreOfMassOffset', vector3(0.0, 0.0, -5.0))` @@ -6945,48 +7556,104 @@ you will have to manually invoke `SHUTDOWN_LOADING_SCREEN_NUI` whenever you want to hide the NUI loading screen. + + + Sets whether or not `SHUTDOWN_LOADING_SCREEN` automatically shuts down the NUI frame for the loading screen. If this is enabled, + you will have to manually invoke `SHUTDOWN_LOADING_SCREEN_NUI` whenever you want to hide the NUI loading screen. + + Sets values to the zoom level data by index. + + + Sets values to the zoom level data by index. + + Overrides how many real ms are equal to one game minute. A setter for [`GetMillisecondsPerGameMinute`](#_0x2F8B4D1C595B11DB). - + - Reveals the entire minimap (FOW = Fog of War) - + Overrides how many real ms are equal to one game minute. + A setter for [`GetMillisecondsPerGameMinute`](#_0x2F8B4D1C595B11DB). + + + Sets the type for the minimap blip clipping object to be either rectangular or rounded. + + + + + Overrides the minimap component data (from `common:/data/ui/frontend.xml`) for a specified component. + + + + + Reveals the entire minimap (FOW = Fog of War) + Sets the display info for a minimap overlay. + + + Sets the display info for a minimap overlay. + + the status of default voip system. It affects on `NETWORK_IS_PLAYER_TALKING` and `mp_facial` animation. This function doesn't need to be called every frame, it works like a switcher. + + + the status of default voip system. It affects on `NETWORK_IS_PLAYER_TALKING` and `mp_facial` animation. + This function doesn't need to be called every frame, it works like a switcher. + + Sets the player's rich presence detail state for social platform providers to a specified string. + + + Sets the player's rich presence detail state for social platform providers to a specified string. + + Sets a pixel in the specified runtime texture. This will have to be committed using `COMMIT_RUNTIME_TEXTURE` to have any effect. + + + Sets a pixel in the specified runtime texture. This will have to be committed using `COMMIT_RUNTIME_TEXTURE` to have any effect. + + + + + Internal function for setting a state bag value. + + Disables the vehicle from being repaired when a vehicle extra is enabled. + + + Disables the vehicle from being repaired when a vehicle extra is enabled. + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_FIELD`, this might require some experimentation. @@ -6994,17 +7661,33 @@ + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_FIELD`, this might require some experimentation. + Example: `SetVehicleHandlingField(vehicle, 'CHandlingData', 'fSteeringLock', 360.0)` + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_FLOAT`, this might require some experimentation. Example: `SetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fSteeringLock', 360.0)` + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_FLOAT`, this might require some experimentation. + Example: `SetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fSteeringLock', 360.0)` + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_INT`, this might require some experimentation. + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_INT`, this might require some experimentation. + + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_VECTOR`, this might require some experimentation. + + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_VECTOR`, this might require some experimentation. @@ -7022,6 +7705,48 @@ ``` + + + Example script: <https://pastebin.com/J6XGbkCW> + + List of known states: + + ``` + 1: Not wheeling. + 65: Vehicle is ready to do wheelie (burnouting). + 129: Vehicle is doing wheelie. + ``` + + + + + Not sure what this changes, probably determines physical rim size in case the tire is blown. + + + + + Sets vehicle's wheels' size (size is the same for all the wheels, cannot get/set specific wheel of vehicle). + Only works on non-default wheels. + Returns whether change was successful (can be false if trying to set size for non-default wheels). + + + + + Use along with SetVehicleWheelSize to resize the wheels (this native sets the collider size affecting physics while SetVehicleWheelSize will change visual size). + + + + + Use along with SetVehicleWheelWidth to resize the wheels (this native sets the collider width affecting physics while SetVehicleWheelWidth will change visual width). + + + + + Sets vehicle's wheels' width (width is the same for all the wheels, cannot get/set specific wheel of vehicle). + Only works on non-default wheels. + Returns whether change was successful (can be false if trying to set width for non-default wheels). + + Adjusts the offset of the specified wheel relative to the wheel's axle center. @@ -7036,16 +7761,40 @@ ``` + + + Adjusts the offset of the specified wheel relative to the wheel's axle center. + Needs to be called every frame in order to function properly, as GTA will reset the offset otherwise. + This function can be especially useful to set the track width of a vehicle, for example: + + ``` + function SetVehicleFrontTrackWidth(vehicle, width) + SetVehicleWheelXOffset(vehicle, 0, -width/2) + SetVehicleWheelXOffset(vehicle, 1, width/2) + end + ``` + + Overrides a floating point value from `visualsettings.dat` temporarily. + + + Overrides a floating point value from `visualsettings.dat` temporarily. + + Shuts down the `loadingScreen` NUI frame, similarly to `SHUTDOWN_LOADING_SCREEN`. + + + Shuts down the `loadingScreen` NUI frame, similarly to `SHUTDOWN_LOADING_SCREEN`. + + statId: see 0xC48FE1971C9743FF @@ -7112,6 +7861,14 @@ + + The backing function for TriggerEvent. + + + The backing function for TriggerLatentServerEvent. + + + The backing function for TriggerLatentServerEvent. @@ -7121,11 +7878,21 @@ The backing function for TriggerServerEvent. + + + The backing function for TriggerServerEvent. + + Returns whether or not the currently executing event was canceled. + + + Returns whether or not the currently executing event was canceled. + + Returns a formatted string (0x%x) @@ -7255,10 +8022,26 @@ A minimap overlay ID. + + + Loads a minimap overlay from a GFx file in the current resource. + + + The path to a `.gfx` file in the current resource. It has to be specified as a `file`. + + + A minimap overlay ID. + + Experimental natives, please do not use in a live environment. + + + + Experimental natives, please do not use in a live environment. + @@ -7307,11 +8090,27 @@ A function in the overlay's TIMELINE. + + + This is similar to the PushScaleformMovieFunction natives, except it calls in the `TIMELINE` of a minimap overlay. + + + The minimap overlay ID. + + + A function in the overlay's TIMELINE. + + Cancels the currently executing event. + + + Cancels the currently executing event. + + Commits the backing pixels to the specified runtime texture. @@ -7320,6 +8119,14 @@ The runtime texture handle. + + + Commits the backing pixels to the specified runtime texture. + + + The runtime texture handle. + + Returns a formatted string (0x%x) @@ -7345,6 +8152,23 @@ A DUI object. + + + Creates a DUI browser. This can be used to draw on a runtime texture using CREATE_RUNTIME_TEXTURE_FROM_DUI_HANDLE. + + + The initial URL to load in the browser. + + + The width of the backing surface. + + + The height of the backing surface. + + + A DUI object. + + Creates a blank runtime texture. @@ -7365,7 +8189,44 @@ A runtime texture handle. - + + + Creates a blank runtime texture. + + + A handle to the runtime TXD to create the runtime texture in. + + + The name for the texture in the runtime texture dictionary. + + + The width of the new texture. + + + The height of the new texture. + + + A runtime texture handle. + + + + + Creates a runtime texture from a DUI handle. + + + A handle to the runtime TXD to create the runtime texture in. + + + The name for the texture in the runtime texture dictionary. + + + The DUI handle returned from GET_DUI_HANDLE. + + + The runtime texture handle. + + + Creates a runtime texture from a DUI handle. @@ -7399,6 +8260,23 @@ A runtime texture handle. + + + Creates a runtime texture from the specified file in the current resource. + + + A handle to the runtime TXD to create the runtime texture in. + + + The name for the texture in the runtime texture dictionary. + + + The file name of an image to load. This should preferably be a PNG, and has to be specified as a `file` in the resource manifest. + + + A runtime texture handle. + + Creates a runtime texture dictionary with the specified name. @@ -7414,6 +8292,21 @@ A handle to the runtime TXD. + + + Creates a runtime texture dictionary with the specified name. + Example: + ```lua + local txd = CreateRuntimeTxd('meow') + ``` + + + The name for the runtime TXD. + + + A handle to the runtime TXD. + + Destroys a DUI browser. @@ -7422,6 +8315,14 @@ The DUI browser handle. + + + Destroys a DUI browser. + + + The DUI browser handle. + + Note: you must use _CREATE_VAR_STRING @@ -7436,6 +8337,18 @@ : + + + Returns a list of door system entries: a door system hash (see [ADD_DOOR_TO_SYSTEM](#_0x6F8838D03D1DC226)) and its object handle. + The data returned adheres to the following layout: + ``` + [{doorHash1, doorHandle1}, ..., {doorHashN, doorHandleN}] + ``` + + + An object containing a list of door system entries. + + This native is not implemented. @@ -7444,6 +8357,11 @@ + + + This native is not implemented. + + This native is not implemented. @@ -7451,18 +8369,41 @@ + + + This native is not implemented. + + This native is not implemented. - - + This native is not implemented. + + + This native is not implemented. + + + + + This native is not implemented. + + + + + + Forces the game snow pass to render. + + + Whether or not to force rendering to use a snow pass. + + Returns all player indices for 'active' physical players known to the client. @@ -7475,6 +8416,34 @@ An object containing a list of player indices. + + + Returns all player indices for 'active' physical players known to the client. + The data returned adheres to the following layout: + ``` + [127, 42, 13, 37] + ``` + + + An object containing a list of player indices. + + + + + A getter for [SET_AMBIENT_PED_RANGE_MULTIPLIER_THIS_FRAME](#_0x0B919E1FB47CC4E0). + + + Returns ambient ped range multiplier value. + + + + + A getter for [SET_AMBIENT_VEHICLE_RANGE_MULTIPLIER_THIS_FRAME](#_0x90B6DA738A9A25DA). + + + Returns ambient vehicle range multiplier value. + + Gets the ped's core value on a scale of 0 to 100. The coreIndex is as follows: @@ -7493,6 +8462,11 @@ Returns the world matrix of the specified camera. To turn this into a view matrix, calculate the inverse. + + + + Returns the world matrix of the specified camera. To turn this into a view matrix, calculate the inverse. + @@ -7507,6 +8481,14 @@ The name of the resource. + + + Returns the name of the currently executing resource. + + + The name of the resource. + + Returns the peer address of the remote game server that the user is currently connected to. @@ -7515,6 +8497,14 @@ The peer address of the game server (e.g. `127.0.0.1:30120`), or NULL if not available. + + + Returns the peer address of the remote game server that the user is currently connected to. + + + The peer address of the game server (e.g. `127.0.0.1:30120`), or NULL if not available. + + Returns the NUI window handle for a specified DUI browser object. @@ -7526,11 +8516,38 @@ The NUI window handle, for use in e.g. CREATE_RUNTIME_TEXTURE_FROM_DUI_HANDLE. + + + Returns the NUI window handle for a specified DUI browser object. + + + The DUI browser handle. + + + The NUI window handle, for use in e.g. CREATE_RUNTIME_TEXTURE_FROM_DUI_HANDLE. + + Return example: 1207.69_dev_pc + + + Returns all pool handles for the given pool name; the data returned adheres to the following layout: + ``` + [ 770, 1026, 1282, 1538, 1794, 2050, 2306, 2562, 2818, 3074, 3330, 3586, 3842, 4098, 4354, 4610, ...] + ``` + ### Supported Pools + **1**: CPed + **2**: CObject + **3**: CVehicle + **4**: CPickup + + + An object containing a list of all pool handles + + Returns the label text given the hash. @@ -7565,6 +8582,32 @@ A boolean indicating TRUE if the data was received successfully. + + + Returns the zoom level data by index from mapzoomdata.meta file. + + + Zoom level index. + + + fZoomScale value. + + + fZoomSpeed value. + + + fScrollSpeed value. + + + vTiles X. + + + vTiles Y. + + + A boolean indicating TRUE if the data was received successfully. + + Gets the amount of metadata values with the specified key existing in the specified resource's manifest. @@ -7577,6 +8620,34 @@ The key to look up in the resource manifest. + + + Gets the amount of metadata values with the specified key existing in the specified resource's manifest. + See also: [Resource manifest](https://docs.fivem.net/resources/manifest/) + + + The resource name. + + + The key to look up in the resource manifest. + + + + + A getter for [SET_PARKED_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME](#_0xEAE6DCC7EEE3DB1D). + + + Returns parked vehicle density multiplier value. + + + + + A getter for [SET_PED_DENSITY_MULTIPLIER_THIS_FRAME](#_0x95E3D6257B166CF2). + + + Returns ped density multiplier value. + + A getter for [\_SET_PED_EYE_COLOR](#_0x50B56988B170AFDF). Returns -1 if fails to get. @@ -7588,6 +8659,17 @@ Returns ped's eye colour, or -1 if fails to get. + + + A getter for [\_SET_PED_EYE_COLOR](#_0x50B56988B170AFDF). Returns -1 if fails to get. + + + The target ped + + + Returns ped's eye colour, or -1 if fails to get. + + A getter for [\_SET_PED_FACE_FEATURE](#_0x71A5C1DBA060049E). Returns 0.0 if fails to get. @@ -7602,6 +8684,20 @@ Returns ped's face feature value, or 0.0 if fails to get. + + + A getter for [\_SET_PED_FACE_FEATURE](#_0x71A5C1DBA060049E). Returns 0.0 if fails to get. + + + The target ped + + + Face feature index + + + Returns ped's face feature value, or 0.0 if fails to get. + + A getter for [\_SET_PED_HAIR_COLOR](#_0x4CFFC65454C93A49). Returns -1 if fails to get. @@ -7613,6 +8709,17 @@ Returns ped's primary hair colour. + + + A getter for [\_SET_PED_HAIR_COLOR](#_0x4CFFC65454C93A49). Returns -1 if fails to get. + + + The target ped + + + Returns ped's primary hair colour. + + A getter for [\_SET_PED_HAIR_COLOR](#_0x4CFFC65454C93A49). Returns -1 if fails to get. @@ -7624,6 +8731,17 @@ Returns ped's secondary hair colour. + + + A getter for [\_SET_PED_HAIR_COLOR](#_0x4CFFC65454C93A49). Returns -1 if fails to get. + + + The target ped + + + Returns ped's secondary hair colour. + + A getter for [SET_PED_HEAD_OVERLAY](#_0x48F44967FA05CC1E) and [\_SET_PED_HEAD_OVERLAY_COLOR](#_0x497BF74A7B9CB952) natives. @@ -7653,6 +8771,118 @@ Returns ped's head overlay data. + + + A getter for [SET_PED_HEAD_OVERLAY](#_0x48F44967FA05CC1E) and [\_SET_PED_HEAD_OVERLAY_COLOR](#_0x497BF74A7B9CB952) natives. + + + The target ped + + + Overlay index + + + Overlay value pointer + + + Colour type pointer + + + First colour pointer + + + Second colour pointer + + + Opacity pointer + + + Returns ped's head overlay data. + + + + + A getter for [SET_PLAYER_MELEE_WEAPON_DAMAGE_MODIFIER](#_0x4A3DC7ECCC321032). + + + Returns player melee weapon damage modifier value. + + + + + A getter for [SET_PLAYER_MELEE_WEAPON_DEFENSE_MODIFIER](#_0xAE540335B4ABC4E2). + + + The player index. + + + The value of player melee weapon defense modifier. + + + + + A getter for [SET_PLAYER_VEHICLE_DAMAGE_MODIFIER](#_0xA50E117CDDF82F0C). + + + The player index. + + + The value of player vehicle damage modifier. + + + + + A getter for [SET_PLAYER_VEHICLE_DEFENSE_MODIFIER](#_0x4C60E6EFDAFF2462). + + + The player index. + + + The value of player vehicle defense modifier. + + + + + A getter for [SET_PLAYER_WEAPON_DAMAGE_MODIFIER](#_0xCE07B9F7817AADA3). + + + The player index. + + + The value of player weapon damage modifier. + + + + + A getter for [SET_PLAYER_WEAPON_DEFENSE_MODIFIER](#_0x2D83BC011CA14A3C). + + + The player index. + + + The value of player weapon defense modifier. + + + + + A getter for [\_SET_PLAYER_WEAPON_DEFENSE_MODIFIER_2](#_0xBCFDE9EDE4CF27DC). + + + The player index. + + + The value of player weapon defense modifier 2. + + + + + A getter for [SET_RANDOM_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME](#_0xB3B3359379FE77D3). + Same as vehicle density multiplier. + + + Returns random vehicle density multiplier value. + + Returns all commands that are registered in the command system. @@ -7672,6 +8902,25 @@ An object containing registered commands. + + + Returns all commands that are registered in the command system. + The data returned adheres to the following layout: + ``` + [ + { + "name": "cmdlist" + }, + { + "name": "command1" + } + ] + ``` + + + An object containing registered commands. + + Gets the metadata value at a specified key/index from a resource's manifest. @@ -7687,6 +8936,21 @@ The value index, in a range from [0..GET_NUM_RESOURCE_METDATA-1]. + + + Gets the metadata value at a specified key/index from a resource's manifest. + See also: [Resource manifest](https://docs.fivem.net/resources/manifest/) + + + The resource name. + + + The key in the resource manifest. + + + The value index, in a range from [0..GET_NUM_RESOURCE_METDATA-1]. + + Returns the current state of the specified resource. @@ -7698,6 +8962,17 @@ The resource state. One of `"missing", "started", "starting", "stopped", "stopping", "uninitialized" or "unknown"`. + + + Returns the current state of the specified resource. + + + The name of the resource. + + + The resource state. One of `"missing", "started", "starting", "stopped", "stopping", "uninitialized" or "unknown"`. + + Gets the height of the specified runtime texture. @@ -7709,6 +8984,17 @@ The height in pixels. + + + Gets the height of the specified runtime texture. + + + A handle to the runtime texture. + + + The height in pixels. + + Gets the row pitch of the specified runtime texture, for use when creating data for `SET_RUNTIME_TEXTURE_ARGB_DATA`. @@ -7720,6 +9006,17 @@ The row pitch in bytes. + + + Gets the row pitch of the specified runtime texture, for use when creating data for `SET_RUNTIME_TEXTURE_ARGB_DATA`. + + + A handle to the runtime texture. + + + The row pitch in bytes. + + Gets the width of the specified runtime texture. @@ -7731,6 +9028,25 @@ The width in pixels. + + + Gets the width of the specified runtime texture. + + + A handle to the runtime texture. + + + The width in pixels. + + + + + A getter for [SET_SCENARIO_PED_DENSITY_MULTIPLIER_THIS_FRAME](#_0x7A556143A1C03898). + + + Returns scenario ped density multiplier value. + + 0 = invalid @@ -7752,6 +9068,41 @@ : + + + Returns the value of a state bag key. + + + Value. + + + + + A getter for [SET_VEHICLE_CHEAT_POWER_INCREASE](#_0xB59E4BD37AE292DB). + + + The target vehicle. + + + Returns vehicle's cheat power increase modifier value. + + + + + A getter for [SET_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME](#_0x245A6883D966D537). + + + Returns vehicle density multiplier value. + + + + + Gets a vehicle's multiplier used with a wheel's GET_VEHICLE_WHEEL_STEERING_ANGLE to determine the angle the wheel is rendered. + + + : + + Returns the effective handling data of a vehicle as a floating-point value. @@ -7770,6 +9121,24 @@ A floating-point value. + + + Returns the effective handling data of a vehicle as a floating-point value. + Example: `local fSteeringLock = GetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fSteeringLock')` + + + The vehicle to obtain data for. + + + The handling class to get. Only "CHandlingData" is supported at this time. + + + The field name to get. These match the keys in `handling.meta`. + + + A floating-point value. + + Returns the effective handling data of a vehicle as an integer value. @@ -7788,6 +9157,24 @@ An integer. + + + Returns the effective handling data of a vehicle as an integer value. + Example: `local modelFlags = GetVehicleHandlingInt(vehicle, 'CHandlingData', 'strModelFlags')` + + + The vehicle to obtain data for. + + + The handling class to get. Only "CHandlingData" is supported at this time. + + + The field name to get. These match the keys in `handling.meta`. + + + An integer. + + Returns the effective handling data of a vehicle as a vector value. @@ -7806,6 +9193,24 @@ An integer. + + + Returns the effective handling data of a vehicle as a vector value. + Example: `local inertiaMultiplier = GetVehicleHandlingVector(vehicle, 'CHandlingData', 'vecInertiaMultiplier')` + + + The vehicle to obtain data for. + + + The handling class to get. Only "CHandlingData" is supported at this time. + + + The field name to get. These match the keys in `handling.meta`. + + + An integer. + + Gets the vehicle indicator light state. 0 = off, 1 = left, 2 = right, 3 = both @@ -7815,6 +9220,49 @@ An integer. + + + Gets the vehicle indicator light state. 0 = off, 1 = left, 2 = right, 3 = both + + + An integer. + + + + + A getter for [MODIFY_VEHICLE_TOP_SPEED](#_0x93A3996368C94158). Returns -1.0 if a modifier is not set. + + + The target vehicle. + + + Returns vehicle's modified top speed. + + + + + Gets brake pressure of a wheel. + Max number of wheels can be retrieved with the native GET_VEHICLE_NUMBER_OF_WHEELS. + + + : + + + : + + + + + Returns vehicle's wheels' size (size is the same for all the wheels, cannot get/set specific wheel of vehicle). + Only works on non-default wheels (returns 0 in case of default wheels). + + + The vehicle to obtain data for. + + + Float representing size of the wheel (usually between 0.5 and 1.5) + + Gets speed of a wheel at the tyre. @@ -7826,14 +9274,71 @@ An integer. + + + Gets speed of a wheel at the tyre. + Max number of wheels can be retrieved with the native GET_VEHICLE_NUMBER_OF_WHEELS. + + + An integer. + + + + + Gets steering angle of a wheel. + Max number of wheels can be retrieved with the native GET_VEHICLE_NUMBER_OF_WHEELS. + + + : + + + : + + + The steering angle of the wheel, with 0 being straight. + + + + + Returns vehicle's wheels' width (width is the same for all the wheels, cannot get/set specific wheel of vehicle). + Only works on non-default wheels (returns 0 in case of default wheels). + + + The vehicle to obtain data for. + + + Float representing width of the wheel (usually between 0.1 and 1.5) + + Returns the offset of the specified wheel relative to the wheel's axle center. + + + + Returns the offset of the specified wheel relative to the wheel's axle center. + - + + + List of known states: + ``` + 1: Not wheeling. + 65: Vehicle is ready to do wheelie (burnouting). + 129: Vehicle is doing wheelie. + ``` + + + Vehicle + + + Vehicle's current wheelie state. + + + List of known states: ``` @@ -7849,6 +9354,17 @@ Vehicle's current wheelie state. + + + A getter for [\_SET_WEAPON_DAMAGE_MODIFIER](#_0x4757F00BC6323CFE). + + + Weapon name hash. + + + A weapon damage modifier. + + Gets the model hash from the weapon hash. @@ -7913,6 +9429,17 @@ A boolean indicating load status. + + + Returns whether or not the specific minimap overlay has loaded. + + + A minimap overlay ID. + + + A boolean indicating load status. + + Note: the buffer should be exactly 32 bytes long @@ -7937,6 +9464,16 @@ A bool indicating if the minimap is currently expanded or normal state. + + + <!-- Native implemented by Disquse. 0xFFF65C63 --> + Returns true if the minimap is currently expanded. False if it's the normal minimap state. + Use [`IsBigmapFull`](#_0x66EE14B2) to check if the full map is currently revealed on the minimap. + + + A bool indicating if the minimap is currently expanded or normal state. + + <!-- Native implemented by Disquse. 0x66EE14B2 --> @@ -7947,6 +9484,16 @@ Returns true if the full map is currently revealed on the minimap. + + + <!-- Native implemented by Disquse. 0x66EE14B2 --> + Returns true if the full map is currently revealed on the minimap. + Use [`IsBigmapActive`](#_0xFFF65C63) to check if the minimap is currently expanded or in it's normal state. + + + Returns true if the full map is currently revealed on the minimap. + + Returns whether or not a browser is created for a specified DUI browser object. @@ -7958,6 +9505,17 @@ A boolean indicating TRUE if the browser is created. + + + Returns whether or not a browser is created for a specified DUI browser object. + + + The DUI browser handle. + + + A boolean indicating TRUE if the browser is created. + + Gets whether or not this is the CitizenFX server. @@ -7966,6 +9524,14 @@ A boolean value. + + + Gets whether or not this is the CitizenFX server. + + + A boolean value. + + Same as GET_IS_LOADING_SCREEN_ACTIVE @@ -7983,6 +9549,18 @@ Whether or not the streaming file has been registered. + + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + Returns whether an asynchronous streaming file registration completed. + + + The file name to check, for example `asset.ydr`. + + + Whether or not the streaming file has been registered. + + Reads the contents of a text file in a specified resource. @@ -7999,6 +9577,157 @@ The file contents + + + Reads the contents of a text file in a specified resource. + If executed on the client, this file has to be included in `files` in the resource manifest. + Example: `local data = LoadResourceFile("devtools", "data.json")` + + + The resource name. + + + The file in the resource. + + + The file contents + + + + + Starts listening to the specified channel, when available. + + + A game voice channel ID. + + + + + Adds the specified channel to the target list for the specified Mumble voice target ID. + + + A Mumble voice target ID, ranging from 1..30 (inclusive). + + + A game voice channel ID. + + + + + Adds the specified player to the target list for the specified Mumble voice target ID. + + + A Mumble voice target ID, ranging from 1..30 (inclusive). + + + A game player index. + + + + + Adds the specified player to the target list for the specified Mumble voice target ID. + + + A Mumble voice target ID, ranging from 1..30 (inclusive). + + + The player's server id. + + + + + Clears the target list for the specified Mumble voice target ID. + + + A Mumble voice target ID, ranging from 1..30 (inclusive). + + + + + Clears channels from the target list for the specified Mumble voice target ID. + + + A Mumble voice target ID, ranging from 1..30 (inclusive). + + + + + Clears players from the target list for the specified Mumble voice target ID. + + + A Mumble voice target ID, ranging from 1..30 (inclusive). + + + + + Returns the mumble voice channel from a player's server id. + + + The player's server id. + + + Int representing the identifier of the voice channel. + + + + + This native will return true if the user succesfully connected to the voice server. + If the user disabled the voice-chat setting it will return false. + + + True if the player is connected to a mumble server. + + + + + Stops listening to the specified channel. + + + A game voice channel ID. + + + + + Changes the Mumble server address to connect to, and reconnects to the new address. + + + The address of the mumble server. + + + The port of the mumble server. + + + + + Sets the current Mumble voice target ID to broadcast voice to. + + + A Mumble voice target ID, ranging from 1..30 (inclusive). 0 disables voice targets, and 31 is server loopback. + + + + + Overrides the output volume for a particular player on Mumble. This will also bypass 3D audio and distance calculations. -1.0 to reset the override. + Set to -1.0 to reset the Volume override. + + + A game player index. + + + The volume, ranging from 0.0 to 1.0 (or above). + + + + + Overrides the output volume for a particular player with the specified server id and player name on Mumble. This will also bypass 3D audio and distance calculations. -1.0 to reset the override. + + + The player's server id. + + + The volume, ranging from 0.0 to 1.0 (or above). + + Returns true if GtaThread+0x77C is equal to 1. @@ -8140,6 +9869,17 @@ On the server, the server ID of the entity owner. On the client, returns the player/slot ID of the entity owner. + + + Returns the owner ID of the specified entity. + + + The entity to get the owner for. + + + On the server, the server ID of the entity owner. On the client, returns the player/slot ID of the entity owner. + + Scope entry for profiler. @@ -8148,11 +9888,24 @@ Scope name. + + + Scope entry for profiler. + + + Scope name. + + Scope exit for profiler. + + + Scope exit for profiler. + + Returns true if the profiler is active. @@ -8161,6 +9914,14 @@ True or false. + + + Returns true if the profiler is active. + + + True or false. + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. @@ -8170,6 +9931,15 @@ A function returning a list of archetypes. + + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + Registers a set of archetypes with the game engine. These should match `CBaseArchetypeDef` class information from the game. + + + A function returning a list of archetypes. + + Registered commands can be executed by entering them in the client console (this works for client side and server side registered commands). Or by entering them in the server console/through an RCON client (only works for server side registered commands). Or if you use a supported chat resource, like the default one provided in the cfx-server-data repository, then you can enter the command in chat by prefixing it with a `/`. @@ -8188,6 +9958,24 @@ If this is a server command and you set this to true, then players will need the command.yourCommandName ace permission to execute this command. + + + Registered commands can be executed by entering them in the client console (this works for client side and server side registered commands). Or by entering them in the server console/through an RCON client (only works for server side registered commands). Or if you use a supported chat resource, like the default one provided in the cfx-server-data repository, then you can enter the command in chat by prefixing it with a `/`. + Commands registered using this function can also be executed by resources, using the [`ExecuteCommand` native](#_0x561C060B). + The restricted bool is not used on the client side. Permissions can only be checked on the server side, so if you want to limit your command with an ace permission automatically, make it a server command (by registering it in a server script). + **Example result**: + ![](https://i.imgur.com/TaCnG09.png) + + + The command you want to register. + + + A handler function that gets called whenever the command is executed. + + + If this is a server command and you set this to true, then players will need the command.yourCommandName ace permission to execute this command. + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. @@ -8198,6 +9986,16 @@ A function returning a list of entities. + + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + Registers a set of entities with the game engine. These should match `CEntityDef` class information from the game. + At this time, this function **should not be used in a live environment**. + + + A function returning a list of entities. + + Registers a specified .gfx file as GFx font library. @@ -8207,6 +10005,15 @@ The name of the .gfx file, without extension. + + + Registers a specified .gfx file as GFx font library. + The .gfx file has to be registered with the streamer already. + + + The name of the .gfx file, without extension. + + Registers a specified font name for use with text draw commands. @@ -8218,6 +10025,35 @@ An index to use with [SET_TEXT_FONT](#_0x66E0276CC5F6B9DA) and similar natives. + + + Registers a specified font name for use with text draw commands. + + + The name of the font in the GFx font library. + + + An index to use with [SET_TEXT_FONT](#_0x66E0276CC5F6B9DA) and similar natives. + + + + + Registers a key mapping for the current resource. + See the related [cookbook post](https://cookbook.fivem.net/2020/01/06/using-the-new-console-key-bindings/) for more information. + + + The command to execute, and the identifier of the binding. + + + A description for in the settings menu. + + + The mapper ID to use for the default binding, e.g. `keyboard`. + + + The IO parameter ID to use for the default binding, e.g. `f3`. + + An internal function which allows the current resource's HLL script runtimes to receive state for the specified event. @@ -8226,6 +10062,14 @@ An event name, or "\*" to disable HLL event filtering for this resource. + + + An internal function which allows the current resource's HLL script runtimes to receive state for the specified event. + + + An event name, or "\*" to disable HLL event filtering for this resource. + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. @@ -8241,6 +10085,21 @@ The string returned from `REGISTER_RESOURCE_ASSET` on the server. + + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + Registers a dynamic streaming asset from the server with the GTA streaming module system. + + + The resource to add the asset to. + + + A file name in the resource. + + + The string returned from `REGISTER_RESOURCE_ASSET` on the server. + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. @@ -8250,6 +10109,15 @@ The KVP key in the current resource to register as an asset. + + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + Registers a KVP value as an asset with the GTA streaming module system. This function currently won't work. + + + The KVP key in the current resource to register as an asset. + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. @@ -8263,6 +10131,19 @@ The URL to fetch the asset from. + + + **Experimental**: This native may be altered or removed in future versions of CitizenFX without warning. + Registers a file from an URL as a streaming asset in the GTA streaming subsystem. This will asynchronously register the asset, and caching is done based on the URL itself - cache headers are ignored. + Use `IS_STREAMING_FILE_READY` to check if the asset has been registered successfully. + + + The file name to register as, for example `asset.ydr`. + + + The URL to fetch the asset from. + + Experimental natives, please do not use in a live environment. @@ -8270,6 +10151,11 @@ + + + Experimental natives, please do not use in a live environment. + + Resets values from the zoom level data by index to defaults from mapzoomdata.meta. @@ -8278,6 +10164,14 @@ Zoom level index. + + + Resets values from the zoom level data by index to defaults from mapzoomdata.meta. + + + Zoom level index. + + Sends a message to the specific DUI root page. This is similar to SEND_NUI_MESSAGE. @@ -8289,6 +10183,17 @@ The message, encoded as JSON. + + + Sends a message to the specific DUI root page. This is similar to SEND_NUI_MESSAGE. + + + The DUI browser handle. + + + The message, encoded as JSON. + + Injects a 'mouse down' event for a DUI object. Coordinates are expected to be set using SEND_DUI_MOUSE_MOVE. @@ -8300,6 +10205,17 @@ Either `'left'`, `'middle'` or `'right'`. + + + Injects a 'mouse down' event for a DUI object. Coordinates are expected to be set using SEND_DUI_MOUSE_MOVE. + + + The DUI browser handle. + + + Either `'left'`, `'middle'` or `'right'`. + + Injects a 'mouse move' event for a DUI object. Coordinates are in browser space. @@ -8314,6 +10230,20 @@ The mouse Y position. + + + Injects a 'mouse move' event for a DUI object. Coordinates are in browser space. + + + The DUI browser handle. + + + The mouse X position. + + + The mouse Y position. + + Injects a 'mouse up' event for a DUI object. Coordinates are expected to be set using SEND_DUI_MOUSE_MOVE. @@ -8325,6 +10255,17 @@ Either `'left'`, `'middle'` or `'right'`. + + + Injects a 'mouse up' event for a DUI object. Coordinates are expected to be set using SEND_DUI_MOUSE_MOVE. + + + The DUI browser handle. + + + Either `'left'`, `'middle'` or `'right'`. + + Injects a 'mouse wheel' event for a DUI object. @@ -8339,6 +10280,20 @@ The wheel X delta. + + + Injects a 'mouse wheel' event for a DUI object. + + + The DUI browser handle. + + + The wheel Y delta. + + + The wheel X delta. + + Sends a message to the `loadingScreen` NUI frame, which contains the HTML page referenced in `loadscreen` resources. @@ -8350,11 +10305,27 @@ A success value. + + + Sends a message to the `loadingScreen` NUI frame, which contains the HTML page referenced in `loadscreen` resources. + + + The JSON-encoded message. + + + A success value. + + This native sets the app id for the discord rich presence implementation. + + + This native sets the app id for the discord rich presence implementation. + + This native sets the image asset for the discord rich presence implementation. @@ -8363,6 +10334,14 @@ The name of a valid asset registered on Discordapp's developer dashboard. note that the asset has to be registered under the same discord API application set using the SET_DISCORD_APP_ID native. + + + This native sets the image asset for the discord rich presence implementation. + + + The name of a valid asset registered on Discordapp's developer dashboard. note that the asset has to be registered under the same discord API application set using the SET_DISCORD_APP_ID native. + + This native sets the small image asset for the discord rich presence implementation. @@ -8371,6 +10350,14 @@ The name of a valid asset registered on Discordapp's developer dashboard. Note that the asset has to be registered under the same discord API application set using the SET_DISCORD_APP_ID native. + + + This native sets the small image asset for the discord rich presence implementation. + + + The name of a valid asset registered on Discordapp's developer dashboard. Note that the asset has to be registered under the same discord API application set using the SET_DISCORD_APP_ID native. + + This native sets the hover text of the small image asset for the discord rich presence implementation. @@ -8379,6 +10366,14 @@ Text to be displayed when hovering over small image asset. Note that you must also set a valid small image asset using the SET_DISCORD_RICH_PRESENCE_ASSET_SMALL native. + + + This native sets the hover text of the small image asset for the discord rich presence implementation. + + + Text to be displayed when hovering over small image asset. Note that you must also set a valid small image asset using the SET_DISCORD_RICH_PRESENCE_ASSET_SMALL native. + + This native sets the hover text of the image asset for the discord rich presence implementation. @@ -8387,6 +10382,14 @@ Text to be displayed when hovering over image asset. Note that you must also set a valid image asset using the SET_DISCORD_RICH_PRESENCE_ASSET native. + + + This native sets the hover text of the image asset for the discord rich presence implementation. + + + Text to be displayed when hovering over image asset. Note that you must also set a valid image asset using the SET_DISCORD_RICH_PRESENCE_ASSET native. + + Navigates the specified DUI browser to a different URL. @@ -8398,6 +10401,17 @@ The new URL. + + + Navigates the specified DUI browser to a different URL. + + + The DUI browser handle. + + + The new URL. + + Sets the entity's health. healthAmount sets the health value to that, and sets the maximum health core value. Setting healthAmount to 0 will kill the entity. Unclear what role p2 serves, but it's either 0 or an entity handle. @@ -8430,6 +10444,24 @@ The value to set. + + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. + Example: `SetHandlingField('AIRTUG', 'CHandlingData', 'fSteeringLock', 360.0)` + + + The vehicle class to set data for. + + + The handling class to set. Only "CHandlingData" is supported at this time. + + + The field name to set. These match the keys in `handling.meta`. + + + The value to set. + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. @@ -8448,6 +10480,24 @@ The floating-point value to set. + + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. + Example: `SetHandlingFloat('AIRTUG', 'CHandlingData', 'fSteeringLock', 360.0)` + + + The vehicle class to set data for. + + + The handling class to set. Only "CHandlingData" is supported at this time. + + + The field name to set. These match the keys in `handling.meta`. + + + The floating-point value to set. + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. @@ -8465,6 +10515,23 @@ The integer value to set. + + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. + + + The vehicle class to set data for. + + + The handling class to set. Only "CHandlingData" is supported at this time. + + + The field name to set. These match the keys in `handling.meta`. + + + The integer value to set. + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. @@ -8483,6 +10550,24 @@ The Vector3 value to set. + + + Sets a global handling override for a specific vehicle class. The name is supposed to match the `handlingName` field from handling.meta. + Example: `SetHandlingVector('AIRTUG', 'CHandlingData', 'vecCentreOfMassOffset', vector3(0.0, 0.0, -5.0))` + + + The vehicle class to set data for. + + + The handling class to set. Only "CHandlingData" is supported at this time. + + + The field name to set. These match the keys in `handling.meta`. + + + The Vector3 value to set. + + Sets whether or not `SHUTDOWN_LOADING_SCREEN` automatically shuts down the NUI frame for the loading screen. If this is enabled, @@ -8492,6 +10577,15 @@ TRUE to manually shut down the loading screen NUI. + + + Sets whether or not `SHUTDOWN_LOADING_SCREEN` automatically shuts down the NUI frame for the loading screen. If this is enabled, + you will have to manually invoke `SHUTDOWN_LOADING_SCREEN_NUI` whenever you want to hide the NUI loading screen. + + + TRUE to manually shut down the loading screen NUI. + + Sets values to the zoom level data by index. @@ -8515,7 +10609,39 @@ vTiles Y. - + + + Sets values to the zoom level data by index. + + + Zoom level index. + + + fZoomScale value. + + + fZoomSpeed value. + + + fScrollSpeed value. + + + vTiles X. + + + vTiles Y. + + + + + Overrides how many real ms are equal to one game minute. + A setter for [`GetMillisecondsPerGameMinute`](#_0x2F8B4D1C595B11DB). + + + Milliseconds. + + + Overrides how many real ms are equal to one game minute. A setter for [`GetMillisecondsPerGameMinute`](#_0x2F8B4D1C595B11DB). @@ -8524,6 +10650,40 @@ Milliseconds. + + + Sets the type for the minimap blip clipping object to be either rectangular or rounded. + + + 0 for rectangular, 1 for rounded. + + + + + Overrides the minimap component data (from `common:/data/ui/frontend.xml`) for a specified component. + + + The name of the minimap component to override. + + + Equivalent to the `alignX` field in `frontend.xml`. + + + Equivalent to the `alignY` field in `frontend.xml`. + + + Equivalent to the `posX` field in `frontend.xml`. + + + Equivalent to the `posY` field in `frontend.xml`. + + + Equivalent to the `sizeX` field in `frontend.xml`. + + + Equivalent to the `sizeY` field in `frontend.xml`. + + Reveals the entire minimap (FOW = Fog of War) @@ -8555,6 +10715,29 @@ The alpha value for the overlay. This is equivalent to the Flash \_alpha property, therefore 100 = 100%. + + + Sets the display info for a minimap overlay. + + + The minimap overlay ID. + + + The X position for the overlay. This is equivalent to a game coordinate X. + + + The Y position for the overlay. This is equivalent to a game coordinate Y, except that it's inverted (gfxY = -gameY). + + + The X scale for the overlay. This is equivalent to the Flash \_xscale property, therefore 100 = 100%. + + + The Y scale for the overlay. This is equivalent to the Flash \_yscale property. + + + The alpha value for the overlay. This is equivalent to the Flash \_alpha property, therefore 100 = 100%. + + Sets the outfit preset for the ped. The presetId is an index which determines it's preset outfit. p2 is always 0. @@ -8581,6 +10764,18 @@ Overriding state. + + + the status of default voip system. It affects on `NETWORK_IS_PLAYER_TALKING` and `mp_facial` animation. + This function doesn't need to be called every frame, it works like a switcher. + + + The target player. + + + Overriding state. + + Sets the player's rich presence detail state for social platform providers to a specified string. @@ -8589,6 +10784,14 @@ The rich presence string to set. + + + Sets the player's rich presence detail state for social platform providers to a specified string. + + + The rich presence string to set. + + Sets a pixel in the specified runtime texture. This will have to be committed using `COMMIT_RUNTIME_TEXTURE` to have any effect. @@ -8615,6 +10818,40 @@ The new A value (0-255). + + + Sets a pixel in the specified runtime texture. This will have to be committed using `COMMIT_RUNTIME_TEXTURE` to have any effect. + + + A handle to the runtime texture. + + + The X position of the pixel to change. + + + The Y position of the pixel to change. + + + The new R value (0-255). + + + The new G value (0-255). + + + The new B value (0-255). + + + The new A value (0-255). + + + + + Internal function for setting a state bag value. + + + : + + 0 = high @@ -8636,6 +10873,17 @@ Setting the value to true prevents the vehicle from being repaired when a extra is enabled. Setting the value to false allows the vehicle from being repaired when a extra is enabled. + + + Disables the vehicle from being repaired when a vehicle extra is enabled. + + + The vehicle to set disable auto vehicle repair. + + + Setting the value to true prevents the vehicle from being repaired when a extra is enabled. Setting the value to false allows the vehicle from being repaired when a extra is enabled. + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_FIELD`, this might require some experimentation. @@ -8654,6 +10902,24 @@ The value to set. + + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_FIELD`, this might require some experimentation. + Example: `SetVehicleHandlingField(vehicle, 'CHandlingData', 'fSteeringLock', 360.0)` + + + The vehicle to set data for. + + + The handling class to set. Only "CHandlingData" is supported at this time. + + + The field name to set. These match the keys in `handling.meta`. + + + The value to set. + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_FLOAT`, this might require some experimentation. @@ -8672,6 +10938,24 @@ The floating-point value to set. + + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_FLOAT`, this might require some experimentation. + Example: `SetVehicleHandlingFloat(vehicle, 'CHandlingData', 'fSteeringLock', 360.0)` + + + The vehicle to set data for. + + + The handling class to set. Only "CHandlingData" is supported at this time. + + + The field name to set. These match the keys in `handling.meta`. + + + The floating-point value to set. + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_INT`, this might require some experimentation. @@ -8689,6 +10973,23 @@ The integer value to set. + + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_INT`, this might require some experimentation. + + + The vehicle to set data for. + + + The handling class to set. Only "CHandlingData" is supported at this time. + + + The field name to set. These match the keys in `handling.meta`. + + + The integer value to set. + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_VECTOR`, this might require some experimentation. @@ -8706,6 +11007,97 @@ The Vector3 value to set. + + + Sets a handling override for a specific vehicle. Certain handling flags can only be set globally using `SET_HANDLING_VECTOR`, this might require some experimentation. + + + The vehicle to set data for. + + + The handling class to set. Only "CHandlingData" is supported at this time. + + + The field name to set. These match the keys in `handling.meta`. + + + The Vector3 value to set. + + + + + Not sure what this changes, probably determines physical rim size in case the tire is blown. + + + The vehicle to obtain data for. + + + Index of wheel, 0-3. + + + Size of rim collider. + + + + + Sets vehicle's wheels' size (size is the same for all the wheels, cannot get/set specific wheel of vehicle). + Only works on non-default wheels. + Returns whether change was successful (can be false if trying to set size for non-default wheels). + + + The vehicle to set data for. + + + Size of the wheels (usually between 0.5 and 1.5 is reasonable). + + + Bool - whether change was successful or not + + + + + Use along with SetVehicleWheelSize to resize the wheels (this native sets the collider size affecting physics while SetVehicleWheelSize will change visual size). + + + The vehicle to obtain data for. + + + Index of wheel, 0-3. + + + Radius of tire collider. + + + + + Use along with SetVehicleWheelWidth to resize the wheels (this native sets the collider width affecting physics while SetVehicleWheelWidth will change visual width). + + + The vehicle to obtain data for. + + + Index of wheel, 0-3. + + + Width of tire collider. + + + + + Sets vehicle's wheels' width (width is the same for all the wheels, cannot get/set specific wheel of vehicle). + Only works on non-default wheels. + Returns whether change was successful (can be false if trying to set width for non-default wheels). + + + The vehicle to set data for. + + + Width of the wheels (usually between 0.1 and 1.5 is reasonable). + + + Bool - whether change was successful or not + + Adjusts the offset of the specified wheel relative to the wheel's axle center. @@ -8718,6 +11110,19 @@ end ``` + + + + Adjusts the offset of the specified wheel relative to the wheel's axle center. + Needs to be called every frame in order to function properly, as GTA will reset the offset otherwise. + This function can be especially useful to set the track width of a vehicle, for example: + ``` + function SetVehicleFrontTrackWidth(vehicle, width) + SetVehicleWheelXOffset(vehicle, 0, -width/2) + SetVehicleWheelXOffset(vehicle, 1, width/2) + end + ``` + @@ -8739,6 +11144,23 @@ Wheelie state + + + Example script: <https://pastebin.com/J6XGbkCW> + List of known states: + ``` + 1: Not wheeling. + 65: Vehicle is ready to do wheelie (burnouting). + 129: Vehicle is doing wheelie. + ``` + + + Vehicle + + + Wheelie state + + Overrides a floating point value from `visualsettings.dat` temporarily. @@ -8750,6 +11172,17 @@ The value to write. + + + Overrides a floating point value from `visualsettings.dat` temporarily. + + + The name of the value to set, such as `pedLight.color.red`. + + + The value to write. + + Same as SHOULD_USE_METRIC_MEASUREMENTS @@ -8760,6 +11193,11 @@ Shuts down the `loadingScreen` NUI frame, similarly to `SHUTDOWN_LOADING_SCREEN`. + + + Shuts down the `loadingScreen` NUI frame, similarly to `SHUTDOWN_LOADING_SCREEN`. + + statId: see 0xC48FE1971C9743FF @@ -8965,10 +11403,20 @@ + + + The backing function for TriggerEvent. + + The backing function for TriggerLatentServerEvent. + + + + The backing function for TriggerLatentServerEvent. + @@ -8982,6 +11430,11 @@ + + + The backing function for TriggerServerEvent. + + Returns whether or not the currently executing event was canceled. @@ -8990,6 +11443,14 @@ A boolean. + + + Returns whether or not the currently executing event was canceled. + + + A boolean. + + Allow copying memory from one IntPtr to another. Required as the implementation does not provide an appropriate override.