diff --git a/shared/warmenu.lua b/shared/warmenu.lua new file mode 100644 index 0000000..901ec9a --- /dev/null +++ b/shared/warmenu.lua @@ -0,0 +1,694 @@ +WarMenu = { } +WarMenu.__index = WarMenu + +if not isServer() then + +-- Deprecated +WarMenu.debug = false +function WarMenu.SetDebugEnabled(enabled) +end +function WarMenu.IsDebugEnabled() + return false +end +--- + +local menus = { } +local keys = { down = 187, scrollDown = 242, up = 188, scrollUp = 241, left = 189, right = 190, select = 191, accept = 237, back = 194, cancel = 238 } + +local skipInputNextFrame = true +local optionCount = 0 + +local currentKey = nil +local currentMenu = nil + +local toolTipWidth = 0.153 + +local spriteWidth = 0.027 +local spriteHeight = spriteWidth * GetAspectRatio() + +local titleHeight = 0.101 +local titleYOffset = 0.021 +local titleFont = 1 +local titleScale = 1.0 + +local buttonHeight = 0.038 +local buttonFont = 0 +local buttonScale = 0.365 +local buttonTextXOffset = 0.005 +local buttonTextYOffset = 0.005 +local buttonSpriteXOffset = 0.002 +local buttonSpriteYOffset = 0.005 + +local defaultStyle = { + x = 0.0175, + y = 0.025, + width = 0.23, + maxOptionCountOnScreen = 10, + titleVisible = true, + titleColor = { 0, 0, 0, 255 }, + titleBackgroundColor = { 245, 127, 23, 255 }, + titleBackgroundSprite = nil, + subTitleColor = { 245, 127, 23, 255 }, + textColor = { 254, 254, 254, 255 }, + subTextColor = { 189, 189, 189, 255 }, + focusTextColor = { 0, 0, 0, 255 }, + focusColor = { 245, 245, 245, 255 }, + backgroundColor = { 0, 0, 0, 160 }, + subTitleBackgroundColor = { 0, 0, 0, 255 }, + buttonPressedSound = { name = 'SELECT', set = 'HUD_FRONTEND_DEFAULT_SOUNDSET' }, --https://pastebin.com/0neZdsZ5 +} + +local function IsNavigatedDown() + return IsControlJustReleased(2, keys.down) or IsControlJustReleased(2, keys.scrollDown) +end + +local function IsNavigatedUp() + return IsControlJustReleased(2, keys.up) or IsControlJustReleased(2, keys.scrollUp) +end + +local function IsSelectedPressed() + return IsControlJustReleased(2, keys.select) or IsControlJustReleased(2, keys.accept) +end + +local function IsBackPressed() + return IsControlJustReleased(2, keys.back) or IsControlJustReleased(2, keys.cancel) +end + +local function setMenuProperty(id, property, value) + if not id then + return + end + + local menu = menus[id] + if menu then + menu[property] = value + end +end + +local function setStyleProperty(id, property, value) + if not id then + return + end + + local menu = menus[id] + + if menu then + if not menu.overrideStyle then + menu.overrideStyle = { } + end + + menu.overrideStyle[property] = value + end +end + +local function getStyleProperty(property, menu) + menu = menu or currentMenu + + if menu.overrideStyle then + local value = menu.overrideStyle[property] + if value ~= nil then + return value + end + end + + return menu.style and menu.style[property] or defaultStyle[property] +end + +local function getTitleHeight() + return getStyleProperty('titleVisible') and titleHeight or 0 +end + +local function copyTable(t) + if type(t) ~= 'table' then + return t + end + + local result = { } + for k, v in pairs(t) do + result[k] = copyTable(v) + end + + return result +end + +local function setMenuVisible(id, visible, holdCurrentOption) + if currentMenu then + if visible then + if currentMenu.id == id then + return + end + else + if currentMenu.id ~= id then + return + end + end + end + + if visible then + local menu = menus[id] + + if not currentMenu then + menu.currentOption = 1 + else + if not holdCurrentOption then + menus[currentMenu.id].currentOption = 1 + end + end + + currentMenu = menu + skipInputNextFrame = true + + SetUserRadioControlEnabled(false) + HudWeaponWheelIgnoreControlInput(true) + else + HudWeaponWheelIgnoreControlInput(false) + SetUserRadioControlEnabled(true) + + currentMenu = nil + end +end + +local function setTextParams(font, color, scale, center, shadow, alignRight, wrapFrom, wrapTo) + SetTextFont(font) + SetTextColour(color[1], color[2], color[3], color[4] or 255) + SetTextScale(scale, scale) + + if shadow then + SetTextDropShadow() + end + + if center then + SetTextCentre(true) + elseif alignRight then + SetTextRightJustify(true) + end + + if not wrapFrom or not wrapTo then + wrapFrom = wrapFrom or getStyleProperty('x') + wrapTo = wrapTo or getStyleProperty('x') + getStyleProperty('width') - buttonTextXOffset + end + + SetTextWrap(wrapFrom, wrapTo) +end + +local function getLinesCount(text, x, y) + BeginTextCommandLineCount('TWOSTRINGS') + AddTextComponentString(tostring(text)) + return EndTextCommandGetLineCount(x, y) +end + +local function drawText(text, x, y) + BeginTextCommandDisplayText('TWOSTRINGS') + AddTextComponentString(tostring(text)) + EndTextCommandDisplayText(x, y) +end + +local function drawRect(x, y, width, height, color) + DrawRect(x, y, width, height, color[1], color[2], color[3], color[4] or 255) +end + +local function getCurrentIndex() + if currentMenu.currentOption <= getStyleProperty('maxOptionCountOnScreen') and optionCount <= getStyleProperty('maxOptionCountOnScreen') then + return optionCount + elseif optionCount > currentMenu.currentOption - getStyleProperty('maxOptionCountOnScreen') and optionCount <= currentMenu.currentOption then + return optionCount - (currentMenu.currentOption - getStyleProperty('maxOptionCountOnScreen')) + end + + return nil +end + +local function drawTitle() + if not getStyleProperty('titleVisible') then + return + end + + local x = getStyleProperty('x') + getStyleProperty('width') / 2 + local y = getStyleProperty('y') + titleHeight / 2 + + if getStyleProperty('titleBackgroundSprite') then + DrawSprite(getStyleProperty('titleBackgroundSprite').dict, getStyleProperty('titleBackgroundSprite').name, x, y, getStyleProperty('width'), titleHeight, 0., 255, 255, 255, 255) + else + drawRect(x, y, getStyleProperty('width'), titleHeight, getStyleProperty('titleBackgroundColor')) + end + + if currentMenu.title then + setTextParams(titleFont, getStyleProperty('titleColor'), titleScale, true) + drawText(currentMenu.title, x, y - titleHeight / 2 + titleYOffset) + end +end + +local function drawSubTitle() + local x = getStyleProperty('x') + getStyleProperty('width') / 2 + local y = getStyleProperty('y') + getTitleHeight() + buttonHeight / 2 + + drawRect(x, y, getStyleProperty('width'), buttonHeight, getStyleProperty('subTitleBackgroundColor')) + + setTextParams(buttonFont, getStyleProperty('subTitleColor'), buttonScale, false) + drawText(currentMenu.subTitle, getStyleProperty('x') + buttonTextXOffset, y - buttonHeight / 2 + buttonTextYOffset) + + if optionCount > getStyleProperty('maxOptionCountOnScreen') then + setTextParams(buttonFont, getStyleProperty('subTitleColor'), buttonScale, false, false, true) + drawText(tostring(currentMenu.currentOption)..' / '..tostring(optionCount), getStyleProperty('x') + getStyleProperty('width'), y - buttonHeight / 2 + buttonTextYOffset) + end +end + +local function drawButton(text, subText) + local currentIndex = getCurrentIndex() + if not currentIndex then + return + end + + local backgroundColor = nil + local textColor = nil + local subTextColor = nil + local shadow = false + + if currentMenu.currentOption == optionCount then + backgroundColor = getStyleProperty('focusColor') + textColor = getStyleProperty('focusTextColor') + subTextColor = getStyleProperty('focusTextColor') + else + backgroundColor = getStyleProperty('backgroundColor') + textColor = getStyleProperty('textColor') + subTextColor = getStyleProperty('subTextColor') + shadow = true + end + + local x = getStyleProperty('x') + getStyleProperty('width') / 2 + local y = getStyleProperty('y') + getTitleHeight() + buttonHeight + (buttonHeight * currentIndex) - buttonHeight / 2 + + drawRect(x, y, getStyleProperty('width'), buttonHeight, backgroundColor) + + setTextParams(buttonFont, textColor, buttonScale, false, shadow) + drawText(text, getStyleProperty('x') + buttonTextXOffset, y - (buttonHeight / 2) + buttonTextYOffset) + + if subText then + setTextParams(buttonFont, subTextColor, buttonScale, false, shadow, true) + drawText(subText, getStyleProperty('x') + buttonTextXOffset, y - buttonHeight / 2 + buttonTextYOffset) + end +end + +function WarMenu.CreateMenu(id, title, subTitle, style) + -- Default settings + local menu = { } + + -- Members + menu.id = id + menu.previousMenu = nil + menu.currentOption = 1 + menu.title = title + menu.subTitle = subTitle and string.upper(subTitle) or 'INTERACTION MENU' + + -- Style + if style then + menu.style = style + end + + menus[id] = menu +end + +function WarMenu.CreateSubMenu(id, parent, subTitle, style) + local parentMenu = menus[parent] + if not parentMenu then + return + end + + WarMenu.CreateMenu(id, parentMenu.title, subTitle and string.upper(subTitle) or parentMenu.subTitle) + + local menu = menus[id] + + menu.previousMenu = parent + + if parentMenu.overrideStyle then + menu.overrideStyle = copyTable(parentMenu.overrideStyle) + end + + if style then + menu.style = style + elseif parentMenu.style then + menu.style = copyTable(parentMenu.style) + end +end + +function WarMenu.CurrentMenu() + return currentMenu and currentMenu.id or nil +end + +function WarMenu.OpenMenu(id) + if id and menus[id] then + PlaySoundFrontend(-1, 'SELECT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) + setMenuVisible(id, true, true) + end +end + +function WarMenu.IsMenuOpened(id) + return currentMenu and currentMenu.id == id +end +WarMenu.Begin = WarMenu.IsMenuOpened + +function WarMenu.IsAnyMenuOpened() + return currentMenu ~= nil +end + +function WarMenu.IsMenuAboutToBeClosed() + return false +end + +function WarMenu.CloseMenu() + if currentMenu then + setMenuVisible(currentMenu.id, false) + optionCount = 0 + currentKey = nil + PlaySoundFrontend(-1, 'QUIT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) + end +end + +function WarMenu.ToolTip(text, width, flipHorizontal) + if not currentMenu then + return + end + + local currentIndex = getCurrentIndex() + if not currentIndex then + return + end + + width = width or toolTipWidth + + local x = nil + if not flipHorizontal then + x = getStyleProperty('x') + getStyleProperty('width') + width / 2 + buttonTextXOffset + else + x = getStyleProperty('x') - width / 2 - buttonTextXOffset + end + + local textX = x - (width / 2) + buttonTextXOffset + setTextParams(buttonFont, getStyleProperty('textColor'), buttonScale, false, true, false, textX, textX + width - (buttonTextYOffset * 2)) + local linesCount = getLinesCount(text, textX, getStyleProperty('y')) + + local height = GetTextScaleHeight(buttonScale, buttonFont) * (linesCount + 1) + buttonTextYOffset + local y = getStyleProperty('y') + getTitleHeight() + (buttonHeight * currentIndex) + height / 2 + + drawRect(x, y, width, height, getStyleProperty('backgroundColor')) + + y = y - (height / 2) + buttonTextYOffset + drawText(text, textX, y) +end + +function WarMenu.Button(text, subText) + if not currentMenu then + return + end + + optionCount = optionCount + 1 + + drawButton(text, subText) + + local pressed = false + + if currentMenu.currentOption == optionCount then + if currentKey == keys.select then + pressed = true + PlaySoundFrontend(-1, getStyleProperty('buttonPressedSound').name, getStyleProperty('buttonPressedSound').set, true) + elseif currentKey == keys.left or currentKey == keys.right then + PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) + end + end + + return pressed +end + +function WarMenu.SpriteButton(text, dict, name, r, g, b, a) + if not currentMenu then + return + end + + local pressed = WarMenu.Button(text) + + local currentIndex = getCurrentIndex() + if not currentIndex then + return + end + + if not HasStreamedTextureDictLoaded(dict) then + RequestStreamedTextureDict(dict) + end + DrawSprite(dict, name, getStyleProperty('x') + getStyleProperty('width') - spriteWidth / 2 - buttonSpriteXOffset, getStyleProperty('y') + getTitleHeight() + buttonHeight + (buttonHeight * currentIndex) - spriteHeight / 2 + buttonSpriteYOffset, spriteWidth, spriteHeight, 0., r or 255, g or 255, b or 255, a or 255) + + return pressed +end + +function WarMenu.InputButton(text, windowTitleEntry, defaultText, maxLength, subText) + if not currentMenu then + return + end + + local pressed = WarMenu.Button(text, subText) + local inputText = nil + + if pressed then + DisplayOnscreenKeyboard(1, windowTitleEntry or 'FMMC_MPM_NA', '', defaultText or '', '', '', '', maxLength or 255) + + while true do + DisableAllControlActions(0) + + local status = UpdateOnscreenKeyboard() + if status == 2 then + break + elseif status == 1 then + inputText = GetOnscreenKeyboardResult() + break + end + + Citizen.Wait(0) + end + end + + return pressed, inputText +end + +function WarMenu.MenuButton(text, id, subText) + if not currentMenu then + return + end + + local pressed = WarMenu.Button(text, subText) + + if pressed then + currentMenu.currentOption = optionCount + setMenuVisible(currentMenu.id, false) + setMenuVisible(id, true, true) + end + + return pressed +end + +function WarMenu.CheckBox(text, checked, callback) + if not currentMenu then + return + end + + local name = nil + if currentMenu.currentOption == optionCount + 1 then + name = checked and 'shop_box_tickb' or 'shop_box_blankb' + else + name = checked and 'shop_box_tick' or 'shop_box_blank' + end + + local pressed = WarMenu.SpriteButton(text, 'commonmenu', name) + + if pressed then + checked = not checked + if callback then callback(checked) end + end + + return pressed +end + +function WarMenu.ComboBox(text, items, currentIndex, selectedIndex, callback) + if not currentMenu then + return + end + + local itemsCount = #items + local selectedItem = items[currentIndex] + local isCurrent = currentMenu.currentOption == optionCount + 1 + selectedIndex = selectedIndex or currentIndex + + if itemsCount > 1 and isCurrent then + selectedItem = '← '..tostring(selectedItem)..' →' + end + + local pressed = WarMenu.Button(text, selectedItem) + + if pressed then + selectedIndex = currentIndex + elseif isCurrent then + if currentKey == keys.left then + if currentIndex > 1 then currentIndex = currentIndex - 1 else currentIndex = itemsCount end + elseif currentKey == keys.right then + if currentIndex < itemsCount then currentIndex = currentIndex + 1 else currentIndex = 1 end + end + end + + if callback then callback(currentIndex, selectedIndex) end + return pressed, currentIndex +end + +function WarMenu.Display() + if currentMenu then + if not IsPauseMenuActive() then + ClearAllHelpMessages() + HudWeaponWheelIgnoreSelection() + DisablePlayerFiring(PlayerId(), true) + DisableControlAction(0, 25, true) + + drawTitle() + drawSubTitle() + + currentKey = nil + + if skipInputNextFrame then + skipInputNextFrame = false + else + if IsNavigatedDown() then + PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) + + if currentMenu.currentOption < optionCount then + currentMenu.currentOption = currentMenu.currentOption + 1 + else + currentMenu.currentOption = 1 + end + elseif IsNavigatedUp() then + PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) + + if currentMenu.currentOption > 1 then + currentMenu.currentOption = currentMenu.currentOption - 1 + else + currentMenu.currentOption = optionCount + end + elseif IsControlJustReleased(2, keys.left) then + currentKey = keys.left + elseif IsControlJustReleased(2, keys.right) then + currentKey = keys.right + elseif IsSelectedPressed() then + currentKey = keys.select + elseif IsBackPressed() then + if menus[currentMenu.previousMenu] then + setMenuVisible(currentMenu.previousMenu, true) + PlaySoundFrontend(-1, 'BACK', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true) + else + WarMenu.CloseMenu() + end + end + end + end + + optionCount = 0 + end +end +WarMenu.End = WarMenu.Display + +function WarMenu.CurrentOption() + if currentMenu and optionCount ~= 0 then + return currentMenu.currentOption + end + + return nil +end + +function WarMenu.IsItemHovered() + if not currentMenu or optionCount == 0 then + return false + end + + return currentMenu.currentOption == optionCount +end + +function WarMenu.IsItemSelected() + return currentKey == keys.select and WarMenu.IsItemHovered() +end + +function WarMenu.SetTitle(id, title) + setMenuProperty(id, 'title', title) +end +WarMenu.SetMenuTitle = WarMenu.SetTitle + +function WarMenu.SetSubTitle(id, text) + setMenuProperty(id, 'subTitle', string.upper(text)) +end +WarMenu.SetMenuSubTitle = WarMenu.SetSubTitle + +function WarMenu.SetMenuStyle(id, style) + setMenuProperty(id, 'style', style) +end + +function WarMenu.SetMenuTitleVisible(id, visible) + setStyleProperty(id, 'titleVisible', visible) +end + +function WarMenu.SetMenuX(id, x) + setStyleProperty(id, 'x', x) +end + +function WarMenu.SetMenuY(id, y) + setStyleProperty(id, 'y', y) +end + +function WarMenu.SetMenuWidth(id, width) + setStyleProperty(id, 'width', width) +end + +function WarMenu.SetMenuMaxOptionCountOnScreen(id, count) + setStyleProperty(id, 'maxOptionCountOnScreen', count) +end + +function WarMenu.SetTitleColor(id, r, g, b, a) + setStyleProperty(id, 'titleColor', { r, g, b, a }) +end +WarMenu.SetMenuTitleColor = WarMenu.SetTitleColor + +function WarMenu.SetMenuSubTitleColor(id, r, g, b, a) + setStyleProperty(id, 'subTitleColor', { r, g, b, a }) +end + +function WarMenu.SetMenuSubTitleBackgroundColor(id, r, g, b, a) + setStyleProperty(id, 'subTitleBackgroundColor', { r, g, b, a }) +end + +function WarMenu.SetTitleBackgroundColor(id, r, g, b, a) + setStyleProperty(id, 'titleBackgroundColor', { r, g, b, a }) +end +WarMenu.SetMenuTitleBackgroundColor = WarMenu.SetTitleBackgroundColor + +function WarMenu.SetTitleBackgroundSprite(id, dict, name) + RequestStreamedTextureDict(dict) + setStyleProperty(id, 'titleBackgroundSprite', { dict = dict, name = name }) +end +WarMenu.SetMenuTitleBackgroundSprite = WarMenu.SetTitleBackgroundSprite + +function WarMenu.SetMenuBackgroundColor(id, r, g, b, a) + setStyleProperty(id, 'backgroundColor', { r, g, b, a }) +end + +function WarMenu.SetMenuTextColor(id, r, g, b, a) + setStyleProperty(id, 'textColor', { r, g, b, a }) +end + +function WarMenu.SetMenuSubTextColor(id, r, g, b, a) + setStyleProperty(id, 'subTextColor', { r, g, b, a }) +end + +function WarMenu.SetMenuFocusColor(id, r, g, b, a) + setStyleProperty(id, 'focusColor', { r, g, b, a }) +end + +function WarMenu.SetMenuFocusTextColor(id, r, g, b, a) + setStyleProperty(id, 'focusTextColor', { r, g, b, a }) +end + +function WarMenu.SetMenuButtonPressedSound(id, name, set) + setStyleProperty(id, 'buttonPressedSound', { name = name, set = set }) +end + +end \ No newline at end of file diff --git a/starter.lua b/starter.lua index 9572e92..118bd9a 100644 --- a/starter.lua +++ b/starter.lua @@ -175,6 +175,8 @@ for _, v in pairs({ -- This is a specific load order 'vehicles.lua', 'effects.lua', + --'warmenu.lua', + -- Do version check last '_scriptversioncheck.lua' }) do @@ -184,6 +186,6 @@ for _, v in pairs({ -- This is a specific load order local fileLoader = assert(load(LoadResourceFile('jim_bridge', ('shared/'..v)), ('@@jim_bridge/shared/'..v))) fileLoader() if debugMode then - print("^5Success^7: ^2loaded file^7: ^3"..(v):gsub("/", "^7/^3"):gsub("%.lua", "^7.lua").."^7") + print("^5CoreLoader^7: ^2loaded file^7: ^3"..(v):gsub("/", "^7/^3"):gsub("%.lua", "^7.lua").."^7") end end \ No newline at end of file