#include "messages.lua" #include "missions.lua" #include "starthub.lua" #include "ui/ui_helpers.lua" #include "ui/ui_extensions.lua" pEnabled = true from = "" messageKey = "" index = 0 totalMessages = 0 currentMessageId = "" currentMessageIndex = 1 scrollSpeed = 300 wheelScrollSpeed = 500 messageOffset = 0 hasMission = "" couldSkip = false -- to prevent "Skip mission" button from disappearing when we press it newMessageSound = nil receivedMessage = nil buzzSound = nil buzzHaptic = nil messageIds = nil playNotificationSound = nil AlternativeIcons = {} AlternativeIcons.getMenuCancel = function(iconSize) iconSize = iconSize or 42 if IsRunningOnPlaystation() then return string.format("[[ui/gamepadbuttons/psx/ui_icons_controls_ps5_circle_white.png;iconsize=%i,%i]]", iconSize, iconSize) elseif IsRunningOnXbox() then return string.format("[[ui/gamepadbuttons/gdk/ui_icons_controls_xboxseries_B_white.png;iconsize=%i,%i]]", iconSize, iconSize) else return string.format("[[menu:menu_cancel;iconsize=%i,%i]]", iconSize, iconSize) end end AlternativeIcons.getMenuPrev = function(iconSize) iconSize = iconSize or 42 if IsRunningOnPlaystation() then return string.format("[[ui/gamepadbuttons/psx/ui_icons_controls_ps5_l1_white.png;iconsize=%i,%i]]", iconSize, iconSize) elseif IsRunningOnXbox() then return string.format("[[ui/gamepadbuttons/gdk/ui_icons_controls_xboxseries_lb_white.png;iconsize=%i,%i]]", iconSize, iconSize) else return string.format("[[menu:menu_prev;iconsize=%i,%i]]", iconSize, iconSize) end end AlternativeIcons.getMenuNext = function(iconSize) iconSize = iconSize or 42 if IsRunningOnPlaystation() then return string.format("[[ui/gamepadbuttons/psx/ui_icons_controls_ps5_r1_white.png;iconsize=%i,%i]]", iconSize, iconSize) elseif IsRunningOnXbox() then return string.format("[[ui/gamepadbuttons/gdk/ui_icons_controls_xboxseries_rb_white.png;iconsize=%i,%i]]", iconSize, iconSize) else return string.format("[[menu:menu_next;iconsize=%i,%i]]", iconSize, iconSize) end end AlternativeIcons.getMenuAccept = function(iconSize) iconSize = iconSize or 42 if IsRunningOnPlaystation() then return string.format("[[ui/gamepadbuttons/psx/ui_icons_controls_ps5_x_white.png;iconsize=%i,%i]]", iconSize, iconSize) elseif IsRunningOnXbox() then return string.format("[[ui/gamepadbuttons/gdk/ui_icons_controls_xboxseries_A_white.png;iconsize=%i,%i]]", iconSize, iconSize) else return string.format("[[menu:menu_accept;iconsize=%i,%i]]", iconSize, iconSize) end end AlternativeIcons.getMenuSkip = function(iconSize) iconSize = iconSize or 42 if IsRunningOnPlaystation() then return string.format("[[ui/gamepadbuttons/psx/ui_icons_controls_ps5_square_white.png;iconsize=%i,%i]]", iconSize, iconSize) elseif IsRunningOnXbox() then return string.format("[[ui/gamepadbuttons/gdk/ui_icons_controls_xboxseries_X_white.png;iconsize=%i,%i]]", iconSize, iconSize) else return string.format("[[menu:interact;iconsize=%i,%i]]", iconSize, iconSize) end end Cellphone = {} Cellphone.lastButtonHeight = 50 Cellphone.gamepadButton = function(ico, label, iconSize, buttonWidth, buttonHeight, icoPadding, fade) fade = fade or 1 icoPadding = icoPadding or 0 iconSize = iconSize or 42 local contentWidth = iconSize + UiGetTextSize(GetTranslatedStringByKey(label)) + icoPadding local vPadding = 2 local hPadding = 10 local bWidth = buttonWidth local bHeight = buttonHeight or 50 if contentWidth + 2 * hPadding > buttonWidth then bWidth = contentWidth + 2 * hPadding end if iconSize + 2 * vPadding > bHeight and not buttonHeight then bHeight = iconSize + 2 * vPadding end Cellphone.lastButtonHeight = bHeight UiPush() UiAlign("middle center") UiTranslate(buttonWidth / 2, 0) UiColorFilter(1, 1, 1, fade) UiImageBox("ui/common/box-solid-6.png", bWidth, bHeight, 6, 6) UiPop() UiPush() UiAlign("left middle") UiTranslate(hPadding + (buttonWidth - contentWidth - 2 * hPadding) / 2, 0) UiPush() UiResetColor() UiColorFilter(1, 1, 1, 1) UiColor(fade, fade, fade, 1) UiWordWrap(1000) UiText(ico) UiPop() UiTranslate(iconSize + icoPadding, 0) UiColor(0,0,0) UiText(GetTranslatedStringByKey(label)) UiPop() end Cellphone.genericButton = function(width, label, ico, iconSize, action, fade) fade = fade or 1 local buttonWidth = width local buttonHeight = 50 local pressed = false if LastInputDevice() == UI_DEVICE_GAMEPAD then buttonHeight = 55 UiTranslate(0, buttonHeight / 2) Cellphone.gamepadButton(ico(iconSize), label, iconSize, buttonWidth, buttonHeight, 2, fade) pressed = InputPressed(action) else UiColorFilter(1, 1, 1, fade) UiButtonImageBox("ui/common/box-solid-6.png", 6, 6, 1, 1, 1) UiColor(0,0,0) pressed = UiTextButton(label, buttonWidth, buttonHeight) end return pressed end function init() playNotificationSound = false notificationInterval = 10 notificationTimer = 0 buzzCounter = 0 hudNotification = "loc@HUDNOTIFICATION_CHECK_YOUR" newMessageSound = LoadSound("MOD/script/cellphone/sms-signal.ogg") buzzSound = LoadSound("MOD/script/cellphone/vibrate-short.ogg") buzzHaptic = LoadHaptic("MOD/haptic/vibrate-short.xml") currentMessageId = "" currentMessageIndex = 1 receivedMessage = "" checkPhone("") showPhone = false end function update() end gMousePos = 0 gScrollMode = false function tick(dt) local playerVehicle = GetPlayerVehicle() local alarm = GetBool("level.alarm") local delivering = GetBool("level.delivering") if playNotificationSound and not alarm and not delivering and playerVehicle == 0 then notificationTimer = notificationTimer + dt if notificationTimer > notificationInterval then PlaySound(buzzSound) PlayHaptic(buzzHaptic, 1) notificationTimer = 0 buzzCounter = buzzCounter + 1 end if buzzCounter >= 3 then SetString("hud.notification", hudNotification) end end local newMessage = GetString("cellphone.newMessage") if newMessage ~= "" then receiveMessage(newMessage) SetString("cellphone.newMessage","") end if GetPlayerScreen() == UiGetScreen() and not showPhone then checkPhone("") end if showPhone and GetPlayerScreen() == UiGetScreen() and playNotificationSound and notificationTimer > 1 then --Receive message if phone is already active checkPhone("") end if showPhone and GetPlayerScreen() ~= UiGetScreen() then close() end if showPhone then SetBool("hud.disable", true) if InputDown("up") then messageOffset = messageOffset + scrollSpeed * dt elseif InputDown("down") then messageOffset = messageOffset - scrollSpeed * dt else if gScrollMode then if InputReleased("lmb") then gScrollMode = false else local _, mousePos = UiGetMousePos() if mousePos ~= gMousePos then local direction = 1 if mousePos > gMousePos then direction = -1 end messageOffset = messageOffset + direction * scrollSpeed * dt gMousePos = mousePos end end elseif InputPressed("lmb") then _, gMousePos = UiGetMousePos() gScrollMode = true else messageOffset = messageOffset + InputValue("mousewheel") * wheelScrollSpeed * dt end end end end function receiveMessage(messageId) local messageQue = GetString("savegame.mod.messageque") if isMessageReceived(messageId) then messageIds = splitString(messageQue,",") index = indexOf(messageIds,messageId) if index ~= nil then table.remove(messageIds,index) local clearedque = table.concat(messageIds,",") messageQue = clearedque SetString("savegame.mod.messageque",messageQue) else --- If the message was recieved earlier but it is not in the queue, --- it will be inserted below, so the index should be set to table elements number index = #messageIds + 1 end end receivedMessage = messageId playNotificationSound = true buzzCounter = 0 PlaySound(buzzSound) PlayHaptic(buzzHaptic, 1) PlaySound(newMessageSound) SetString("game.player.tool","cellphone") if messageQue == "" then messageQue = messageId else messageQue = messageQue .. "," .. messageId end SetString("savegame.mod.messageque", messageQue) SetInt("savegame.mod.message."..messageId, 1) end function checkPhone(messageId) showPhone = true playNotificationSound = false currentMessageId = messageId local messageQue = GetString("savegame.mod.messageque") messageIds = splitString(messageQue,",") currentMessageIndex = #messageIds totalMessages = #messageIds if messageId == "" then messageId = messageIds[currentMessageIndex] currentMessageId = messageId end if messageId then displayMessage(messageId) end end function displayMessage(messageId) from = gArtMessages[messageId].from messageKey = gArtMessages[messageId].body messageOffset = 0 index = indexOf(messageIds,messageId) if index == nil then index = 1 end totalMessages = #messageIds end function isMessageReceived(messageId) return GetInt("savegame.mod.message."..messageId) > 0 end function close() if receivedMessage ~= "" then SetInt("savegame.mod.message."..receivedMessage..".read", 1) end showPhone = false end function draw() UiFont("cellphone/pixeloid.ttf", 38) UiPush() drawPhone() UiPop() UiPush() UiColor(1,1,1,0.5) UiScale(2) UiImage("cellphone/raster.png") UiPop() end function measureMessageHeight(message, textFrom) local _, messageHeight = UiGetTextSize(message) local mmessageOffset = 70 if textFrom ~= "" then local _, h = UiGetTextSize(textFrom) mmessageOffset = 41 + h end return messageHeight + mmessageOffset, mmessageOffset end function drawPhone() UiPush() if LastInputDevice() == UI_DEVICE_GAMEPAD then UiSetCursorState(UI_CURSOR_HIDE) -- we draw mouse by ourselves, but not lock it end UiAlign("center middle") UiWordWrap(600) UiColorFilter(0.43, 0.87, 0.87) local buttonWidth = 200 local buttonHeight = 50 local offset = 21 UiTranslate(offset, 21) UiPush() UiAlign("top left") local prevText = GetTranslatedStringByKey("UI_BUTTON_PREV") local nextText = GetTranslatedStringByKey("UI_BUTTON_NEXT") local prevSize = UiGetTextSize(prevText) local nextSize = UiGetTextSize(nextText) local smsText = GetTranslatedStringByKey("UI_TEXT_SMS").." "..index.."/"..totalMessages local smsSize = UiGetTextSize(smsText) if prevSize > buttonWidth then buttonWidth = prevSize end if nextSize > buttonWidth then buttonWidth = nextSize end UiPush() UiPush() local freeSpace = UiWidth() - offset * 2 - buttonWidth * 2 UiTranslate(buttonWidth + (freeSpace - smsSize) / 2, 9) if index == nil then index = 0 end UiText(smsText, true) UiPop() UiPush() UiPush() local fade = 1 if currentMessageIndex - 1 <= 0 then UiDisableInput() fade = 0.5 end if Cellphone.genericButton(buttonWidth, "loc@UI_BUTTON_PREV", AlternativeIcons.getMenuPrev, 80, "menu_prev", fade) then if currentMessageIndex - 1 > 0 then if receivedMessage ~= "" then SetInt("savegame.mod.message."..receivedMessage..".read", 1) end UiSound("cellphone/button-beep.ogg", 0.5) currentMessageIndex = currentMessageIndex - 1 local messageId = messageIds[currentMessageIndex] currentMessageId = messageId displayMessage(messageId) end end UiPop() UiTranslate(-offset, 0) UiTranslate(UiWidth() - buttonWidth - offset, 0) UiPush() fade = 1 if currentMessageIndex + 1 > totalMessages then UiDisableInput() fade = 0.5 end if Cellphone.genericButton(buttonWidth, "loc@UI_BUTTON_NEXT", AlternativeIcons.getMenuNext, 80, "menu_next", fade) then if currentMessageIndex + 1 <= totalMessages then if receivedMessage ~= "" then SetInt("savegame.mod.message."..receivedMessage..".read", 1) end UiSound("cellphone/button-beep.ogg", 0.5) currentMessageIndex = currentMessageIndex + 1 local messageId = messageIds[currentMessageIndex] currentMessageId = messageId displayMessage(messageId) end end UiPop() UiPop() UiPop() if currentMessageId then hasMission = gArtMessages[currentMessageId].mission --spcecial case to be able to replay paperboy mission if currentMessageId == "intro" and GetBool("savegame.mod.tutorial.complete") then hasMission = "paperboy" end end local canSkip = couldSkip or (not GetBool("savegame.mod."..hasMission..".complete") and GetBool("options.game.missionskipping")) -- main screen with scrolling UiPush() UiTranslate(0, 68) local message = GetTranslatedStringByKey(messageKey) local areaHeight = 480 if hasMission ~= "" then areaHeight = 405 if canSkip then areaHeight = 340 end end local textFrom = GetTranslatedStringByKey("UI_TEXT_FROM") .. " " .. GetTranslatedStringByKey(from) local h, verticalOffset = measureMessageHeight(message, textFrom) UiWindow(600, areaHeight, true) if h > areaHeight then UiWordWrap(590) -- Added word wrap, so we have to recalculate height. -- New height will be same or greater than original height h, verticalOffset = measureMessageHeight(message, textFrom) local minOffset = areaHeight - h if messageOffset < minOffset then messageOffset = minOffset end if messageOffset > 0 then messageOffset = 0 end -- draw scrollbar UiPush() UiColor(0.25, 0.25, 0.25) UiTranslate(595, 0) UiRect(5, areaHeight) local scale = areaHeight/h local bar_height = areaHeight * scale offset = -messageOffset * scale UiRect(5, areaHeight) UiColor(1, 1, 1) UiTranslate(0, offset) UiRect(5, bar_height) UiPop() UiTranslate(0, messageOffset) end if from ~= "" then UiText(textFrom) end UiTranslate(0, verticalOffset) if messageKey == "" then message = GetTranslatedStringByKey("MESSAGE_NO_MESSAGES") end UiPush() UiText(message, true) UiPop() UiPop() UiTranslate(0, areaHeight + 75) UiPush() buttonWidth = 440 if currentMessageId then UiTranslate(20, 0) UiTranslate(-buttonWidth / 2, 0) if hasMission ~= "" then UiTranslate(280, 6) UiPush() local scale = 1 if not GetBool("savegame.mod."..hasMission..".complete") then scale = math.max(1.0, math.sin(GetTime() * 4) * 0.05 + 1.0) UiScale(scale) end UiTranslate(buttonWidth * (1 - scale) / 2, buttonHeight * (1 - scale) / 2) if Cellphone.genericButton(buttonWidth, "loc@UI_BUTTON_PLAY_MISSION", AlternativeIcons.getMenuAccept, 60, "menu_accept") then UiSound("cellphone/button-beep.ogg", 0.5) startMission(hasMission) end UiPop() UiTranslate(0, 64) if canSkip then UiPush() if Cellphone.genericButton(buttonWidth, "loc@UI_BUTTON_SKIP_MISSION", AlternativeIcons.getMenuSkip, 60, "interact") then UiSound("cellphone/button-beep.ogg", 0.5) skipMission(hasMission) couldSkip = true end UiPop() end end end UiPop() UiPop() UiPush() UiAlign("top left") buttonWidth = 200 UiTranslate(300, 570) UiTranslate(-buttonWidth / 2, -buttonHeight / 2) if Cellphone.genericButton(buttonWidth, "loc@UI_BUTTON_CLOSE", AlternativeIcons.getMenuCancel, 60, "menu_cancel") then UiSound("cellphone/button-beep.ogg", 0.5, 1.2) SetPlayerScreen(0) end UiPop() UiPop() if playNotificationSound then UiColor(0,0,0,0.5) UiRect(UiWidth(), UiHeight()) UiColor(1,1,1) if math.fmod(GetTime(), 0.8) > 0.3 then UiPush() UiColorFilter(0.5, 1, 1) UiAlign("center middle") UiTranslate(UiCenter(), UiMiddle()) UiImageBox("ui/common/box-solid-6.png", 500, 120, 6, 6) UiWindow(400, 100) UiTranslate(UiCenter(), UiMiddle()) UiScale(1.5) UiColorFilter(0, 0, 0) UiText(GetTranslatedStringByKey("UI_TEXT_NEW_MESSAGE")) UiPop() end end end function splitString(str, delimiter) local result = {} for word in string.gmatch(str, '([^'..delimiter..']+)') do result[#result+1] = trim(word) end return result end function indexOf(array, value) for i, v in ipairs(array) do if v == value then return i end end return nil end function trim(s) return (s:gsub("^%s*(.-)%s*$", "%1")) end function startMission(id) if receivedMessage ~= "" then SetInt("savegame.mod.message."..receivedMessage..".read", 1) end StartLevel(id, gArtMissions[id].file, gArtMissions[id].layers) end function skipMission(id) if receivedMessage ~= "" then SetInt("savegame.mod.message."..receivedMessage..".read", 1) end SetBool("savegame.mod."..id..".complete",true) startHub() end function resetProgress() DebugPrint(GetTranslatedStringByKey("TUTORIAL_PROGRESS")) SetBool("savegame.mod.tutorial.complete",false) SetBool("savegame.mod.tutorial.car",false) DebugPrint(GetTranslatedStringByKey("MISSION_PROGRESS")) SetBool("savegame.mod.paperboy.complete",false) SetBool("savegame.mod.art_heist.complete",false) SetBool("savegame.mod.museum_heist.complete",false) SetBool("savegame.mod.art_calm.complete",false) SetBool("savegame.mod.museum_destroy.complete",false) DebugPrint(GetTranslatedStringByKey("MOD_SAVEGAME")) ClearKey("savegame.mod") ClearKey("level.track") ClearKey("cellphone") end