Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local config = ac.configValues({
- hubUrl = ""
- })
- ---@class NoHesiClient
- local NoHesiClient = class("NoHesiClient")
- local closestcar = 1
- -- Import the WebBrowser library
- local WebBrowser = require('shared/web/browser')
- ui.setAsynchronousImagesLoading(true)
- -- Configure the WebBrowser
- WebBrowser.configure({
- targetFPS = 240,
- })
- -- Create an instance of the browser with initial parameters
- local browser = WebBrowser({
- size = vec2(500, 400), -- Set the size of the browser window
- backgroundColor = rgbm(0,0,0,0), -- Set the background color
- directRender = true, -- Enabl direct rendering
- redirectAudio = true, -- Enabele audio redirection
- })
- -- Store the last size of the browser window
- local lastSize = vec2(450, 420)
- -- Initialize mouse buttons states
- local mouseButtons = {false, false, false}
- function checkAndLoadVideo()
- local rootFolder = ac.getFolder(ac.FolderID.Root) .. "\\cache\\remote_assets\\nohesivideo"
- local targetFile = rootFolder .. "\\2_90FPS_2.wmv"
- print("Checking if target file exists: " .. targetFile)
- -- Check if the file exists
- if not io.fileExists(targetFile) then
- print("File does not exist, starting download process.")
- local function onAssetsLoaded(err, folder)
- if err then
- print("Error loading assets: " .. err)
- return
- else
- print("Assets loaded successfully.")
- end
- -- Path to the file to move
- local sourceFile = folder .. "\\2_90FPS_2.wmv"
- print("Source file path: " .. sourceFile)
- -- Create the directory if it doesn't exist
- if not io.dirExists(rootFolder) then
- print("Target directory does not exist, creating: " .. rootFolder)
- io.createDir(rootFolder)
- else
- print("Target directory already exists.")
- end
- -- Attempt to move the file
- print("Attempting to move file from " .. sourceFile .. " to " .. targetFile)
- if io.move(sourceFile, targetFile) then
- print("File successfully moved to " .. targetFile)
- loadVideoInPlayer(targetFile)
- else
- print("Failed to move the file. Checking permissions and file locks.")
- end
- end
- -- Start downloading and unpacking the assets
- print("Initiating download from URL.")
- web.loadRemoteAssets("https://cdn.discordapp.com/attachments/880053607024717854/1209263927045652591/2_90FPS_2.zip?ex=65e649cb&is=65d3d4cb&hm=33f584a885ac8ea30f635cea6c28831a9ea8bf0e17e4799a51597b2bc1b81d00&", onAssetsLoaded)
- else
- print("File already exists, loading into MediaPlayer.")
- loadVideoInPlayer(targetFile)
- end
- end
- local htmlContent = [[
- <!DOCTYPE html>
- <html lang='en'>
- <head>
- <meta charset='UTF-8'>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,slnt,wdth,wght,GRAD,XTRA,YOPQ,YTAS,YTDE,YTFI,YTLC,[email protected],-10..0,25..151,100..1000,-200..150,323..603,25..135,649..854,-305..-98,560..788,416..570,528..760&display=swap" rel="stylesheet">
- <style>
- .shake-top {
- animation: shake-top 0.8s cubic-bezier(0.455, 0.030, 0.515, 0.955) both;
- }
- @keyframes shake-top {
- 0%, 100% {
- transform: rotate(0deg);
- transform-origin: 50% 0;
- }
- 10% {
- transform: rotate(2deg);
- }
- 20%, 40%, 60% {
- transform: rotate(-4deg);
- }
- 30%, 50%, 70% {
- transform: rotate(4deg);
- }
- 80% {
- transform: rotate(-2deg);
- }
- 90% {
- transform: rotate(2deg);
- }
- }
- .blink-2 {
- animation: blink-2 0.4s both;
- }
- @keyframes blink-2 {
- 0% {
- opacity: 1;
- }
- 50% {
- opacity: 0.1;
- }
- 100% {
- opacity: 1;
- }
- }
- :root {
- --font-family: 'Roboto Flex', sans-serif;
- --second-family: 'Roboto', sans-serif;
- --main-color: #B130FF; /* Purple color */
- }
- body, html {
- height: 100%;
- margin: 0;
- display: flex;
- justify-content: left;
- align-items: baseline;
- background-color: rgba(0, 0, 0, 0.0)/* Dark background */
- }
- .dashboard {
- justify-content: flex-start; /* Это изменение выровняет все содержимое dashboard, включая logo, слева */
- }
- .logo {
- justify-content: flex-start;
- width: 100%;
- }
- .logo-text {
- margin-left: 10px;
- font-size: 24px;
- font-weight: 700;
- font-family: var(--second-family);
- }
- .score-section {
- display: flex;
- align-items: center; /* This will vertically center the items */
- margin-top: 20px; /* Adjust as necessary */
- }
- .score-text {
- margin-top: -13px; /* Поднимает элемент на 10 пикселей вверх */
- font-family: 'Roboto Flex', sans-serif;
- font-weight: 1000; /* Extra Black - Обратите внимание, что максимальный доступный вес может быть 900 для некоторых шрифтов */
- font-size: 26px;
- line-height: 106%;
- letter-spacing: 0.03em;
- text-transform: uppercase;
- color: #fff;
- /* Применение настроек переменных шрифтов */
- font-variation-settings: 'wdth' 111, 'GRAD' 0, 'slnt' 0, 'XTRA' 496, 'XOPQ' 96, 'YOPQ' 79, 'YTLC' 416, 'YTUC' 712, 'YTAS' 649, 'YTDE' -203, 'YTFI' 738, 'opsz' 79;
- }
- .score-badge {
- margin-top: -40px; /* Поднимает элемент вверх */
- margin-left: 20px; /* Поднимает элемент вверх */
- border: 1.2px solid rgba(255, 255, 255, 0.13); /* Оставляем рамку как есть, если она вам нужна */
- border-radius: 4px;
- padding: 4px 8px 4px 4px; /* Измененные паддинги */
- width: 71px; /* Измененная ширина */
- height: 21px; /* Измененная высота */
- background: rgba(1, 1, 1, 1); /* Новый цвет фона */
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 6px; /* Добавляем пространство между SVG и текстом */
- }
- .score-badge-text {
- font-family: 'Roboto Flex', sans-serif;
- font-weight: 740; /* Medium */
- font-size: 16px; /* Размер шрифта оставляем как был */
- color: rgba(238, 237, 238, 0.87); /* Цвет текста оставляем как был */
- font-variation-settings: 'wdth' 151, 'GRAD' 0, 'slnt' 0, 'XTRA' 468, 'XOPQ' 96, 'YOPQ' 79, 'YTLC' 514, 'YTUC' 712, 'YTAS' 750, 'YTDE' -203, 'YTFI' 738, 'opsz' 21;
- }
- .speed-up {
- margin-top: -20px; /* Поднимает элемент на 10 пикселей вверх */
- font-family: 'Roboto Flex', sans-serif;
- font-weight: 700; /* Bold */
- font-stretch: expanded;
- font-style: oblique 10deg; /* Для курсива */
- font-size: 11px;
- line-height: 82%;
- text-transform: uppercase;
- color: #ff2074;
- border: 0.49px solid rgba(255, 0, 95, 0.68);
- border-radius: 8px;
- padding: 7px 13px;
- width: 210px;
- height: 15px;
- background: rgba(255, 32, 116, 0.15);
- display: flex;
- justify-content: center;
- align-items: center;
- /* Использование переменных шрифтов */
- font-variation-settings: 'wdth' 151, 'slnt' -10, 'XTRA' 468, 'XOPQ' 96, 'YOPQ' 79, 'YTLC' 514, 'YTUC' 712, 'YTAS' 750, 'YTDE' -203, 'YTFI' 738;
- }
- .points-info {
- font-weight: 1000; /* Extra Black - убедитесь, что шрифт поддерживает это значение */
- font-size: 22px;
- line-height: 100%;
- text-transform: uppercase;
- color: #fff; /* White color */
- font-family: 'Roboto Flex', sans-serif;
- font-variation-settings: 'wdth' 111, 'GRAD' 0, 'slnt' 0, 'XTRA' 496, 'XOPQ' 96, 'YOPQ' 79, 'YTLC' 416, 'YTUC' 712, 'YTAS' 649, 'YTDE' -203, 'YTFI' 738, 'opsz' 79;
- margin-top: 40px; /* Adjust space from the SPEED UP block */
- }
- .block-model {
- display: flex;
- justify-content: flex-start; /* Выравнивает элементы по левому краю */
- gap: 9px; /* Использует переменную для расстояния между элементами */
- margin-top: 10px; /* Отступ сверху */
- padding-left: var(--padding-left-block-model); /* Если нужен отступ слева для всего контейнера */
- width: 100%; /* Занимает всю доступную ширину */
- }
- .item {
- display: flex;
- flex-direction: column; /* Устанавливает направление элементов в колонку */
- align-items: center; /* Центрирует элементы по горизонтали */
- }
- .combo-text {
- font-weight: 700;
- font-size: 12px;
- line-height: 0%;
- letter-spacing: -0.02em;
- text-transform: uppercase;
- text-align: center;
- color: var(--combo-text-color, rgba(238, 237, 238, 0.68)); /* Значение по умолчанию */
- font-family: 'Roboto Flex', sans-serif;
- margin-bottom: 8px; /* Отступ между текстом COMBO и объектом */
- font-variation-settings: 'wdth' 151, 'slnt' -10, 'GRAD' 0, 'XTRA' 468, 'XOPQ' 96, 'YOPQ' 79, 'YTLC' 514, 'YTUC' 712, 'YTAS' 750, 'YTDE' -203, 'YTFI' 738, 'opsz' 10;
- }
- .object {
- border: 0.86px solid rgba(255, 255, 255, 0.13);
- border-radius: 6px;
- padding: 5px 6px;
- width: 61px;
- height: 20px;
- background: rgba(255, 255, 255, 0.08);
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .object-text {
- font-weight: 600;
- font-size: 14px;
- line-height: 143%;
- text-align: center;
- color: rgba(238, 237, 238, 0.87);
- font-family: 'Roboto Flex', sans-serif;
- }
- .speed-warning {
- color: rgba(255, 32, 116, 1);
- font-family: 'Roboto Flex', sans-serif;
- font-size: 14px;
- font-weight: 700;
- line-height: 10px;
- letter-spacing: 0em;
- text-transform: uppercase; /* Делает весь текст в верхнем регистре */
- text-align: left; /* Центрирует текст по центру */
- font-variation-settings: 'wdth' 151, 'GRAD' 0, 'slnt' -10, 'XTRA' 468, 'XOPQ' 96, 'YOPQ' 79, 'YTLC' 514, 'YTUC' 712, 'YTAS' 750, 'YTDE' -203, 'YTFI' 738;
- margin-top: 20px; /* Отступ сверху, чтобы отделить текст от объектов */
- margin-left: 20px; /* Отступ сверху, чтобы отделить текст от объектов */
- display: block; /* Гарантирует, что элемент будет на новой строке */
- }
- .content {
- visibility: hidden; /* Содержимое скрыто, но прогружается */
- opacity: 0;
- transition: visibility 0s, opacity 1s linear; /* Плавное появление */
- transition-delay: 2s; /* Задержка перед началом анимации появления */
- }
- .logo-text {
- position: absolute;
- opacity: 0; /* Изначально текст невидим */
- margin-top: 30px
- }
- </style>
- </head>
- <body>
- </div>
- </div>
- </div>
- <div class="content">
- <div class='dashboard'>
- <div class='logo'>
- <svg width='90' height='28' viewBox='0 0 90 28' fill='none' xmlns='http://www.w3.org/2000/svg'>
- <path d='M13.3 5.2l.1-.1 1.7.5h.8q1.3-.4 2.4-.4v.5q-.5.6-.5.9l.2.9-.2.2v.1l.3 1v1.4q-.5.7-.5 1.2v.2l.7 1.7-.7 1.6.6 1v.1l-.2 1.4v2.3l.2.4v.6l-.1.7.1.2v.1l-.2 1 .2.9-2.9.4-1.3-.2-.5.1-.6-.1-.8.2-.4-.1-.4.1q-.2-.3-.3-1.2 1.1 0 1.1-2v-.2l-.2-1.1.2-1.9V16l-.5-1.5v-.4q.2-.3.6-3.1l.2-.4-.2-.7.1-.9q-.3-2.4-1-2.4-.4 0-.6-.5 0-.6 2.6-.9zm6.1.1l1.1-.1 1.8 1.2q1.4 0 3 2.1.6 2.4 1.4 2.4.1 1.3.3 1.6v1l-.1 1 .1.5-.2.5.2.5q-1 4.7-2 4.7l.1.6q-1 .2-2.4 1.9-2.3.3-2.5.7-1 0-1-1.5l1.5-.8v-.3h.1l-.1-3.7v-1.9l.1-.9-.1-.1-.2-.4h-.1l.3-.7-.2-.8.2-.1h.1l-.1-1q.2-.3.3-1.8l-.3-.5.1-.5q-.2 0-.6-1l.1-.4-.1-.7h.1l-.1-.4h-.8zm10 20.6v1.6l-.4.1-.3-.2h-.4l-.4.1v-.1h-.5l-.6.1q-.2-.1-.6-.1l-.5.1q-.6 0-.9-.2l-.5.1h-.6l-.3-.1-.4.3-.8-.2-.5.1-.2-.1h-.2l-.1-.1q-.8 0-1 .3h-.5q0-.1-.7-.2l-.2.1-.6-.2-.5.2h-.4l-.2.1q-.8-.1-.8-.3l-1.1.2-.9-.1-.9.2-.7-.2h-1.1q-.1.1-.7.2l-.3-.1h-.1v-1.7l1.2.2q.3 0 .5-.2.4 0 .7.2l.8-.2 1.2.1h.1l.2.1.4-.1h.3l.3.1.9-.1.7.1h1l.7-.1h.2l1.9-.1H22.2q.2.2.4.3l.6-.2h.4q.3-.1.6-.1h.2l.3.1.4-.1.7.1.6-.2q1.1.2 2 .2H29.4zm0-23.9v1.5l-.7-.1-.9.2-.7-.1H25.9l-.5.2-.4-.1h-.1l-.4-.1-.1.1-.7-.2-.2.1-.6-.1-.6.1h-.2l-.4-.1v.1l-.5-.1-.2.1-.2-.1h-.6l-.4.2h-.1v-.1l-.9.2-.5-.2h-1l-.1-.1-.1.1h-1.2l-.2-.1-.2.2-.5-.2-.2.1-.1-.1-.3.2-.6-.1h-.4l-.7-.2-1.5.2h-.7V1.9q0 .1.3.1h.1v-.1l.3.2h.1q.1-.1.5-.1l.2-.3 1.5.4h.1l.6-.3h.6l.1.2 1.2-.1q.3.1.7.1.2 0 .5-.2l.3.2h.3l.6-.1.6.2q.7-.1.8-.3l.2.1q.5 0 .8-.3.3 0 .5.2.9.2.9.3l.4-.1v.1h.1l.3-.1h.6l1.6-.3 1.3.2.8-.1v.1l.2-.1h.7l.7.2h.4zm1.2 3.1l1.4.5h.8l2 .1v.1q0 1.5-1.3 1.5l-1.6 1.3q0 1.1-1.5 2l-.2.6q-.9-.2-.9-.4l.3-.5-.1-.8.2-.7-.1-.2v-.1l.4-.8v-.2q-.5-.7-.5-1.2v-.6q.3-.6 1.1-.6zm5.1 1.2l1.3-.8q.8.3 1.7.3l1.5-.3q2.1.1 2.1.5-.2 1.4-.6 1.4l.1.9q-1 2.7-1.7 3.3l-.4 1.6v.8q-1.5 1.1-1.5 2.4-1.2 1.8-1.9 4.6-.4 0-1 2.2v.6l-.6.1-3.4-.5h-1.1l-.5.1q-.6 0-.6-1.2l.2-.6h-.1q0-.3 1.2-1.2l1-2.3-.1-.7 2.7-5.3q0-1.3 1.3-3.3l-.1-.1v-.2q.2-.2.5-2.3zM41.5 18h.3l.5.4-.3 1.8.6 3.2-.2.2-1-.1-.5.4-1.1-.1-1.1.2q-2.7-.2-2.7-.5.4-1.4 1.2-1.4l1.8-.7q.3 0 .3-.6 1.1 0 1.5-2.3.1-.5.7-.5zm3-16.3v1.5l-.5.1-.8-.2-.4.1-.6-.1-.6.2-.2-.1-1.9.2-.1-.1-.4.1-.2-.1q-.1-.2-.4-.2-.5.1-.5.2l-.5-.1q-.2.2-.5.2h-.2l-.3-.1h-.5l-.6-.1-.6.2q-1.1-.2-2.1-.2l-.1.1v-.1l-.5.1-.3-.1q-.2 0-.2.2-.3 0-1.3-.3l-.8.2h-.5V1.7q0 .1.1.1.5-.1 1.1-.1h1.1l.8-.1.4.1H32.7l.4-.1.1.1h.5l.5-.1q.3.1.6.1l.5-.1q.6 0 1 .2l.4-.1h.7l.3.1.3-.2.9.1.4-.1.2.1H39.8l.1.1q.7 0 1-.3l.2.1.3-.1q0 .1.7.2l.1-.1.7.2.4-.2h.7q.3 0 .5.1zm0 25.8h-.7q-.8 0-.8-.2l-1.1.2-1-.1-.8.1-.7-.1h-1.1q-.1.1-.7.1h-.3l-.3.1-.2-.1-.3.1q0-.2-.7-.3l-.9.2H33l-.1-.1h-.2l-.3.1-.2-.1q0 .1-1.1.1-.1-.1-.6-.1 0 .1-.5.1 0-.1-.5-.2h-.6V26h.3l.7-.2.7.1h.8l.5.1 1.6-.2.1.1.2-.1.2.1.1-.1.4.1.6-.1.8.1.8-.2 1.7.2q.3 0 .5-.1.4 0 .7.1l.8-.1 1.2.1v-.1h.1l.2.1.4-.1h.3l.3.1.9-.1.7.2zm5.1-22.4q1.2.3 1.2.5V6q0 .9-1.5 1.3l-.5 1.4q.7 1.8 1.5 1.8.1.3 2.5 1 1.8 1.6 2.9 1.6 0 .2 1.2 1.2v.9q.3 0 .3.2l-.1.5q.5.6.5 2.1-.3 0-.5 2.2-1.2 1.3-2.3 3-2.7.8-3.7.8H51q-.2 0-.4-.7.2-1.2 1.1-1.2 1.2 0 1.3-1.1v-1.1q-.5-2.2-3-2.2-.1-.4-2-1 0-.3-1.9-1.1-.6-1.6-.9-1.6v-.6l-1-1.3.6-1.2-.4-1.2.4-.2v-.6q1.6-2.5 2.6-2.9l.1.1h.2q.3 0 1.9-1zm6.9 0h.1q.1.9.4.9l-.1.6.1.7-.4 1.3v.1l.5 1.3v.4q0 .6-.7 1.2h-.6v-1.2Q54.4 8.1 54 7.9q-2-1-2.3-1.3v-.3q0-.9.5-.9 2.3.3 2.7.7h.5q0-.8 1.1-1zM44.9 16.8h.4q.4.2.9 1.4v.1l-.1.6q.8.6 1.3 2.5v.1h.6l1.9 1.4v.6q0 .4-1.3.4-.1-.3-.8-.6l-.1.1h-.2q-.7-.3-.7-.5-.8 0-1.4 1H45l-.3-.6.1-.6-.3-.3v-.8l-.2-.7.2-1v-.8l-.1-.5q.2-1.8.5-1.8zM59.1 1.7v1.6h-1.5l-.6.1-.8-.1-.8.2-1.7-.3q-.3 0-.5.2-.4 0-.7-.2l-.8.2-1.2-.1h-.1l-.2-.1-.4.1h-.3l-.3-.1-.9.1-.7-.1h-1l-.7.1h-.2l-1.7.1V1.8l.1-.1.1.1q.8 0 1-.2h.5q0 .1.7.1h.2l.6.1.5-.1h.4l.2-.1q.8.1.8.2l1.1-.1h1l.8-.1.7.2h1.1q.1-.1.7-.2l.3.1.3-.1h.2l.3-.1q0 .3.7.3l.9-.2h.4l.5.1h.1v-.1l.6.1h.3zm0 24v1.6q-.8 0-1.7.2l-.6-.2-.7.1-.4-.1-.3.1h-.2q-.3 0-.5-.1h-.5l-.5-.2q-.3.1-.5.3h-.6l-1.9-.1h-.2l-.6-.1h-1l-.8.1-.9-.1-.3.1h-.3l-.4-.1-.1.1H46l-1.3.1-.7-.2v-1.5l.4-.1.9.1h.9l1.1.2q0-.2.8-.3l.2.1h.5l.4.2.7-.2h.1q.7 0 .7-.1h.5q.3.3 1 .3l.1-.2.2.1.3-.1.4.1.8-.2.4.3.3-.2.1.1.5-.1.5.2q.3-.2 1-.2l.4.1q.4 0 .6-.1h1.1zM60 5.1l1 .1.9-.1q1.2 0 1.3 1.6l-1.2.7v.3q0 1.1-.7 1.8 0 2.5-.9 2.5H60q-.4 0-.7-1.7l.1-.2V10l-.1-.7.1-1.7-.1-.2v-.1l.1-.4q-.3 0-.5-1.4 0-.4 1.1-.4zm11.2-1.7l-.1-.1q-1.1 0-1.1-.1h-.2l-.2-.1q0 .2-1.5.3l-.9-.2h-1.3l-.6.1-.6-.1h-.5l-.5-.2-.8.2-.5-.1-.7.2-.7-.1h-.8l-.5-.1-1.1.1V1.7l.1.1h.2l.3-.2.2.2q0-.2 1.1-.2.1.1.6.2 0-.2.5-.2 0 .1.5.2h.6l.4-.2 1.1.3.4-.1h.1l.3-.1.9.2.5-.2.1.1.8-.2.7.3 1.1-.3 1 .1.4-.1q0 .1.5.2l.1-.2q.6 0 1.1.2.6 0 .8-.3h.2l.9.2h.2l.6-.2q1.1.2 1.3.4h.2q.3 0 .3-.2h.1v.1h.2v1.5h-.3l-.4.1q-.7-.1-.8-.2h-.2l-1.1-.1q-.7 0-.9.2h-.1l-.2-.1-.3.1v-.1h-.2q-.3.1-1.3.2zM71 27.3h-.1l-.4.1q0-.1-.2-.1l-.4.1v-.1q-.5.1-.7.2-.5-.1-.7-.3h-2.1l-.4-.1-.9.2h-.2l-.4-.1h-.3q-.3 0-.9.2l-.2-.1-.7.1h-.2l-.9-.3q-.9.3-1.2.3 0-.2-.2-.2l-.3.1-.5-.1v.1l-.1-.1q-.2 0-.4.1v-1.6l.3.1.2-.1.1.1.4-.2.8.2 1-.1q.7 0 1.2.2.5-.4.7-.4h.2l1 .2h.7l.4-.1.9.1q.2 0 .8-.2.2 0 .7.3h.1l.2-.1v.1h.1q.2-.2.3-.2h.5q.1.1.3.1.8 0 .8-.1l.6.2.3-.1H71.5l.8.2h.2l.9.1.6-.1.3.1 1.6-.3h.3q.1.2.8.2v1.5l-.6-.2-1.1.4-1.5-.2q0 .2-.3.2h-.2q-.3 0-.5-.2l-1.1.2q-.6 0-.7-.3zm.4-21.8l1.1-.3h1.8q.7 1.2.7 2.6-.4.6-.4 1.7l.2.5-.5 2h-.6q-.3 0-.6-1.4l.3-.2q0-2.3-1.2-2.8 0-.8-.8-.8l-.1-.1v-.1l.2-.5zm-3.6-.3l.5.2.6-.2q.7 0 1.3.6l-.1 2.4.1 1.1-.3.6.4 2.2q-.3 0-.4.9l.2.4-.3.7v.1l.1.4-.2 2.6q.3.4.5 2.7l-.1.5.1.4-.3.2v.6q.2.4 1.4.8l.1.6v.2q-.3 0-.6.7h-.4l-.7-.2q-.6.3-1.6.3l-.8-.3v.2l-.1-.2H67l-1 .3q-.6 0-1.5-.6l-1.2.3q0-.3-.5-.3v-.3q.3 0 .3-.6.6 0 .9-1.6l.1-.1v-1.1q0-2.6.5-2.9v-.1q-.2-1.6-.7-1.9v-.1q.4-.4.4-.8V13l-.3-2v-.1q0-.9.8-1.9 0-1.8-.4-3.4h.1l2.3.1q0-.3 1-.5z' fill='#e319d8' stroke='#1428c4' stroke-miterlimit='#10' stroke-width="1.5"/>
- </svg>
- </div>
- <div class='score-section'>
- <p class='score-text'>PB 0 PTS</p>
- <div class='score-badge'>
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M15.4259 10.5856V13.502L14.9075 13.7149L6.82868 5.61646V14.1579L10.9011 18.205H7.27772L4.28613 15.225V0.213343L4.80369 0L15.4259 10.5856ZM19.1747 24L19.6922 23.7867V8.77496L16.7007 5.79503H13.0989L17.1497 9.8421V18.3835L9.07086 10.2851L8.55244 10.498V13.4144L19.1747 24Z" fill="#08060A" fill-opacity="0.83" />
- </svg>
- <span class='score-badge-text'>#1</span>
- </div>
- </div>
- </div>
- <!-- Новый элемент для SPEED UP -->
- <div class='speed-up'>SPEED UP!</div>
- <div class='points-info' id='pointsDisplay'>PB 0 PTS</div>
- <div class='block-model'>
- <div class='item'>
- <p class='combo-text'>COMBO</p>
- <div class='object'>
- <div class='object-text' style='color: rgba(255, 214, 67, 0.87);'>0x</div>
- </div>
- </div>
- <div class='item'>
- <p class='combo-text'>SPEED</p>
- <div class='object'>
- <div class='object-text' style='color: rgba(106, 255, 103, 0.87);'>0x</div> <!-- Пример другого цвета -->
- </div>
- </div>
- <div class='item'>
- <p class='combo-text'>Proximity</p>
- <div class='object'>
- <div class='object-text' style='color: rgba(238, 237, 238, 0.87);'>0x</div> <!-- И еще один цвет -->
- </div>
- </div>
- </div>
- <p class='speed-warning'>KEEP SPEED ABOVE 95KM/H</p>
- </div>
- </div>
- </div>
- <script>
- // Функция для плавного обновления числа
- // Функция для плавного обновления числа с поддержкой условного форматирования текста
- // Универсальная функция анимации для обновления числовых значений
- function animateValueUpdate({ selector, oldValue, newValue, isPB = false, precision = 0, unit }) {
- const duration = 700;
- const start = performance.now();
- function updateNumber(timestamp) {
- const elapsedTime = timestamp - start;
- const progress = Math.min(elapsedTime / duration, 1);
- const currentNumber = oldValue + (newValue - oldValue) * progress;
- // Использование локали 'en-US' для формата с запятой как разделителем тысяч
- const formattedNumber = currentNumber.toLocaleString('en-US', { minimumFractionDigits: precision, maximumFractionDigits: precision });
- const displayValue = isPB ? `PB: ${formattedNumber} ${unit}` : `${formattedNumber}${unit}`;
- document.querySelector(selector).textContent = displayValue;
- if (progress < 1) {
- requestAnimationFrame(updateNumber);
- }
- }
- requestAnimationFrame(updateNumber);
- }
- // Обработка обновления различных значений
- function updateValue(type, newValue) {
- const selectorMap = {
- bestScore: '.score-text',
- currentScore: '#pointsDisplay',
- combo: '.item:nth-child(1) .object-text',
- speed: '.item:nth-child(2) .object-text',
- proximity: '.item:nth-child(3) .object-text'
- };
- const isPB = type === 'bestScore';
- const selector = selectorMap[type];
- const currentValueText = document.querySelector(selector)?.textContent.replace(/[^\d.-]/g, '') || "0";
- const currentValue = parseFloat(currentValueText);
- const precision = ['combo', 'speed', 'proximity'].includes(type) ? 1 : 0;
- const unit = ['combo', 'speed', 'proximity'].includes(type) ? "x" : "PTS"; // Удаление пробела для X
- animateValueUpdate({ selector, oldValue: currentValue, newValue: parseFloat(newValue), isPB, precision, unit });
- }
- // Очередь для хранения данных обновлений
- const updateQueue = [];
- let isAnimating = false; // Флаг, показывающий, идет ли в данный момент анимация
- function processNextUpdate() {
- if (updateQueue.length === 0 || isAnimating) {
- return; // Выходим, если анимация уже идёт или нет заданий в очереди
- }
- isAnimating = true; // Устанавливаем флаг анимации
- const data = updateQueue.shift(); // Извлекаем первый элемент очереди
- const speedUpElement = document.querySelector('.speed-up');
- if (!speedUpElement) {
- console.error("Элемент для обновления сообщения о ускорении не найден.");
- isAnimating = false;
- processNextUpdate(); // Пытаемся обработать следующий элемент в очереди
- return;
- }
- // Удаляем предыдущие классы анимации и добавляем новый
- speedUpElement.classList.remove('blink-2', 'shake-top');
- const animationClass = data.color.r === 1 && data.color.g === 0 && data.color.b === 0 ? 'shake-top' : 'blink-2';
- speedUpElement.classList.add(animationClass);
- // Изменяем цвет в середине анимации blink-2
- setTimeout(() => {
- speedUpElement.style.background = `rgba(${data.color.r * 255}, ${data.color.g * 255}, ${data.color.b * 255}, 0.2)`;
- speedUpElement.style.border = `0.49px solid rgba(${data.color.r * 255}, ${data.color.g * 255}, ${data.color.b * 255}, 0.68)`;
- speedUpElement.style.color = `rgba(${data.color.r * 255}, ${data.color.g * 255}, ${data.color.b * 255}, 1)`;
- }, 200); // 200ms для достижения 50% анимации blink-2
- // Обновляем текст в середине анимации
- setTimeout(() => {
- speedUpElement.textContent = data.message;
- }, 200); // Обновляем текст одновременно с изменением цвета
- // Завершаем анимацию и удаляем классы анимации с небольшой задержкой после полного цикла
- setTimeout(() => {
- speedUpElement.classList.remove('blink-2', 'shake-top');
- isAnimating = false;
- processNextUpdate(); // Обрабатываем следующее обновление в очереди
- }, 400); // Полная длительность анимации blink-2
- }
- function updateSpeedUpMessage(data) {
- updateQueue.push(data); // Добавляем данные в очередь
- if (!isAnimating) {
- processNextUpdate(); // Запускаем обработку очереди, если анимация не активна
- }
- }
- function updatePlace(newPlace) {
- console.log("updatePlace called with:", newPlace); // Отладочная информация
- // Поиск элемента, содержащего текст места
- const placeElement = document.querySelector('.score-badge-text');
- if (!placeElement) {
- console.error("Элемент для обновления места не найден.");
- return;
- }
- // Обновление текста элемента
- placeElement.textContent = `${newPlace}`;
- }
- function updateBadgeStyle() {
- const badge = document.querySelector('.score-badge');
- const badgeText = badge.querySelector('.score-badge-text');
- const svg = badge.querySelector('svg');
- // Определение стилей для разных позиций
- const styleConfigs = {
- 1: {
- backgroundColor: 'rgba(221, 3, 85, 1)',
- textColor: 'rgba(8, 6, 10, 1)',
- logoColor: 'rgba(8, 6, 10, 1)',
- fontSize: '16px'
- },
- 2: {
- backgroundColor: 'rgba(238, 237, 238, 0.87)',
- textColor: 'rgba(8, 6, 10, 1)',
- logoColor: 'rgba(8, 6, 10, 1)',
- fontSize: '16px'
- },
- 3: {
- backgroundColor: 'rgba(255, 129, 38, 1)',
- textColor: 'rgba(8, 6, 10, 1)',
- logoColor: 'rgba(8, 6, 10, 1)',
- fontSize: '16px'
- },
- default: {
- backgroundColor: 'rgba(255, 255, 255, 0.17)',
- textColor: 'rgba(238, 237, 238, 0.87)',
- logoColor: 'rgba(238, 237, 238, 0.52)',
- fontSize: ['16px', '14px', '12px'] // Используйте динамическое определение размера шрифта
- }
- };
- const position = parseInt(badgeText.textContent.replace(/[^\d]/g, '')) || 0;
- const config = styleConfigs[position] || styleConfigs.default;
- // Динамическое определение размера шрифта на основе позиции
- const fontSizeIndex = position < 100 ? 0 : position < 1000 ? 1 : 2;
- const finalFontSize = config.fontSize instanceof Array ? config.fontSize[fontSizeIndex] : config.fontSize;
- // Применение стилей
- badge.style.background = config.backgroundColor;
- badgeText.style.color = config.textColor;
- badgeText.style.fontSize = finalFontSize;
- svg.querySelectorAll('path').forEach(path => path.setAttribute('fill', config.logoColor));
- }
- // Функция для управления видимостью предупреждения о скорости
- function speedWarningDisplay(show) {
- const speedWarningElement = document.querySelector('.speed-warning');
- if (!speedWarningElement) return; // Выходим, если элемент не найден
- // Устанавливаем свойство display в зависимости от аргумента show
- speedWarningElement.style.display = show ? 'block' : 'none';
- }
- // Обработчик события, получающий данные от AC
- AC.onReceive('speedblock', arg => {
- // Проверяем значение arg, чтобы определить, показывать или скрывать предупреждение
- const shouldShow = arg === 'show'; // Пример условия, arg должен быть 'show' для отображения
- speedWarningDisplay(shouldShow);
- });
- function setupEventHandlers() {
- AC.onReceive('updateBestScore', newValue => updateValue('bestScore', newValue));
- AC.onReceive('updateScore', newValue => updateValue('currentScore', newValue));
- AC.onReceive('updateCombo', newValue => updateValue('combo', newValue));
- AC.onReceive('updateSpeed', newValue => updateValue('speed', newValue));
- AC.onReceive('updateProximity', newValue => updateValue('proximity', newValue));
- AC.onReceive('messages', updateSpeedUpMessage);
- AC.onReceive('speedblock', arg => speedWarningDisplay(arg === 'show'));
- AC.onReceive('updatePlace', newPlace => {
- updatePlace(newPlace);
- updateBadgeStyle(); // Вызов для обновления стилей после изменения места
- });
- AC.onReceive('webanim', arg => {
- if(arg === "startweb") {
- showContent();
- }
- });
- }
- function showContent() {
- const content = document.querySelector('.content');
- if(content) {
- content.style.visibility = 'visible';
- content.style.opacity = '1';
- }
- }
- document.addEventListener('DOMContentLoaded', setupEventHandlers);
- </script>
- </body>
- </html>
- ]]
- function checkAndLoadVideo()
- local rootFolder = ac.getFolder(ac.FolderID.Root) .. "\\cache\\remote_assets\\nohesivideo"
- local targetFile = rootFolder .. "\\2_90FPS_2.wmv"
- -- Check if the file exists
- if not io.fileExists(targetFile) then
- -- If the file doesn't exist, first download it
- local function onAssetsLoaded(err, folder)
- if err then
- print("Error loading assets: " .. err)
- return
- end
- -- Path to the file to move
- local sourceFile = folder .. "\\2_90FPS_2.wmv"
- -- Create the directory if it doesn't exist
- if not io.dirExists(rootFolder) then
- io.createDir(rootFolder)
- end
- -- Move the file
- if io.move(sourceFile, targetFile) then
- print("File successfully moved to " .. targetFile)
- loadVideoInPlayer(targetFile)
- else
- print("Failed to move the file.")
- end
- end
- -- Start downloading and unpacking the assets
- web.loadRemoteAssets("https://cdn.discordapp.com/attachments/880053607024717854/1209263927045652591/2_90FPS_2.zip?ex=65e649cb&is=65d3d4cb&hm=33f584a885ac8ea30f635cea6c28831a9ea8bf0e17e4799a51597b2bc1b81d00&", onAssetsLoaded)
- else
- -- If the file already exists, load it into MediaPlayer
- loadVideoInPlayer(targetFile)
- end
- end
- ---@param address string Hub address
- function NoHesiClient:initialize(address)
- self.address = address .. "/api/"
- end
- function NoHesiClient:postSettings(Uix, Uiy, Uis, fpsCheck, cpuoCheck, UIToggle, UIClear)
- local payload = string.format("{\"uix\": %s, \"uiy\": %s, \"uis\": %s, \"steamid\": \"%s\", \"fpscheck\": %s, \"cpuocheck\": %s, \"uitoggle\": %s, \"uiclear\": %s}", Uix, Uiy, Uis, ac.getUserSteamID(), fpsCheck, cpuoCheck, UIToggle, UIClear)
- local success, err = pcall(function()
- ac.signBlob(payload, function (data)
- local urlEncodedData = urlEncode(ac.encodeBase64(data))
- local url = self.address .. "settings?data=" .. ac.encodeBase64(payload) .. "&ip=" .. ac.encodeBase64(ac.getServerIP()) .. "&port=" .. ac.encodeBase64(ac.getServerPortTCP()) .. "&steamid=" .. ac.encodeBase64(ac.getUserSteamID()) .. "&serverinfo=" .. ac.encodeBase64(tostring(car.sessionID)..tostring(sim.randomSeed)) .. "&sig=" .. urlEncodedData
- web.post(url, function (err, response)
- ac.debug("err", err)
- end)
- end)
- end)
- if not success then
- ac.debug("Error occurred:", err)
- end
- end
- function NoHesiClient:getSettings(callback)
- local payload = ac.getUserSteamID()
- local success, err = pcall(function()
- ac.signBlob(payload, function (data)
- local urlEncodedData = urlEncode(ac.encodeBase64(data))
- local url = self.address .. "settings?data=" .. ac.encodeBase64(payload) .. "&ip=" .. ac.encodeBase64(ac.getServerIP()) .. "&port=" .. ac.encodeBase64(ac.getServerPortTCP()) .. "&steamid=" .. ac.encodeBase64(ac.getUserSteamID()) .. "&serverinfo=" .. ac.encodeBase64(tostring(car.sessionID)..tostring(sim.randomSeed)) .. "&sig=" .. urlEncodedData
- web.get(url, function (err, response)
- ac.debug("err", err)
- callback(JSON.parse(response.body))
- end)
- end)
- end)
- if not success then
- ac.debug("Error occurred:", err)
- end
- end
- function NoHesiClient:postScore(score, combo, speed, time, distance, callback)
- local payload = string.format("{\"score\": \"%s\", \"combo\": %s, \"avg_speed\": %s, \"run_time\": %s, \"run_distance\": %s, \"steamid\": \"%s\", \"input\": %s, \"car_model\": \"%s\", \"server\": \"%s\"}", score, combo, speed, time, distance, ac.getUserSteamID(), sim.inputMode, ac.getCarID(0):lower(), ac.getServerIP() .. ":" .. ac.getServerPortTCP())
- local success, err = pcall(function()
- ac.signBlob(payload, function (data)
- local urlEncodedData = urlEncode(ac.encodeBase64(data))
- local url = self.address .. "score?data=" .. ac.encodeBase64(payload) .. "&ip=" .. ac.encodeBase64(ac.getServerIP()) .. "&port=" .. ac.encodeBase64(ac.getServerPortTCP()) .. "&steamid=" .. ac.encodeBase64(ac.getUserSteamID()) .. "&serverinfo=" .. ac.encodeBase64(tostring(car.sessionID)..tostring(sim.randomSeed)) .. "&sig=" .. urlEncodedData
- web.post(url, function (err, response)
- ac.debug("err", err)
- callback(JSON.parse(response.body))
- end)
- end)
- end)
- if not success then
- ac.debug("Error occurred:", err)
- end
- end
- function NoHesiClient:getScore(callback)
- local payload = ac.getUserSteamID()
- local success, err = pcall(function()
- ac.signBlob(payload, function (data)
- local urlEncodedData = urlEncode(ac.encodeBase64(data))
- local url = self.address .. "score?data=" .. ac.encodeBase64(payload) .. "&ip=" .. ac.encodeBase64(ac.getServerIP()) .. "&port=" .. ac.encodeBase64(ac.getServerPortTCP()) .. "&steamid=" .. ac.encodeBase64(ac.getUserSteamID()) .. "&serverinfo=" .. ac.encodeBase64(tostring(car.sessionID)..tostring(sim.randomSeed)) .. "&sig=" .. urlEncodedData
- web.get(url, function (err, response)
- ac.debug("err", err)
- callback(JSON.parse(response.body))
- end)
- end)
- end)
- if not success then
- ac.debug("Error occurred:", err)
- end
- end
- function urlEncode(str)
- if str then
- str = string.gsub(str, "\n", "\r\n")
- str = string.gsub(str, "([^%w ])",
- function(c) return string.format("%%%02X", string.byte(c)) end)
- str = string.gsub(str, " ", "+")
- end
- return str
- end
- function script.prepare(dt)
- ac.log('speed' + ac.getCar(1).speedKmh)
- return ac.getCarState(1).speedKmh > 60
- end
- local nohesiclient = NoHesiClient(config.hubUrl)
- local requiredSpeed = 95
- local sim = ac.getSim()
- local maxcombo = 0
- local averageSpeed = 0
- local startdistance = 0
- local rundistance = 0
- local starttime = 0
- local runtime = 0
- local distancedriven = 0
- local timePassed = 0
- local speedMessageTimer = 0
- local mackMessageTimer = 0
- local totalScore = 0
- local comboMeter = 1
- local speedComboMeter = 1
- local proxiComboMeter = 1
- local comboColor = 0
- local dangerouslySlowTimer = 0
- local carsState = {}
- local wheelsWarningTimeout = 0
- local personalBest = 0
- local ownRank = 0
- local cheatcounter = 0
- local leaderboardByName = {}
- local MackMessages = { 'MAAAACK!!!!', 'MACKSAUCE!!', 'You Hesitated....', 'bRUH', 'No Shot...'}
- --new
- local CloseMessages = { 'IN THAT!!!!!', 'IN THERE.', 'D I V E', 'SKRRT!!!' }
- local NormalMessages = { 'Overtake', 'Smooth Move', 'Easy Breeze' }
- local FPS = sim.fps
- local CPUO = sim.cpuOccupancy
- local Uix, Uiy, Uis, fpsCheck, cpuoCheck, UIToggle, UIClear = nil, nil, nil, nil, nil, nil, nil
- local logoCheck = true
- --new
- local previousBestScore = nil
- local previousTotalScore = nil
- local previousComboMeter = nil
- local previousSpeedComboMeter = nil
- local previousProximity = nil
- local previousRank = nil
- local previousMessages = {} -- Для хранения предыдущих сообщений
- local previousSpeedWarningShown = nil -- Добавляем переменную состояния для предупреждения о скорости
- function OptionUI()
- local uiState = ac.getUI()
- ui.text('Settings list:')
- ui.childWindow('##checkyo', vec2(400, 450), ui.WindowFlags.MenuBar,function ()
- if ui.checkbox("Show FPS", fpsCheck) then
- fpsCheck = not fpsCheck
- end
- if ui.checkbox("Show CPU Occupancy", cpuoCheck) then
- cpuoCheck = not cpuoCheck
- end
- if ui.checkbox("Points UI", UIToggle) then
- UIToggle = not UIToggle
- end
- if ui.checkbox("Clear / Dark UI", UIClear) then
- UIClear = not UIClear
- end
- ui.text('Points position:')
- Uix = ui.slider('##slideyyo', Uix, -500, uiState.windowSize.x, 'x-axis: %0.0f')
- Uiy = ui.slider('##slidexyo', Uiy, -500, uiState.windowSize.y, 'y-axis: %0.0f')
- Uis = ui.slider('##slidesyo', Uis, 0, 1, 'Scale: %0.2f')
- if ui.button("☁️ Save Settings", vec2(120, 20)) then
- nohesiclient:postSettings(Uix, Uiy, Uis, fpsCheck, cpuoCheck, UIToggle, UIClear)
- end
- ui.sameLine(160, 0)
- if ui.button("☁️ Load Settings", vec2(120, 20)) then
- nohesiclient:getSettings(function (settings)
- ac.log(settings)
- if settings[1] == nil then
- Uix = uiState.windowSize.y * 0.005
- Uiy = uiState.windowSize.x * 0.0025
- Uis = uiState.windowSize.y * 0.0005
- fpsCheck = false
- cpuoCheck = false
- UIToggle = true
- UIClear = true
- else
- Uix = settings[1].uix
- Uiy = settings[1].uiy
- Uis = settings[1].uis
- fpsCheck = settings[1].fpscheck
- cpuoCheck = settings[1].cpuocheck
- UIToggle = settings[1].uitoggle
- UIClear = settings[1].uiclear
- end
- end)
- end
- if ui.button("Reset", vec2(50, 20)) then
- Uix = uiState.windowSize.y * 0.005
- Uiy = uiState.windowSize.x * 0.0025
- Uis = uiState.windowSize.y * 0.0005
- fpsCheck = false
- cpuoCheck = false
- UIToggle = true
- UIClear = true
- end
- end)
- end
- function OptionUIClosed()
- end
- nohesiclient:getSettings(function (settings)
- local uiState = ac.getUI()
- if settings[1] == nil then
- Uix = uiState.windowSize.y * 0.005
- Uiy = uiState.windowSize.x * 0.0025
- Uis = uiState.windowSize.y * 0.0005
- fpsCheck = false
- cpuoCheck = false
- UIToggle = true
- UIClear = true
- else
- Uix = settings[1].uix
- Uiy = settings[1].uiy
- Uis = settings[1].uis
- fpsCheck = settings[1].fpscheck
- cpuoCheck = settings[1].cpuocheck
- UIToggle = settings[1].uitoggle
- UIClear = settings[1].uiclear
- end
- end)
- nohesiclient:getScore(function (score)
- personalBest = score.score
- ownRank = score.rank
- end)
- local function updateTopScores()
- local newScores = {}
- for name, score in pairs(leaderboardByName) do
- table.insert(newScores, { Name = name, Score = score})
- end
- table.sort(newScores, function (a, b) return a.Score > b.Score end)
- topScores = newScores
- end
- local personalBestEvent = ac.OnlineEvent({
- ac.StructItem.key("overtakePb"),
- score = ac.StructItem.double()
- }, function (sender, message)
- leaderboardByName[ac.getDriverName(sender.index)] = message.score
- updateTopScores()
- end)
- local function broadcastPersonalBest()
- if totalScore > personalBest then
- personalBestEvent({ score = totalScore })
- end
- end
- local function savePersonalBest()
- if totalScore > personalBest then
- personalBest = totalScore
- nohesiclient:postScore(totalScore, maxcombo, averageSpeed, runtime, rundistance, function (score)
- personalBest = score.score
- ownRank = score.rank
- end)
- end
- end
- --updateLeaderboard()
- --setInterval(updateLeaderboard, 60)
- setInterval(broadcastPersonalBest, 1)
- if runtime > 0 then
- setInterval(savePersonalBest, 5)
- end
- local leaderboardSize = vec2(250, 80)
- local uiState = ac.getUI()
- function script.update(dt)
- if timePassed == 0 then
- addMessage('Made by No Hesi', 0)
- end
- local player = ac.getCarState(1)
- if player.engineLifeLeft < 1 then
- ac.console('Overtake score: ' .. totalScore)
- return
- end
- distancedriven = player.distanceDrivenSessionKm
- timePassed = timePassed + dt
- speedMessageTimer = speedMessageTimer + dt
- mackMessageTimer = mackMessageTimer + dt
- local comboFadingRate = 0.5 * math.lerp(1, 0.1, math.lerpInvSat(player.speedKmh, 80, 200)) + player.wheelsOutside
- comboMeter = math.max(1, comboMeter - dt * comboFadingRate)
- speedComboMeter = math.floor(math.lerp(1, 3, math.lerpInvSat(player.speedKmh, 150, 300)) * 10 + 0.5) / 10
- local sim = ac.getSim()
- while sim.carsCount > #carsState do
- carsState[#carsState + 1] = {}
- end
- if wheelsWarningTimeout > 0 then
- wheelsWarningTimeout = wheelsWarningTimeout - dt
- elseif player.wheelsOutside > 0 then
- if wheelsWarningTimeout == 0 then
- end
- addMessage('Car is Out Of Zone', -1)
- wheelsWarningTimeout = 60
- end
- if player.speedKmh < requiredSpeed then
- proxiComboMeter = 1
- if dangerouslySlowTimer > 3 then
- ac.console('Overtake score: ' .. totalScore)
- comboMeter = 1
- totalScore = 0
- else
- if dangerouslySlowTimer < 3 then
- if speedMessageTimer > 5 then
- --addMessage('a'..dangerouslySlowTimer, -1)
- speedMessageTimer = 0
- end
- end
- if dangerouslySlowTimer == 0 then
- addMessage('Speed up!', -1)
- end
- end
- dangerouslySlowTimer = dangerouslySlowTimer + dt
- comboMeter = 1
- if totalScore > personalBest and dangerouslySlowTimer > 3 then
- personalBest = totalScore
- -- ac.sendChatMessage('just scored a ' .. personalBest)
- nohesiclient:postScore(totalScore, maxcombo, averageSpeed, runtime, rundistance, function (score)
- personalBest = score.score
- ownRank = score.rank
- end)
- end
- return
- else
- dangerouslySlowTimer = 0
- end
- if player.collidedWith == 0 then
- if totalScore > personalBest then
- personalBest = totalScore
- -- ac.sendChatMessage('just scored a ' .. personalBest)
- nohesiclient:postScore(totalScore, maxcombo, averageSpeed, runtime, rundistance, function (score)
- personalBest = score.score
- ownRank = score.rank
- end)
- end
- cheatcounter = 0
- comboMeter = 1
- totalScore = 0
- if mackMessageTimer > 1 then
- addMessage(MackMessages[math.random(1, #MackMessages)], -1)
- mackMessageTimer = 0
- end
- end
- if totalScore > 0 then
- rundistance = distancedriven - startdistance
- runtime = timePassed - starttime
- averageSpeed = (rundistance / runtime)*3600
- else
- maxcombo = 0
- averageSpeed = 0
- runtime = 0
- starttime = 0
- rundistance = 0
- startdistance = 0
- proxiComboMeter = 1
- end
- local function get_closest_car_index()
- local k = 0
- local min = math.huge
- for i = 2, ac.getSim().carsCount do
- local car = ac.getCarState(i)
- local closestcarname = ac.getCarID(i-1)
- if not string.find(closestcarname, "traffic") then
- local dist = car.position:distance(player.position)
- if dist < min then
- k = i
- min = dist
- end
- end
- end
- return k
- end
- local closestcarindex = get_closest_car_index()
- local closestcar = ac.getCarState(closestcarindex)
- local closestcarname
- if closestcar then
- closestcarname = ac.getCarID(closestcarindex-1)
- if closestcar.position:closerToThan(player.position, 20) and math.dot(car.look, player.look) > 0.2 then
- local carToPlayer = player.position - closestcar.position
- local dotProduct = closestcar.look:dot(carToPlayer)
- local dotProduct2 = closestcar.side:dot(carToPlayer)
- local t = math.lerpInvSat(math.abs(dotProduct), 19, 6)
- local penaltyFactor = 1 + (t^2 * 4)
- local p = math.lerpInvSat(math.abs(dotProduct)+(math.abs(dotProduct2)*penaltyFactor), 19, 6)
- proxiComboMeter = math.round(math.lerp(1, 4, (-(p * p) + 2 * p)), 1)
- else
- proxiComboMeter = 1
- end
- else
- proxiComboMeter = 1 -- Assuming default value in case of error
- end
- for i = 2, ac.getSim().carsCount do
- local car = ac.getCarState(i)
- local state = carsState[i]
- -- ac.debug(car.collidedWith .. " COLLISION")
- if car.position:closerToThan(player.position, 4.5) then
- local drivingAlong = math.dot(car.look, player.look) > 0.2
- if not drivingAlong then
- state.drivingAlong = false
- if not state.nearMiss and car.position:closerToThan(player.position, 3) then
- state.nearMiss = true
- end
- end
- if not state.overtaken and not state.collided and state.drivingAlong then
- local posDir = (car.position - player.position):normalize()
- local posDot = math.dot(posDir, car.look)
- state.maxPosDot = math.max(state.maxPosDot, posDot)
- if posDot < -0.5 and state.maxPosDot > 0.5 then
- totalScore = totalScore + math.ceil(10 * comboMeter)
- if startdistance == 0 and starttime == 0 then
- startdistance = distancedriven
- starttime = timePassed
- end
- if cheatcounter > 10 then
- setInterval(function() ac.sendChatMessage('debug message from the anti-cheat') end, 1, "cheat")
- physics.engageGear(0, 0)
- physics.setCarNoInput(true)
- physics.teleportCarTo(0, 'PIT')
- totalScore = 0
- nohesiclient:postScore(totalScore, maxcombo, averageSpeed, runtime, rundistance, function (score)
- personalBest = score.score
- ownRank = score.rank
- end)
- setTimeout(function () ac.shutdownAssettoCorsa() end, 5)
- state.overtaken = true
- elseif car.position:closerToThan(player.position, 1.9) then
- cheatcounter = cheatcounter + 1
- state.overtaken = true
- elseif car.position:closerToThan(player.position, 3) then
- comboMeter = comboMeter + (2 + speedComboMeter + proxiComboMeter - 1)
- comboColor = comboColor + math.random(1, 90)
- addMessage(CloseMessages[math.random(#CloseMessages)].. ' ' .. (2 + speedComboMeter + proxiComboMeter - 1) .. 'x', 2)
- state.overtaken = true
- else
- comboMeter = comboMeter + (speedComboMeter + proxiComboMeter - 1)
- comboColor = comboColor + 90
- addMessage(NormalMessages[math.random(#NormalMessages)] .. ' ' .. (speedComboMeter + proxiComboMeter - 1) .. 'x', comboMeter > 50 and 1 or 0)
- state.overtaken = true
- end
- if comboMeter > maxcombo then
- maxcombo = comboMeter
- end
- end
- end
- else
- state.maxPosDot = -1
- state.overtaken = false
- state.collided = false
- state.drivingAlong = true
- state.nearMiss = false
- end
- end
- end
- local messages = {}
- local glitter = {}
- local glitterCount = 0
- function addMessage(text, mood)
- for i = math.min(#messages + 1, 1), 2, -1 do
- messages[i] = messages[i - 1]
- messages[i].targetPos = i
- end
- messages[1] = { text = text, age = 0, targetPos = 1, currentPos = 1, mood = mood }
- if mood == 1 then
- for i = 1, 60 do
- local dir = vec2(math.random() - 0.5, math.random() - 0.5)
- glitterCount = glitterCount + 1
- glitter[glitterCount] = {
- color = rgbm.new(hsv(math.random() * 360, 1, 1):rgb(), 1),
- pos = vec2(80, 140) + dir * vec2(40, 20),
- velocity = dir:normalize():scale(0.2 + math.random()),
- life = 0.5 + 0.5 * math.random()
- }
- end
- end
- end
- local function updateMessages(dt)
- comboColor = comboColor + dt * 10 * comboMeter
- if comboColor > 360 then comboColor = comboColor - 360 end
- for i = 1, #messages do
- local m = messages[i]
- m.age = m.age + dt
- m.currentPos = math.applyLag(m.currentPos, m.targetPos, 0.8, dt)
- end
- end
- local speedWarning = 0
- local fontpath = ''
- web.loadRemoteAssets('https://hub.nohesi.gg/ui/microgramma.zip', function (err, response)
- fontpath = response
- end)
- function loadVideoInPlayer(videoPath)
- player = ui.MediaPlayer()
- player:setSource(videoPath) -- Установите корректный путь к файлу
- :setAutoPlay(true) -- Автоматическое воспроизведение после загрузки
- :setLooping(true) -- Циклическое воспроизведение
- player:setUpdatePeriod(0.0) -- Установка периода обновления
- end
- checkAndLoadVideo()
- function add_suffix(position)
- local last_two_digits = position % 100
- if last_two_digits >= 11 and last_two_digits <= 13 then
- return "th"
- elseif position % 10 == 1 then
- return "st"
- elseif position % 10 == 2 then
- return "nd"
- elseif position % 10 == 3 then
- return "rd"
- else
- return "th"
- end
- end
- browser:navigate('data:text/html;base64,' .. ac.encodeBase64(htmlContent))
- local counter = 0
- -- Time for the image to fully appear in seconds
- local fadeInTime = 2
- -- Time for the image to fully fade out in seconds
- local fadeOutTime = 2
- -- Flag indicating if the animation is currently active
- local isAnimationActive = false -- Изначально анимация не активна
- -- Flag to check if the animation has been started
- local hasAnimationStarted = false
- -- Current time within the animation cycle
- local currentTime = 0
- -- Function to start the animation
- local function startAnimation()
- if not hasAnimationStarted and not ac.getSim().isInMainMenu then
- isAnimationActive = true
- hasAnimationStarted = true
- end
- end
- -- Animation function
- local function animate(dt)
- -- Start the animation based on the condition
- startAnimation()
- -- Stop the animation if it's not active
- if not isAnimationActive then return end
- -- Update the current animation time
- currentTime = currentTime + dt
- local totalTime = fadeInTime + fadeOutTime
- local alpha = 0
- -- Calculate the alpha value based on the current phase of the animation
- if currentTime <= fadeInTime then
- alpha = currentTime / fadeInTime
- elseif currentTime <= totalTime then
- alpha = 1 - (currentTime - fadeInTime) / fadeOutTime
- browser:sendAsync('webanim', "startweb")
- else
- isAnimationActive = false -- Deactivate the animation after completion
- currentTime = 0 -- Reset currentTime to allow the animation to be started again if needed
- end
- -- Set the alpha channel for color modulation
- local color = rgbm(1, 1, 1, alpha)
- -- Render the image with the animated alpha channel
- display.image({
- image = player,
- pos = vec2(0, 0),
- size = vec2(300, 300),
- color = color,
- uvStart = vec2(0, 0),
- uvEnd = vec2(1, 1)
- })
- end
- -- Schedule the animation to stop after the total duration
- setTimeout(function()
- isAnimationActive = false -- Deactivate the animation after completion
- end, fadeInTime + fadeOutTime)
- local function sendDataIfChanged()
- local speedWarningShown = ac.getCar(0).speedKmh <= 90 -- true, если скорость меньше или равна 90 км/ч
- local roundedComboMeter = math.floor(comboMeter * 10 + 0.5) / 10
- if personalBest ~= previousBestScore then
- browser:sendAsync('updateBestScore', personalBest)
- previousBestScore = personalBest
- end
- if totalScore ~= previousTotalScore then
- browser:sendAsync('updateScore', totalScore)
- previousTotalScore = totalScore
- end
- -- Сравниваем округленные значения до десятых
- if roundedComboMeter ~= previousComboMeter then
- browser:sendAsync('updateCombo', roundedComboMeter)
- previousComboMeter = roundedComboMeter
- end
- if speedComboMeter ~= previousSpeedComboMeter then
- browser:sendAsync('updateSpeed', speedComboMeter)
- previousSpeedComboMeter = speedComboMeter
- end
- if proxiComboMeter ~= previousProximity then
- browser:sendAsync('updateProximity', proxiComboMeter)
- previousProximity = proxiComboMeter
- end
- if ownRank ~= previousRank then
- browser:sendAsync('updatePlace', "#"..ownRank)
- previousRank = ownRank
- end
- -- Проверка и отправка состояния предупреждения о скорости
- if speedWarningShown ~= previousSpeedWarningShown then
- browser:sendAsync('speedblock', speedWarningShown and "show" or "notshow")
- previousSpeedWarningShown = speedWarningShown
- end
- for i, m in ipairs(messages) do
- local prevM = previousMessages[i] or {}
- -- Преобразование результатов функции rgbm в соответствующий объект
- local colorComponents = m.mood == 1 and {r=1, g=1, b=1, m=1}
- or m.mood == -1 and {r=1, g=0, b=0, m=1}
- or m.mood == 2 and {r=0.714, g=0.02, b=0.976, m=1}
- or {r=1, g=1, b=1, m=1} -- Упрощаем, всегда используем полную непрозрачность
- -- Проверяем, изменилось ли сообщение или его настроение
- if m.text ~= prevM.text or m.mood ~= prevM.mood then
- browser:sendAsync('messages', {message = m.text, color = colorComponents})
- end
- -- Обновляем предыдущее состояние сообщения
- previousMessages[i] = {text = m.text, mood = m.mood}
- end
- end
- function script.drawUI(dt)
- animate(ac.getScriptDeltaT())
- updateMessages(uiState.dt)
- ui.beginScale()
- ui.beginTransparentWindow('overtakeScore', vec2(Uix, Uiy), vec2(560, 300))
- -- Create an invisible item for spacing
- ui.dummy(vec2(520, 420))
- -- Get coordinates for rendering and draw the browser
- local p1, p2 = ui.itemRect()
- browser:draw(p1, p2, true)
- counter = counter + ac.getScriptDeltaT()
- if counter >= 0.1 then
- sendDataIfChanged()
- counter = 0
- end
- -- Handle mouse input for the browser
- -- nextupdate
- -- local function getMouseButtons()
- -- mouseButtons[1] = uis.isMouseLeftKeyDown
- -- mouseButtons[2] = uis.isMouseRightKeyDown
- -- mouseButtons[3] = uis.isMouseMiddleKeyDown
- -- return mouseButtons
- --end
- -- browser:mouseInput(ui.mouseLocalPos():sub(p1):div(lastSize), getMouseButtons(), uis.mouseWheel, false)
- ui.endTransparentWindow()
- if logoCheck then
- ui.drawImage('https://i.imgur.com/rHHbNc0.png', vec2(100 , uiState.windowSize.y), vec2(0, uiState.windowSize.y - 100))
- end
- ui.beginOutline()
- if fpsCheck then
- ui.setCursorY(0)
- ui.pushFont(ui.Font.Main)
- setInterval(function() FPS = sim.fps end, 0.1, "FPSCounter")
- ui.text(math.floor(FPS) .. " FPS")
- ui.endOutline(rgbm(0, 0, 0, 0.3))
- ui.sameLine(100, 0)
- end
- if cpuoCheck then
- ui.setCursorY(0)
- ui.pushFont(ui.Font.Main)
- setInterval(function() CPUO = sim.cpuOccupancy end, 0.1, "CPUOCounter")
- ui.text(math.floor(CPUO) .. "% CPU Occupancy")
- if fpsCheck then
- ui.sameLine(300, 0)
- else
- ui.sameLine(200, 0)
- end
- end
- ui.endOutline(rgbm(0, 0, 0, 0.3))
- end
- ui.registerOnlineExtra(ui.Icons.Burn, 'No Hesi UI Settings', nil, OptionUI, OptionUIClosed, ui.OnlineExtraFlags.Tool)
Add Comment
Please, Sign In to add comment