View difference between Paste ID: 4x6YG4ST and s3QwHM2i
SHOW: | | - or go back to the newest paste.
1
local config = ac.configValues({
2
  hubUrl = ""
3
})
4
5
---@class NoHesiClient
6
local NoHesiClient = class("NoHesiClient")
7
8
9
local closestcar = 1
10
11
12
-- Import the WebBrowser library
13
local WebBrowser = require('shared/web/browser')
14
ui.setAsynchronousImagesLoading(true)
15
16
-- Configure the WebBrowser
17
WebBrowser.configure({
18
  targetFPS = 240,
19
})
20
21
-- Create an instance of the browser with initial parameters
22
local browser = WebBrowser({
23
  size = vec2(500, 400),  -- Set the size of the browser window
24
  backgroundColor = rgbm(0,0,0,0),  -- Set the background color
25
  directRender = true,  -- Enabl direct rendering
26
  redirectAudio = true,  -- Enabele audio redirection
27
})
28
29
-- Store the last size of the browser window
30
local lastSize = vec2(450, 420)
31
-- Initialize mouse buttons states
32
local mouseButtons = {false, false, false}
33
34
35
function checkAndLoadVideo()
36
local rootFolder = ac.getFolder(ac.FolderID.Root) .. "\\cache\\remote_assets\\nohesivideo"
37
local targetFile = rootFolder .. "\\2_90FPS_2.wmv"
38
print("Checking if target file exists: " .. targetFile)
39
40
-- Check if the file exists
41
if not io.fileExists(targetFile) then
42
    print("File does not exist, starting download process.")
43
    local function onAssetsLoaded(err, folder)
44
        if err then
45
            print("Error loading assets: " .. err)
46
            return
47
        else
48
            print("Assets loaded successfully.")
49
        end
50
51
        -- Path to the file to move
52
        local sourceFile = folder .. "\\2_90FPS_2.wmv"
53
        print("Source file path: " .. sourceFile)
54
55
        -- Create the directory if it doesn't exist
56
        if not io.dirExists(rootFolder) then
57
            print("Target directory does not exist, creating: " .. rootFolder)
58
            io.createDir(rootFolder)
59
        else
60
            print("Target directory already exists.")
61
        end
62
63
        -- Attempt to move the file
64
        print("Attempting to move file from " .. sourceFile .. " to " .. targetFile)
65
        if io.move(sourceFile, targetFile) then
66
            print("File successfully moved to " .. targetFile)
67
            loadVideoInPlayer(targetFile)
68
        else
69
            print("Failed to move the file. Checking permissions and file locks.")
70
        end
71
    end
72
    -- Start downloading and unpacking the assets
73
    print("Initiating download from URL.")
74
    web.loadRemoteAssets("https://cdn.discordapp.com/attachments/880053607024717854/1209263927045652591/2_90FPS_2.zip?ex=65e649cb&is=65d3d4cb&hm=33f584a885ac8ea30f635cea6c28831a9ea8bf0e17e4799a51597b2bc1b81d00&", onAssetsLoaded)
75
else
76
    print("File already exists, loading into MediaPlayer.")
77
    loadVideoInPlayer(targetFile)
78
end
79
end
80
81
82
83
84
local htmlContent = [[
85
<!DOCTYPE html> 
86
<html lang='en'>
87
<head>
88
<meta charset='UTF-8'>
89
<link rel="preconnect" href="https://fonts.googleapis.com">
90
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
91
<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">
92
93
<style>
94
95
96
97
.shake-top {
98
  animation: shake-top 0.8s cubic-bezier(0.455, 0.030, 0.515, 0.955) both;
99
}
100
101
@keyframes shake-top {
102
  0%, 100% {
103
    transform: rotate(0deg);
104
    transform-origin: 50% 0;
105
  }
106
  10% {
107
    transform: rotate(2deg);
108
  }
109
  20%, 40%, 60% {
110
    transform: rotate(-4deg);
111
  }
112
  30%, 50%, 70% {
113
    transform: rotate(4deg);
114
  }
115
  80% {
116
    transform: rotate(-2deg);
117
  }
118
  90% {
119
    transform: rotate(2deg);
120
  }
121
}
122
123
124
125
126
127
128
.blink-2 {
129
  animation: blink-2 0.4s both;
130
}
131
132
@keyframes blink-2 {
133
  0% {
134
    opacity: 1;
135
  }
136
  50% {
137
    opacity: 0.1;
138
  }
139
  100% {
140
    opacity: 1;
141
  }
142
}
143
144
145
146
147
148
149
:root {
150
  --font-family: 'Roboto Flex', sans-serif;
151
  --second-family: 'Roboto', sans-serif;
152
  --main-color: #B130FF; /* Purple color */
153
}
154
155
156
body, html {
157
  height: 100%;
158
  margin: 0;
159
  display: flex;
160
  justify-content: left;
161
  align-items: baseline;
162
  background-color: rgba(0, 0, 0, 0.0)/* Dark background */
163
164
}
165
166
.dashboard {
167
  justify-content: flex-start; /* Это изменение выровняет все содержимое dashboard, включая logo, слева */
168
}
169
170
.logo {
171
  justify-content: flex-start;
172
  width: 100%;
173
}
174
175
176
177
.logo-text {
178
    
179
  margin-left: 10px;
180
  font-size: 24px;
181
  font-weight: 700;
182
  font-family: var(--second-family);
183
}
184
185
.score-section {
186
  display: flex;
187
  align-items: center; /* This will vertically center the items */
188
  margin-top: 20px; /* Adjust as necessary */
189
}
190
191
.score-text {
192
   margin-top: -13px; /* Поднимает элемент на 10 пикселей вверх */
193
  font-family: 'Roboto Flex', sans-serif;
194
  font-weight: 1000; /* Extra Black - Обратите внимание, что максимальный доступный вес может быть 900 для некоторых шрифтов */
195
  font-size: 26px;
196
  line-height: 106%;
197
  letter-spacing: 0.03em;
198
  text-transform: uppercase;
199
  color: #fff;
200
201
  /* Применение настроек переменных шрифтов */
202
  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;
203
}
204
.score-badge {
205
  margin-top: -40px; /* Поднимает элемент вверх */
206
  margin-left: 20px; /* Поднимает элемент вверх */
207
  border: 1.2px solid rgba(255, 255, 255, 0.13); /* Оставляем рамку как есть, если она вам нужна */
208
  border-radius: 4px;
209
  padding: 4px 8px 4px 4px; /* Измененные паддинги */
210
  width: 71px; /* Измененная ширина */
211
  height: 21px; /* Измененная высота */
212
  background: rgba(1, 1, 1, 1); /* Новый цвет фона */
213
  display: flex;
214
  justify-content: center;
215
  align-items: center;
216
  gap: 6px; /* Добавляем пространство между SVG и текстом */
217
}
218
219
.score-badge-text {
220
  font-family: 'Roboto Flex', sans-serif;
221
  font-weight: 740; /* Medium */
222
  font-size: 16px; /* Размер шрифта оставляем как был */
223
  color: rgba(238, 237, 238, 0.87); /* Цвет текста оставляем как был */
224
  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;
225
}
226
227
228
.speed-up {
229
  margin-top: -20px; /* Поднимает элемент на 10 пикселей вверх */
230
  font-family: 'Roboto Flex', sans-serif;
231
  font-weight: 700; /* Bold */
232
  font-stretch: expanded;
233
  font-style: oblique 10deg; /* Для курсива */
234
  font-size: 11px;
235
  line-height: 82%;
236
  text-transform: uppercase;
237
  color: #ff2074;
238
  border: 0.49px solid rgba(255, 0, 95, 0.68);
239
  border-radius: 8px;
240
  padding: 7px 13px;
241
  width: 210px;
242
  height: 15px;
243
  background: rgba(255, 32, 116, 0.15);
244
  display: flex;
245
  justify-content: center;
246
  align-items: center;
247
248
249
  /* Использование переменных шрифтов */
250
  font-variation-settings: 'wdth' 151, 'slnt' -10, 'XTRA' 468, 'XOPQ' 96, 'YOPQ' 79, 'YTLC' 514, 'YTUC' 712, 'YTAS' 750, 'YTDE' -203, 'YTFI' 738;
251
}
252
253
.points-info {
254
  font-weight: 1000; /* Extra Black - убедитесь, что шрифт поддерживает это значение */
255
  font-size: 22px;
256
  line-height: 100%;
257
  text-transform: uppercase;
258
  color: #fff; /* White color */
259
  font-family: 'Roboto Flex', sans-serif;
260
  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;
261
  margin-top: 40px; /* Adjust space from the SPEED UP block */
262
}
263
264
.block-model {
265
display: flex;
266
justify-content: flex-start; /* Выравнивает элементы по левому краю */
267
gap: 9px; /* Использует переменную для расстояния между элементами */
268
margin-top: 10px; /* Отступ сверху */
269
padding-left: var(--padding-left-block-model); /* Если нужен отступ слева для всего контейнера */
270
width: 100%; /* Занимает всю доступную ширину */
271
}
272
273
274
.item {
275
display: flex;
276
flex-direction: column; /* Устанавливает направление элементов в колонку */
277
align-items: center; /* Центрирует элементы по горизонтали */
278
}
279
280
.combo-text {
281
font-weight: 700;
282
font-size: 12px;
283
line-height: 0%;
284
letter-spacing: -0.02em;
285
text-transform: uppercase;
286
text-align: center;
287
color: var(--combo-text-color, rgba(238, 237, 238, 0.68)); /* Значение по умолчанию */
288
font-family: 'Roboto Flex', sans-serif;
289
margin-bottom: 8px; /* Отступ между текстом COMBO и объектом */
290
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;
291
292
}
293
294
295
.object {
296
border: 0.86px solid rgba(255, 255, 255, 0.13);
297
border-radius: 6px;
298
padding: 5px 6px;
299
width: 61px;
300
height: 20px;
301
background: rgba(255, 255, 255, 0.08);
302
display: flex;
303
justify-content: center;
304
align-items: center;
305
}
306
307
.object-text {
308
font-weight: 600;
309
font-size: 14px;
310
line-height: 143%;
311
text-align: center;
312
color: rgba(238, 237, 238, 0.87);
313
font-family: 'Roboto Flex', sans-serif;
314
}
315
316
.speed-warning {
317
color: rgba(255, 32, 116, 1);
318
font-family: 'Roboto Flex', sans-serif;
319
font-size: 14px;
320
font-weight: 700;
321
line-height: 10px;
322
letter-spacing: 0em;
323
text-transform: uppercase; /* Делает весь текст в верхнем регистре */
324
text-align: left; /* Центрирует текст по центру */
325
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;
326
margin-top: 20px; /* Отступ сверху, чтобы отделить текст от объектов */
327
margin-left: 20px; /* Отступ сверху, чтобы отделить текст от объектов */
328
display: block; /* Гарантирует, что элемент будет на новой строке */
329
330
}
331
332
333
334
.content {
335
visibility: hidden; /* Содержимое скрыто, но прогружается */
336
opacity: 0;
337
transition: visibility 0s, opacity 1s linear; /* Плавное появление */
338
transition-delay: 2s; /* Задержка перед началом анимации появления */
339
}
340
341
.logo-text {
342
position: absolute;
343
344
opacity: 0; /* Изначально текст невидим */
345
margin-top: 30px
346
347
}
348
349
350
351
352
</style>
353
</head>
354
<body>
355
356
357
358
</div>
359
</div>
360
</div>
361
<div class="content">
362
<div class='dashboard'>
363
<div class='logo'>
364-
  <svg width='90' height='28' viewBox='0 0 90 28' fill='none' xmlns='http://www.w3.org/2000/svg'>
364+
   <svg width='90' height='40' viewBox='0 0 400 200' fill='none' xmlns='http://www.w3.org/2000/svg'>
365-
      <path d='M12.7547 12.1201V15.4593L12.1611 15.7031L2.91113 6.43064V16.2103L7.57387 20.8441H3.42526L0 17.4321V0.24427L0.592587 0L12.7547 12.1201ZM17.0469 27.4792L17.6395 27.2349V10.047L14.2142 6.6351H10.0903L14.7283 11.2689V21.0485L5.47835 11.7761L4.88477 12.0199V15.359L17.0469 27.4792Z' fill='#B130FF' />
365+
      <path d='M79.212,143.7q-6.688,5.168-9.804,5.168q-1.672,0-2.47-1.634t-0.798-3.534q0-3.648,3.078-10.944t5.89-7.296q1.444,0,2.47,5.7t2.09,8.284q5.168-5.472,14.972-18.24q12.312-16.188,16.34-17.708q1.368-0.532,2.812-0.532t2.394,0.608t0.95,1.52q0,1.292-2.204,1.52q-2.66,1.444-8.208,6.992q-5.548,5.396-8.892,10.108q-8.132,11.324-16.72,18.468q4.256,6.308,13.376,6.308q0.988,0,2.964-0.152q21.128-1.976,29.944-18.316q3.572-6.536,3.572-12.92t-4.636-11.476q-3.724-4.18-8.094-5.168t-8.93-0.988q-11.704,0-21.584,5.396q-10.412,5.7-10.412,12.768q0,1.9,1.026,3.116t1.406,1.368l-0.456,0q-1.976-0.38-3.496-1.976q-1.672-1.976-1.672-4.18q0-7.6,9.538-13.072t22.382-5.472q5.928,0,12.16,1.368t11.324,6.536q5.852,6.08,5.852,13.756q0,5.7-3.344,11.78q-5.776,10.488-17.252,15.352q-9.196,3.876-19.836,3.876t-15.732-6.384z M68.876,143.396q0,2.356,0.912,4.104q3.268-1.444,7.904-6.004q-2.204-3.648-2.774-5.738t-0.798-5.51q-5.244,7.524-5.244,13.148z M79.744,122.116q0.076,0,0.152-0.076q0,0.152-0.038,0.152t-0.114-0.076z M207.36092,105.092q-0.38,0-8.132-1.216q-12.844,7.296-29.45,18.696t-26.866,21.052q11.628-1.292,23.636-2.356q1.368-0.076,3.952-0.076q9.5,0,14.972,1.824t5.472,4.864q0,1.824-2.204,4.028q-2.888,2.66-3.952,2.66q-0.38,0-0.38-0.646t0.874-1.444t0.874-1.482q0-2.356-7.068-4.332q-7.372-2.128-13.832-2.128t-15.01,1.254t-13.49,1.254l-0.532,0q-1.216,1.064-1.52,1.064q-0.152,0-0.152-0.266t0.456-1.026q-2.812-0.152-2.812-1.596q0-1.14,1.216-2.356t2.128-1.216q0.152,0,0.304,0.152q0.38,0.608,3.268,1.064q19.228-20.064,55.1-39.748l-2.28-0.456q-5.396-1.216-13.908-1.216t-14.82,2.28q-1.368,0.304-4.104,1.672q-4.408,2.204-4.408,5.244q0,0.456,0.19,1.102t-0.038,1.026q-0.304,0.456-1.026,0.456t-1.368-1.064t-0.646-2.28t0.38-2.508q1.444-4.864,9.348-7.144q4.028-1.14,13.49-1.14t18.734,1.672q1.14,0.076,3.8,0.684l4.18-2.28q0.608-0.304,2.66-0.304t2.052,0.76q0,0.304-0.684,0.532q-0.304,0.228-3.876,2.204q3.42,0.228,4.218,0.228t1.444-0.38t0.456-1.14t0.114-0.76q0.456,0,1.102,1.026t0.646,1.862q0,1.9-2.508,1.9z M257.98984,113.072q-2.812,1.14-4.826,1.14t-2.014-0.836q0-0.532,2.242-0.608t3.23-0.532q2.888-1.52,2.888-4.484q0-1.52-0.836-3.192q-2.432-4.332-11.78-4.332q-5.396,0-10.792,1.634t-9.044,5.016t-3.648,6.27q0,4.408,8.74,6.46l8.968,1.672q5.396,0.988,8.056,2.736q3.496,2.128,3.496,6.08q0,1.596-0.532,3.42q-2.432,8.208-12.54,12.54q-8.436,3.724-19.912,3.724q-3.268,0-5.7-0.304q2.432,1.368,6.384,2.356q0.304,0.076,0.304,0.304q0,0.684-1.596,0.684q-3.116,0-7.752-3.724q-6.308-0.988-9.424-3.496q-1.976-1.596-3.648-4.712t-1.672-5.624t0.988-4.18q2.28-4.028,5.776-4.028q2.66,0,3.116,4.484q1.064,8.664,1.444,9.88q1.14,3.724,3.876,6.308q3.192,0.684,6.916,0.684q8.208,0,16.112-3.268t11.4-9.272q1.444-2.356,1.444-4.18q0-3.572-6.308-5.548l-13.376-3.04q-4.104-0.988-6.574-3.268t-2.47-5.244q0-4.56,5.7-9.12q4.332-3.496,10.602-5.434t12.312-1.938t9.994,1.748q5.092,2.356,5.092,6.878t-4.636,6.346z M209.04584,146.968q-4.408-4.864-5.624-13.3q-0.684-4.788-1.292-4.788t-1.14,0.342t-1.064,1.482t-0.532,2.964q0,5.852,4.104,10.032q2.052,2.128,5.548,3.268z M260.20676,140.508q0-8.208,4.864-16.264q-5.624-3.572-5.624-10.564q0-8.436,7.448-14.972q8.36-7.372,21.584-7.372q6.08,0,13.072,1.748q1.596,0.38,13.604,5.89t17.252,5.51q3.496,0,5.244-1.976q0.912-0.988,0.912-2.128t-0.988-2.052q-0.532-0.76-0.532-0.988t0.418-0.228t1.444,1.33t1.026,3.116t-2.394,3.42t-6.042,1.634q-5.7,0-17.708-5.51t-15.086-6.118t-7.03-0.608q-15.732,0-24.776,10.184q-1.064,1.14-2.508,4.598t-1.444,6.65q0,4.408,3.116,6.84q3.952-5.776,9.348-9.196t10.944-3.42q6.84,0,6.84,5.32q0,6.384-7.486,13.87t-13.87,7.486q-4.104,0-4.104-3.496q0-1.216,0.646-2.128t1.558-0.684l-0.152,1.748q0,1.14,3.496,1.14q5.168,0,10.906-5.32t6.954-10.412q0.304-1.14,0.304-2.052q0-3.42-4.37-3.42t-9.044,3.344t-8.778,8.816q0.304,0.152,2.356,0.608l4.56,0.152q3.344,0.152,3.344,0.76q0,1.064-3.192,1.064l-0.456,0q-4.636,0-7.6-1.14q-5.852,8.968-5.852,16.492q0,9.728,7.524,9.728q8.512,0,17.708-11.856l14.896-22.192q8.36-12.16,14.516-12.464l2.888,0.684q1.368,0.76,1.368,2.28t-0.76,1.52l-2.432-0.38q-5.244,0-13.452,11.324l-15.124,21.128q-9.728,11.324-18.62,11.324q-6.08,0-8.398-3.268t-2.318-9.5z' fill='#B130FF' />
366-
      <path d='M87.2604 18.2395V9.70853H89.856V18.2395H87.2604Z' fill='#B130FF' />
366+
367-
      <path d='M75.7939 15.9837L75.7646 15.5091H78.0614L78.1024 15.7435C78.1688 16.1224 78.4208 16.388 78.8583 16.5403C79.2958 16.6927 80.0164 16.7689 81.0203 16.7689C81.989 16.7689 82.6707 16.6907 83.0652 16.5345C83.4636 16.3743 83.6628 16.1556 83.6628 15.8783C83.6628 15.6439 83.4558 15.4681 83.0417 15.3509C82.6277 15.2337 81.7 15.0931 80.2586 14.9291C78.6415 14.7259 77.5204 14.4076 76.8954 13.974C76.2705 13.5404 75.958 12.9565 75.958 12.2221C75.958 11.3706 76.3544 10.7104 77.1474 10.2417C77.9403 9.76907 79.1219 9.53275 80.6922 9.53275C82.3093 9.53275 83.53 9.73977 84.3542 10.1538C85.1784 10.564 85.6256 11.187 85.696 12.0229L85.7194 12.3276H83.4519L83.4167 12.1811C83.3699 11.9155 83.1648 11.6948 82.8015 11.519C82.4382 11.3393 81.7488 11.2495 80.7332 11.2495C79.8739 11.2495 79.2704 11.3198 78.9227 11.4604C78.579 11.601 78.4071 11.7866 78.4071 12.017C78.4071 12.2084 78.5985 12.3686 78.9813 12.4975C79.368 12.6264 80.3074 12.7729 81.7996 12.9369C83.3777 13.1362 84.489 13.4076 85.1335 13.7514C85.778 14.0951 86.1002 14.6888 86.1002 15.5326C86.1002 16.4544 85.6764 17.1653 84.8288 17.6653C83.9851 18.1653 82.7332 18.4153 81.073 18.4153C79.409 18.4153 78.1259 18.2161 77.2236 17.8176C76.3251 17.4192 75.8486 16.8079 75.7939 15.9837Z' fill='#B130FF' />
367+
368-
      <path d='M66.2493 18.2395V9.70853H74.8682V11.6596H68.6398V13.1186H74.3116V14.7826H68.6398V16.3704H74.9736V18.2395H66.2493Z' fill='#B130FF' />
368+
369-
      <path d='M54.2848 18.2395V9.70853H56.8453V12.7612H61.6967V9.70853H64.2572V18.2395H61.6967V14.8998H56.8453V18.2395H54.2848Z' fill='#B130FF' />
369+
370-
      <path d='M36.4553 13.8686V13.9623C36.4553 12.5014 37.0509 11.3979 38.2423 10.6519C39.4337 9.90578 40.9083 9.53275 42.666 9.53275C44.4199 9.53275 45.8964 9.90578 47.0956 10.6519C48.2947 11.3979 48.8943 12.5014 48.8943 13.9623V13.8686C48.8943 15.388 48.2947 16.5267 47.0956 17.2845C45.8964 18.0383 44.4199 18.4153 42.666 18.4153C40.9122 18.4153 39.4376 18.0383 38.2423 17.2845C37.0509 16.5267 36.4553 15.388 36.4553 13.8686ZM39.1329 13.9037C39.1329 14.8295 39.4571 15.5267 40.1055 15.9954C40.7579 16.4603 41.6153 16.6985 42.6777 16.7103C43.7324 16.7259 44.5859 16.4935 45.2382 16.013C45.8905 15.5326 46.2167 14.8334 46.2167 13.9154V13.9623C46.2167 13.0873 45.8905 12.4291 45.2382 11.9877C44.5898 11.5424 43.7363 11.312 42.6777 11.2964C41.6074 11.2846 40.7481 11.5073 40.0997 11.9643C39.4552 12.4213 39.1329 13.0854 39.1329 13.9564V13.9037Z' fill='#B130FF' />
370+
371-
      <path d='M25.5982 18.2395V9.70853H28.0766L32.7874 14.8822C32.846 14.9408 32.8929 14.9857 32.928 15.017C32.9671 15.0482 33.0003 15.0795 33.0276 15.1107H33.0569C33.0491 15.0248 33.0433 14.9564 33.0394 14.9056C33.0394 14.8549 33.0394 14.7865 33.0394 14.7006V9.70853H35.1955V18.2395H32.7933L28.0356 12.9955C27.9731 12.9291 27.9204 12.8784 27.8774 12.8432C27.8383 12.8041 27.8012 12.7612 27.7661 12.7143H27.7426C27.7465 12.7573 27.7485 12.8002 27.7485 12.8432C27.7524 12.8823 27.7544 12.9174 27.7544 12.9487L27.7661 18.2395H25.5982Z' fill='#B130FF' />
371+
372
  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
373
    <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" />
374
  </svg>
375
  <span class='score-badge-text'>#1</span>
376
</div>
377
378
  </div>
379
</div>
380
<!-- Новый элемент для SPEED UP -->
381
<div class='speed-up'>SPEED UP!</div>
382
<div class='points-info' id='pointsDisplay'>PB 0 PTS</div>
383
<div class='block-model'>
384
<div class='item'>
385
  <p class='combo-text'>COMBO</p>
386
  <div class='object'>
387
    <div class='object-text' style='color: rgba(255, 214, 67, 0.87);'>0x</div>
388
  </div>
389
</div>
390
<div class='item'>
391
  <p class='combo-text'>SPEED</p>
392
  <div class='object'>
393
    <div class='object-text' style='color: rgba(106, 255, 103, 0.87);'>0x</div> <!-- Пример другого цвета -->
394
  </div>
395
</div>
396
<div class='item'>
397
  <p class='combo-text'>Proximity</p>
398
  <div class='object'>
399
    <div class='object-text' style='color: rgba(238, 237, 238, 0.87);'>0x</div> <!-- И еще один цвет -->
400
  </div>
401
402
  
403
</div>
404
405
</div>
406
<p class='speed-warning'>KEEP SPEED ABOVE 95KM/H</p>
407
</div>
408
</div>
409
</div>
410
411
<script>
412
// Функция для плавного обновления числа
413
// Функция для плавного обновления числа с поддержкой условного форматирования текста
414
// Универсальная функция анимации для обновления числовых значений
415
function animateValueUpdate({ selector, oldValue, newValue, isPB = false, precision = 0, unit }) {
416
  const duration = 700;
417
  const start = performance.now();
418
419
  function updateNumber(timestamp) {
420
    const elapsedTime = timestamp - start;
421
    const progress = Math.min(elapsedTime / duration, 1);
422
    const currentNumber = oldValue + (newValue - oldValue) * progress;
423
    // Использование локали 'en-US' для формата с запятой как разделителем тысяч
424
    const formattedNumber = currentNumber.toLocaleString('en-US', { minimumFractionDigits: precision, maximumFractionDigits: precision });
425
    const displayValue = isPB ? `PB: ${formattedNumber} ${unit}` : `${formattedNumber}${unit}`;
426
427
    document.querySelector(selector).textContent = displayValue;
428
429
    if (progress < 1) {
430
      requestAnimationFrame(updateNumber);
431
    }
432
  }
433
434
  requestAnimationFrame(updateNumber);
435
}
436
437
// Обработка обновления различных значений
438
function updateValue(type, newValue) {
439
  const selectorMap = {
440
    bestScore: '.score-text',
441
    currentScore: '#pointsDisplay',
442
    combo: '.item:nth-child(1) .object-text',
443
    speed: '.item:nth-child(2) .object-text',
444
    proximity: '.item:nth-child(3) .object-text'
445
  };
446
447
  const isPB = type === 'bestScore';
448
  const selector = selectorMap[type];
449
  const currentValueText = document.querySelector(selector)?.textContent.replace(/[^\d.-]/g, '') || "0";
450
  const currentValue = parseFloat(currentValueText);
451
  const precision = ['combo', 'speed', 'proximity'].includes(type) ? 1 : 0;
452
  const unit = ['combo', 'speed', 'proximity'].includes(type) ? "x" : "PTS"; // Удаление пробела для X
453
454
  animateValueUpdate({ selector, oldValue: currentValue, newValue: parseFloat(newValue), isPB, precision, unit });
455
}
456
457
458
// Очередь для хранения данных обновлений
459
const updateQueue = [];
460
let isAnimating = false; // Флаг, показывающий, идет ли в данный момент анимация
461
462
function processNextUpdate() {
463
  if (updateQueue.length === 0 || isAnimating) {
464
    return; // Выходим, если анимация уже идёт или нет заданий в очереди
465
  }
466
467
  isAnimating = true; // Устанавливаем флаг анимации
468
  const data = updateQueue.shift(); // Извлекаем первый элемент очереди
469
470
  const speedUpElement = document.querySelector('.speed-up');
471
  if (!speedUpElement) {
472
    console.error("Элемент для обновления сообщения о ускорении не найден.");
473
    isAnimating = false;
474
    processNextUpdate(); // Пытаемся обработать следующий элемент в очереди
475
    return;
476
  }
477
478
  // Удаляем предыдущие классы анимации и добавляем новый
479
  speedUpElement.classList.remove('blink-2', 'shake-top');
480
  const animationClass = data.color.r === 1 && data.color.g === 0 && data.color.b === 0 ? 'shake-top' : 'blink-2';
481
  speedUpElement.classList.add(animationClass);
482
483
  // Изменяем цвет в середине анимации blink-2
484
  setTimeout(() => {
485
    speedUpElement.style.background = `rgba(${data.color.r * 255}, ${data.color.g * 255}, ${data.color.b * 255}, 0.2)`;
486
    speedUpElement.style.border = `0.49px solid rgba(${data.color.r * 255}, ${data.color.g * 255}, ${data.color.b * 255}, 0.68)`;
487
    speedUpElement.style.color = `rgba(${data.color.r * 255}, ${data.color.g * 255}, ${data.color.b * 255}, 1)`;
488
  }, 200); // 200ms для достижения 50% анимации blink-2
489
490
  // Обновляем текст в середине анимации
491
  setTimeout(() => {
492
    speedUpElement.textContent = data.message;
493
  }, 200); // Обновляем текст одновременно с изменением цвета
494
495
  // Завершаем анимацию и удаляем классы анимации с небольшой задержкой после полного цикла
496
  setTimeout(() => {
497
    speedUpElement.classList.remove('blink-2', 'shake-top');
498
    isAnimating = false;
499
    processNextUpdate(); // Обрабатываем следующее обновление в очереди
500
  }, 400); // Полная длительность анимации blink-2
501
}
502
503
function updateSpeedUpMessage(data) {
504
  updateQueue.push(data); // Добавляем данные в очередь
505
  if (!isAnimating) {
506
    processNextUpdate(); // Запускаем обработку очереди, если анимация не активна
507
  }
508
}
509
510
511
512
513
514
515
516
517
518
function updatePlace(newPlace) {
519
console.log("updatePlace called with:", newPlace); // Отладочная информация
520
521
// Поиск элемента, содержащего текст места
522
const placeElement = document.querySelector('.score-badge-text');
523
if (!placeElement) {
524
  console.error("Элемент для обновления места не найден.");
525
  return;
526
}
527
528
// Обновление текста элемента
529
placeElement.textContent = `${newPlace}`;
530
}
531
532
function updateBadgeStyle() {
533
const badge = document.querySelector('.score-badge');
534
const badgeText = badge.querySelector('.score-badge-text');
535
const svg = badge.querySelector('svg');
536
537
// Определение стилей для разных позиций
538
const styleConfigs = {
539
  1: {
540
    backgroundColor: 'rgba(221, 3, 85, 1)',
541
    textColor: 'rgba(8, 6, 10, 1)',
542
    logoColor: 'rgba(8, 6, 10, 1)',
543
    fontSize: '16px'
544
  },
545
  2: {
546
    backgroundColor: 'rgba(238, 237, 238, 0.87)',
547
    textColor: 'rgba(8, 6, 10, 1)',
548
    logoColor: 'rgba(8, 6, 10, 1)',
549
    fontSize: '16px'
550
  },
551
  3: {
552
    backgroundColor: 'rgba(255, 129, 38, 1)',
553
    textColor: 'rgba(8, 6, 10, 1)',
554
    logoColor: 'rgba(8, 6, 10, 1)',
555
    fontSize: '16px'
556
  },
557
  default: {
558
    backgroundColor: 'rgba(255, 255, 255, 0.17)',
559
    textColor: 'rgba(238, 237, 238, 0.87)',
560
    logoColor: 'rgba(238, 237, 238, 0.52)',
561
    fontSize: ['16px', '14px', '12px'] // Используйте динамическое определение размера шрифта
562
  }
563
};
564
565
const position = parseInt(badgeText.textContent.replace(/[^\d]/g, '')) || 0;
566
const config = styleConfigs[position] || styleConfigs.default;
567
568
// Динамическое определение размера шрифта на основе позиции
569
const fontSizeIndex = position < 100 ? 0 : position < 1000 ? 1 : 2;
570
const finalFontSize = config.fontSize instanceof Array ? config.fontSize[fontSizeIndex] : config.fontSize;
571
572
// Применение стилей
573
badge.style.background = config.backgroundColor;
574
badgeText.style.color = config.textColor;
575
badgeText.style.fontSize = finalFontSize;
576
svg.querySelectorAll('path').forEach(path => path.setAttribute('fill', config.logoColor));
577
}
578
579
580
// Функция для управления видимостью предупреждения о скорости
581
function speedWarningDisplay(show) {
582
const speedWarningElement = document.querySelector('.speed-warning');
583
if (!speedWarningElement) return; // Выходим, если элемент не найден
584
585
// Устанавливаем свойство display в зависимости от аргумента show
586
speedWarningElement.style.display = show ? 'block' : 'none';
587
}
588
589
590
591
// Обработчик события, получающий данные от AC
592
AC.onReceive('speedblock', arg => {
593
// Проверяем значение arg, чтобы определить, показывать или скрывать предупреждение
594
const shouldShow = arg === 'show'; // Пример условия, arg должен быть 'show' для отображения
595
speedWarningDisplay(shouldShow);
596
});
597
598
599
600
601
function setupEventHandlers() {
602
AC.onReceive('updateBestScore', newValue => updateValue('bestScore', newValue));
603
AC.onReceive('updateScore', newValue => updateValue('currentScore', newValue));
604
AC.onReceive('updateCombo', newValue => updateValue('combo', newValue));
605
AC.onReceive('updateSpeed', newValue => updateValue('speed', newValue));
606
AC.onReceive('updateProximity', newValue => updateValue('proximity', newValue));
607
AC.onReceive('messages', updateSpeedUpMessage);
608
AC.onReceive('speedblock', arg => speedWarningDisplay(arg === 'show'));
609
AC.onReceive('updatePlace', newPlace => {
610
  updatePlace(newPlace);
611
  updateBadgeStyle(); // Вызов для обновления стилей после изменения места
612
});
613
614
AC.onReceive('webanim', arg => {
615
  if(arg === "startweb") {
616
      showContent();
617
  }
618
});
619
}
620
621
function showContent() {
622
const content = document.querySelector('.content');
623
if(content) {
624
  content.style.visibility = 'visible';
625
  content.style.opacity = '1';
626
}
627
}
628
629
document.addEventListener('DOMContentLoaded', setupEventHandlers);
630
631
632
</script>
633
634
635
636
637
</body>
638
</html>
639
]]
640
641
642
643
function checkAndLoadVideo()
644
local rootFolder = ac.getFolder(ac.FolderID.Root) .. "\\cache\\remote_assets\\nohesivideo"
645
local targetFile = rootFolder .. "\\2_90FPS_2.wmv"
646
-- Check if the file exists
647
if not io.fileExists(targetFile) then
648
    -- If the file doesn't exist, first download it
649
    local function onAssetsLoaded(err, folder)
650
        if err then
651
            print("Error loading assets: " .. err)
652
            return
653
        end
654
655
        -- Path to the file to move
656
        local sourceFile = folder .. "\\2_90FPS_2.wmv"
657
658
        -- Create the directory if it doesn't exist
659
        if not io.dirExists(rootFolder) then
660
            io.createDir(rootFolder)
661
        end
662
663
        -- Move the file
664
        if io.move(sourceFile, targetFile) then
665
            print("File successfully moved to " .. targetFile)
666
            loadVideoInPlayer(targetFile)
667
        else
668
            print("Failed to move the file.")
669
        end
670
    end
671
    -- Start downloading and unpacking the assets
672
    web.loadRemoteAssets("https://cdn.discordapp.com/attachments/880053607024717854/1209263927045652591/2_90FPS_2.zip?ex=65e649cb&is=65d3d4cb&hm=33f584a885ac8ea30f635cea6c28831a9ea8bf0e17e4799a51597b2bc1b81d00&", onAssetsLoaded)
673
else
674
    -- If the file already exists, load it into MediaPlayer
675
    loadVideoInPlayer(targetFile)
676
end
677
end
678
679
680
---@param address string Hub address
681
function NoHesiClient:initialize(address)
682
  self.address = address .. "/api/"
683
end
684
685
function NoHesiClient:postSettings(Uix, Uiy, Uis, fpsCheck, cpuoCheck, UIToggle, UIClear)
686
  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)
687
  local success, err = pcall(function()
688
      ac.signBlob(payload, function (data)
689
          local urlEncodedData = urlEncode(ac.encodeBase64(data))
690
          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
691
          web.post(url, function (err, response)
692
              ac.debug("err", err)
693
          end)
694
      end)
695
  end)
696
  if not success then
697
      ac.debug("Error occurred:", err)
698
  end
699
end
700
701
function NoHesiClient:getSettings(callback)
702
  local payload = ac.getUserSteamID()
703
  local success, err = pcall(function()
704
      ac.signBlob(payload, function (data)
705
          local urlEncodedData = urlEncode(ac.encodeBase64(data))
706
          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
707
          web.get(url, function (err, response)
708
              ac.debug("err", err)
709
              callback(JSON.parse(response.body))
710
          end)
711
      end)
712
  end)
713
714
  if not success then
715
      ac.debug("Error occurred:", err)
716
  end
717
end
718
719
function NoHesiClient:postScore(score, combo, speed, time, distance, callback)
720
  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())
721
  local success, err = pcall(function()
722
      ac.signBlob(payload, function (data)
723
          local urlEncodedData = urlEncode(ac.encodeBase64(data))
724
          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
725
          web.post(url, function (err, response)
726
              ac.debug("err", err)
727
              callback(JSON.parse(response.body))
728
          end)
729
      end)
730
  end)
731
732
  if not success then
733
      ac.debug("Error occurred:", err)
734
  end
735
end
736
737
function NoHesiClient:getScore(callback)
738
  local payload = ac.getUserSteamID()
739
  local success, err = pcall(function()
740
      ac.signBlob(payload, function (data)
741
          local urlEncodedData = urlEncode(ac.encodeBase64(data))
742
          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
743
          web.get(url, function (err, response)
744
              ac.debug("err", err)
745
              callback(JSON.parse(response.body))
746
          end)
747
      end)
748
  end)
749
750
  if not success then
751
      ac.debug("Error occurred:", err)
752
  end
753
end
754
755
function urlEncode(str)
756
  if str then
757
    str = string.gsub(str, "\n", "\r\n")
758
    str = string.gsub(str, "([^%w ])",
759
      function(c) return string.format("%%%02X", string.byte(c)) end)
760
    str = string.gsub(str, " ", "+")
761
  end
762
  return str
763
end
764
765
function script.prepare(dt)
766
ac.log('speed' + ac.getCar(1).speedKmh)
767
return ac.getCarState(1).speedKmh > 60
768
end
769
770
local nohesiclient = NoHesiClient(config.hubUrl)
771
local requiredSpeed = 95
772
local sim = ac.getSim()
773
774
local maxcombo = 0
775
local averageSpeed = 0
776
local startdistance = 0
777
local rundistance = 0
778
local starttime = 0
779
local runtime = 0
780
local distancedriven = 0
781
local timePassed = 0
782
local speedMessageTimer = 0
783
local mackMessageTimer = 0
784
local totalScore = 0
785
local comboMeter = 1
786
local speedComboMeter = 1
787
local proxiComboMeter = 1
788
local comboColor = 0
789
local dangerouslySlowTimer = 0
790
local carsState = {}
791
local wheelsWarningTimeout = 0
792
local personalBest = 0
793
local ownRank = 0
794
local cheatcounter = 0
795
local leaderboardByName = {}
796
local MackMessages = { 'MAAAACK!!!!', 'MACKSAUCE!!', 'You Hesitated....', 'bRUH', 'No Shot...'}
797
--new
798
local CloseMessages = { 'IN THAT!!!!!', 'IN THERE.', 'D I V E', 'SKRRT!!!' }
799
local NormalMessages = { 'Overtake', 'Smooth Move', 'Easy Breeze' }
800
local FPS = sim.fps
801
local CPUO = sim.cpuOccupancy
802
local Uix, Uiy, Uis, fpsCheck, cpuoCheck, UIToggle, UIClear = nil, nil, nil, nil, nil, nil, nil
803
local logoCheck = true
804
--new
805
local previousBestScore = nil
806
local previousTotalScore = nil
807
local previousComboMeter = nil
808
local previousSpeedComboMeter = nil
809
local previousProximity = nil
810
local previousRank = nil
811
local previousMessages = {} -- Для хранения предыдущих сообщений
812
local previousSpeedWarningShown = nil -- Добавляем переменную состояния для предупреждения о скорости
813
814
815
function OptionUI()
816
  local uiState = ac.getUI()
817
  ui.text('Settings list:')
818
  ui.childWindow('##checkyo', vec2(400, 450), ui.WindowFlags.MenuBar,function ()
819
      if ui.checkbox("Show FPS", fpsCheck) then
820
          fpsCheck = not fpsCheck
821
      end
822
      if ui.checkbox("Show CPU Occupancy", cpuoCheck) then
823
          cpuoCheck = not cpuoCheck
824
      end
825
      if ui.checkbox("Points UI", UIToggle) then
826
          UIToggle = not UIToggle
827
      end
828
      if ui.checkbox("Clear / Dark UI", UIClear) then
829
          UIClear = not UIClear
830
      end
831
832
      ui.text('Points position:')
833
      Uix = ui.slider('##slideyyo', Uix, -500, uiState.windowSize.x, 'x-axis: %0.0f')
834
      Uiy = ui.slider('##slidexyo', Uiy, -500, uiState.windowSize.y, 'y-axis: %0.0f')
835
      Uis = ui.slider('##slidesyo', Uis, 0, 1, 'Scale: %0.2f')
836
      if ui.button("☁️ Save Settings", vec2(120, 20)) then
837
          nohesiclient:postSettings(Uix, Uiy, Uis, fpsCheck, cpuoCheck, UIToggle, UIClear)
838
      end
839
      ui.sameLine(160, 0)
840
      if ui.button("☁️ Load Settings", vec2(120, 20)) then
841
          nohesiclient:getSettings(function (settings)
842
              ac.log(settings)
843
              if settings[1] == nil then
844
                  Uix = uiState.windowSize.y * 0.005
845
                  Uiy = uiState.windowSize.x * 0.0025
846
                  Uis = uiState.windowSize.y * 0.0005
847
                  fpsCheck = false
848
                  cpuoCheck = false
849
                  UIToggle = true
850
                  UIClear = true
851
              else
852
                  Uix = settings[1].uix
853
                  Uiy = settings[1].uiy
854
                  Uis = settings[1].uis
855
                  fpsCheck = settings[1].fpscheck
856
                  cpuoCheck = settings[1].cpuocheck
857
                  UIToggle = settings[1].uitoggle
858
                  UIClear = settings[1].uiclear
859
              end
860
          end)
861
      end
862
      if ui.button("Reset", vec2(50, 20)) then
863
          Uix = uiState.windowSize.y * 0.005
864
          Uiy = uiState.windowSize.x * 0.0025
865
          Uis = uiState.windowSize.y * 0.0005
866
          fpsCheck = false
867
          cpuoCheck = false
868
          UIToggle = true
869
          UIClear = true
870
      end
871
  end)
872
end
873
874
function OptionUIClosed()
875
876
end
877
878
nohesiclient:getSettings(function (settings)
879
  local uiState = ac.getUI()
880
  if settings[1] == nil then
881
      Uix = uiState.windowSize.y * 0.005
882
      Uiy = uiState.windowSize.x * 0.0025
883
      Uis = uiState.windowSize.y * 0.0005
884
      fpsCheck = false
885
      cpuoCheck = false
886
      UIToggle = true
887
      UIClear = true
888
  else
889
      Uix = settings[1].uix
890
      Uiy = settings[1].uiy
891
      Uis = settings[1].uis
892
      fpsCheck = settings[1].fpscheck
893
      cpuoCheck = settings[1].cpuocheck
894
      UIToggle = settings[1].uitoggle
895
      UIClear = settings[1].uiclear
896
  end
897
end)
898
899
nohesiclient:getScore(function (score)
900
  personalBest = score.score
901
  ownRank = score.rank
902
end)
903
904
local function updateTopScores()
905
local newScores = {}
906
for name, score in pairs(leaderboardByName) do
907
  table.insert(newScores, { Name = name, Score = score})
908
end
909
table.sort(newScores, function (a, b) return a.Score > b.Score end)
910
topScores = newScores
911
end
912
913
local personalBestEvent = ac.OnlineEvent({
914
ac.StructItem.key("overtakePb"),
915
score = ac.StructItem.double()
916
}, function (sender, message)
917
leaderboardByName[ac.getDriverName(sender.index)] = message.score
918
updateTopScores()
919
end)
920
921
local function broadcastPersonalBest()
922
  if totalScore > personalBest then
923
      personalBestEvent({ score = totalScore })
924
  end
925
end
926
927
local function savePersonalBest()
928
  if totalScore > personalBest then
929
      personalBest = totalScore
930
      nohesiclient:postScore(totalScore, maxcombo, averageSpeed, runtime, rundistance, function (score)
931
          personalBest = score.score
932
          ownRank = score.rank
933
      end)
934
  end
935
end
936
937
--updateLeaderboard()
938
--setInterval(updateLeaderboard, 60)
939
setInterval(broadcastPersonalBest, 1)
940
if runtime > 0 then
941
  setInterval(savePersonalBest, 5)
942
end
943
944
local leaderboardSize = vec2(250, 80)
945
946
local uiState = ac.getUI()
947
948
function script.update(dt)
949
if timePassed == 0 then
950
  addMessage('Made by No Hesi', 0)
951
end
952
953
local player = ac.getCarState(1)
954
if player.engineLifeLeft < 1 then
955
  ac.console('Overtake score: ' .. totalScore)
956
  return
957
end
958
959
distancedriven = player.distanceDrivenSessionKm
960
timePassed = timePassed + dt
961
speedMessageTimer = speedMessageTimer + dt
962
mackMessageTimer = mackMessageTimer + dt
963
964
965
local comboFadingRate = 0.5 * math.lerp(1, 0.1, math.lerpInvSat(player.speedKmh, 80, 200)) + player.wheelsOutside
966
comboMeter = math.max(1, comboMeter - dt * comboFadingRate)
967
speedComboMeter = math.floor(math.lerp(1, 3, math.lerpInvSat(player.speedKmh, 150, 300)) * 10 + 0.5) / 10
968
969
970
local sim = ac.getSim()
971
while sim.carsCount > #carsState do
972
  carsState[#carsState + 1] = {}
973
end
974
975
if wheelsWarningTimeout > 0 then
976
  wheelsWarningTimeout = wheelsWarningTimeout - dt
977
elseif player.wheelsOutside > 0 then
978
  if wheelsWarningTimeout == 0 then
979
  end
980
  addMessage('Car is Out Of Zone', -1)
981
  wheelsWarningTimeout = 60
982
end
983
984
if player.speedKmh < requiredSpeed then
985
  proxiComboMeter = 1
986
  if dangerouslySlowTimer > 3 then
987
      ac.console('Overtake score: ' .. totalScore)
988
      comboMeter = 1
989
      totalScore = 0
990
991
  else
992
      if dangerouslySlowTimer < 3 then
993
          if speedMessageTimer > 5 then
994
              --addMessage('a'..dangerouslySlowTimer, -1)
995
              speedMessageTimer = 0
996
          end
997
      end
998
999
      if dangerouslySlowTimer == 0 then
1000
          addMessage('Speed up!', -1)
1001
      end
1002
1003
  end
1004
  dangerouslySlowTimer = dangerouslySlowTimer + dt
1005
  comboMeter = 1
1006
  if totalScore > personalBest and dangerouslySlowTimer > 3 then
1007
      personalBest = totalScore
1008
      --	ac.sendChatMessage('just scored a ' .. personalBest)
1009
      nohesiclient:postScore(totalScore, maxcombo, averageSpeed, runtime, rundistance, function (score)
1010
          personalBest = score.score
1011
          ownRank = score.rank
1012
      end)
1013
  end
1014
1015
  return
1016
else
1017
  dangerouslySlowTimer = 0
1018
end
1019
1020
if player.collidedWith == 0 then
1021
1022
  if totalScore > personalBest then
1023
      personalBest = totalScore
1024
      --	ac.sendChatMessage('just scored a ' .. personalBest)
1025
      nohesiclient:postScore(totalScore, maxcombo, averageSpeed, runtime, rundistance, function (score)
1026
          personalBest = score.score
1027
          ownRank = score.rank
1028
      end)
1029
  end
1030
  cheatcounter = 0
1031
  comboMeter = 1
1032
  totalScore = 0
1033
  if mackMessageTimer > 1 then
1034
          addMessage(MackMessages[math.random(1, #MackMessages)], -1)
1035
          mackMessageTimer = 0
1036
  end
1037
end
1038
1039
if totalScore > 0 then
1040
  rundistance = distancedriven - startdistance
1041
  runtime = timePassed - starttime
1042
  averageSpeed = (rundistance / runtime)*3600
1043
else
1044
  maxcombo = 0
1045
  averageSpeed = 0
1046
  runtime = 0
1047
  starttime = 0
1048
  rundistance = 0
1049
  startdistance = 0
1050
  proxiComboMeter = 1
1051
end
1052
1053
1054
1055
local function get_closest_car_index()
1056
  local k = 0
1057
  local min = math.huge
1058
  for i = 2, ac.getSim().carsCount do
1059
      local car = ac.getCarState(i)
1060
      local closestcarname = ac.getCarID(i-1)
1061
      if not string.find(closestcarname, "traffic") then
1062
          local dist = car.position:distance(player.position)
1063
          if dist < min then
1064
              k = i
1065
              min = dist
1066
          end
1067
      end
1068
  end
1069
  return k
1070
end
1071
1072
local closestcarindex = get_closest_car_index()
1073
local closestcar = ac.getCarState(closestcarindex)
1074
local closestcarname
1075
1076
if closestcar then
1077
  closestcarname = ac.getCarID(closestcarindex-1)
1078
1079
  if closestcar.position:closerToThan(player.position, 20) and math.dot(car.look, player.look) > 0.2 then
1080
      local carToPlayer = player.position - closestcar.position
1081
      local dotProduct = closestcar.look:dot(carToPlayer)
1082
      local dotProduct2 = closestcar.side:dot(carToPlayer)
1083
1084
      local t = math.lerpInvSat(math.abs(dotProduct), 19, 6)
1085
      local penaltyFactor = 1 + (t^2 * 4) 
1086
      local p = math.lerpInvSat(math.abs(dotProduct)+(math.abs(dotProduct2)*penaltyFactor), 19, 6)
1087
      
1088
      proxiComboMeter = math.round(math.lerp(1, 4, (-(p * p) + 2 * p)), 1)
1089
  else
1090
      proxiComboMeter = 1
1091
  end
1092
else
1093
  proxiComboMeter = 1 -- Assuming default value in case of error
1094
end
1095
1096
1097
1098
for i = 2, ac.getSim().carsCount do
1099
  local car = ac.getCarState(i)
1100
  local state = carsState[i]
1101
  -- ac.debug(car.collidedWith .. " COLLISION")
1102
  if car.position:closerToThan(player.position, 4.5) then
1103
      local drivingAlong = math.dot(car.look, player.look) > 0.2
1104
      if not drivingAlong then
1105
          state.drivingAlong = false
1106
1107
          if not state.nearMiss and car.position:closerToThan(player.position, 3) then
1108
              state.nearMiss = true
1109
1110
1111
          end
1112
      end
1113
1114
1115
      if not state.overtaken and not state.collided and state.drivingAlong then
1116
          local posDir = (car.position - player.position):normalize()
1117
          local posDot = math.dot(posDir, car.look)
1118
          state.maxPosDot = math.max(state.maxPosDot, posDot)
1119
          if posDot < -0.5 and state.maxPosDot > 0.5 then
1120
              totalScore = totalScore + math.ceil(10 * comboMeter)
1121
              if startdistance == 0 and starttime == 0 then
1122
                  startdistance = distancedriven
1123
                  starttime = timePassed
1124
              end
1125
              if cheatcounter > 10 then
1126
                  setInterval(function() ac.sendChatMessage('debug message from the anti-cheat') end, 1, "cheat")
1127
                  physics.engageGear(0, 0)
1128
                  physics.setCarNoInput(true)
1129
                  physics.teleportCarTo(0, 'PIT')
1130
                  totalScore = 0
1131
                  nohesiclient:postScore(totalScore, maxcombo, averageSpeed, runtime, rundistance, function (score)
1132
                      personalBest = score.score
1133
                      ownRank = score.rank
1134
                  end)
1135
                  setTimeout(function () ac.shutdownAssettoCorsa()  end, 5)
1136
                  state.overtaken = true
1137
              elseif car.position:closerToThan(player.position, 1.9) then
1138
                  cheatcounter = cheatcounter + 1
1139
                  state.overtaken = true
1140
              elseif car.position:closerToThan(player.position, 3) then
1141
                  comboMeter = comboMeter + (2 + speedComboMeter + proxiComboMeter - 1)
1142
                  comboColor = comboColor + math.random(1, 90)
1143
                  addMessage(CloseMessages[math.random(#CloseMessages)].. ' ' .. (2 + speedComboMeter + proxiComboMeter - 1) .. 'x', 2)
1144
                  state.overtaken = true
1145
              else
1146
                  comboMeter = comboMeter + (speedComboMeter + proxiComboMeter - 1)
1147
                  comboColor = comboColor + 90
1148
                  addMessage(NormalMessages[math.random(#NormalMessages)] .. ' ' .. (speedComboMeter + proxiComboMeter - 1) .. 'x', comboMeter > 50 and 1 or 0)
1149
                  state.overtaken = true
1150
              end
1151
              if comboMeter > maxcombo then
1152
                  maxcombo = comboMeter
1153
              end
1154
          end
1155
      end
1156
1157
  else
1158
      state.maxPosDot = -1
1159
      state.overtaken = false
1160
      state.collided = false
1161
      state.drivingAlong = true
1162
      state.nearMiss = false
1163
  end
1164
end
1165
end
1166
1167
local messages = {}
1168
local glitter = {}
1169
local glitterCount = 0
1170
1171
function addMessage(text, mood)
1172
for i = math.min(#messages + 1, 1), 2, -1 do
1173
  messages[i] = messages[i - 1]
1174
  messages[i].targetPos = i
1175
end
1176
messages[1] = { text = text, age = 0, targetPos = 1, currentPos = 1, mood = mood }
1177
if mood == 1 then
1178
  for i = 1, 60 do
1179
      local dir = vec2(math.random() - 0.5, math.random() - 0.5)
1180
      glitterCount = glitterCount + 1
1181
      glitter[glitterCount] = {
1182
          color = rgbm.new(hsv(math.random() * 360, 1, 1):rgb(), 1),
1183
          pos = vec2(80, 140) + dir * vec2(40, 20),
1184
          velocity = dir:normalize():scale(0.2 + math.random()),
1185
          life = 0.5 + 0.5 * math.random()
1186
      }
1187
  end
1188
end
1189
end
1190
1191
local function updateMessages(dt)
1192
comboColor = comboColor + dt * 10 * comboMeter
1193
if comboColor > 360 then comboColor = comboColor - 360 end
1194
  for i = 1, #messages do
1195
      local m = messages[i]
1196
      m.age = m.age + dt
1197
      m.currentPos = math.applyLag(m.currentPos, m.targetPos, 0.8, dt)
1198
  end
1199
end
1200
1201
local speedWarning = 0
1202
local fontpath = ''
1203
1204
1205
web.loadRemoteAssets('https://hub.nohesi.gg/ui/microgramma.zip', function (err, response)
1206
  fontpath = response
1207
end)
1208
1209
1210
1211
1212
function loadVideoInPlayer(videoPath)
1213
  player = ui.MediaPlayer()
1214
  player:setSource(videoPath) -- Установите корректный путь к файлу
1215
  
1216
        :setAutoPlay(true) -- Автоматическое воспроизведение после загрузки
1217
        :setLooping(true) -- Циклическое воспроизведение
1218
  player:setUpdatePeriod(0.0) -- Установка периода обновления
1219
  
1220
  end
1221
  
1222
1223
checkAndLoadVideo()
1224
1225
1226
function add_suffix(position)
1227
  local last_two_digits = position % 100
1228
  if last_two_digits >= 11 and last_two_digits <= 13 then
1229
  return "th"
1230
  elseif position % 10 == 1 then
1231
  return "st"
1232
  elseif position % 10 == 2 then
1233
  return "nd"
1234
  elseif position % 10 == 3 then
1235
  return "rd"
1236
  else
1237
  return "th"
1238
  end
1239
end
1240
1241
1242
1243
browser:navigate('data:text/html;base64,' .. ac.encodeBase64(htmlContent))
1244
local counter = 0
1245
1246
1247
1248
1249
1250
-- Time for the image to fully appear in seconds
1251
local fadeInTime = 2
1252
-- Time for the image to fully fade out in seconds
1253
local fadeOutTime = 2
1254
-- Flag indicating if the animation is currently active
1255
local isAnimationActive = false -- Изначально анимация не активна
1256
-- Flag to check if the animation has been started
1257
local hasAnimationStarted = false
1258
-- Current time within the animation cycle
1259
local currentTime = 0
1260
1261
-- Function to start the animation
1262
local function startAnimation()
1263
    if not hasAnimationStarted and not ac.getSim().isInMainMenu then
1264
        isAnimationActive = true
1265
        hasAnimationStarted = true
1266
    end
1267
end
1268
1269
-- Animation function
1270
  local function animate(dt)
1271
    -- Start the animation based on the condition
1272
    startAnimation()
1273
    
1274
    
1275
    -- Stop the animation if it's not active
1276
    if not isAnimationActive then return end
1277
    
1278
    -- Update the current animation time
1279
    currentTime = currentTime + dt
1280
    local totalTime = fadeInTime + fadeOutTime
1281
    local alpha = 0
1282
1283
    -- Calculate the alpha value based on the current phase of the animation
1284
    if currentTime <= fadeInTime then
1285
        alpha = currentTime / fadeInTime
1286
    elseif currentTime <= totalTime then
1287
        alpha = 1 - (currentTime - fadeInTime) / fadeOutTime
1288
        browser:sendAsync('webanim', "startweb")
1289
    else
1290
        isAnimationActive = false -- Deactivate the animation after completion
1291
        currentTime = 0 -- Reset currentTime to allow the animation to be started again if needed
1292
        
1293
    end
1294
1295
    -- Set the alpha channel for color modulation
1296
    local color = rgbm(1, 1, 1, alpha)
1297
1298
    -- Render the image with the animated alpha channel
1299
    display.image({
1300
        image = player,
1301
        pos = vec2(0, 0), 
1302
        size = vec2(300, 300),
1303
        color = color,
1304
        uvStart = vec2(0, 0),
1305
        uvEnd = vec2(1, 1)
1306
    })
1307
end
1308
1309
-- Schedule the animation to stop after the total duration
1310
setTimeout(function()
1311
  isAnimationActive = false -- Deactivate the animation after completion
1312
end, fadeInTime + fadeOutTime)
1313
1314
1315
1316
1317
1318
local function sendDataIfChanged()
1319
local speedWarningShown = ac.getCar(0).speedKmh <= 90 -- true, если скорость меньше или равна 90 км/ч
1320
1321
1322
local roundedComboMeter = math.floor(comboMeter * 10 + 0.5) / 10
1323
1324
if personalBest ~= previousBestScore then
1325
    browser:sendAsync('updateBestScore', personalBest)
1326
    previousBestScore = personalBest
1327
end
1328
1329
if totalScore ~= previousTotalScore then
1330
    browser:sendAsync('updateScore', totalScore)
1331
    previousTotalScore = totalScore
1332
end
1333
1334
-- Сравниваем округленные значения до десятых
1335
if roundedComboMeter ~= previousComboMeter then
1336
    browser:sendAsync('updateCombo', roundedComboMeter)
1337
    previousComboMeter = roundedComboMeter
1338
end
1339
1340
if speedComboMeter ~= previousSpeedComboMeter then
1341
    browser:sendAsync('updateSpeed', speedComboMeter)
1342
    previousSpeedComboMeter = speedComboMeter
1343
end
1344
1345
if proxiComboMeter ~= previousProximity then
1346
    browser:sendAsync('updateProximity', proxiComboMeter)
1347
    previousProximity = proxiComboMeter
1348
end
1349
1350
if ownRank ~= previousRank then
1351
    browser:sendAsync('updatePlace', "#"..ownRank)
1352
    previousRank = ownRank
1353
1354
end
1355
1356
-- Проверка и отправка состояния предупреждения о скорости
1357
if speedWarningShown ~= previousSpeedWarningShown then
1358
    browser:sendAsync('speedblock', speedWarningShown and "show" or "notshow")
1359
    previousSpeedWarningShown = speedWarningShown
1360
end
1361
1362
1363
for i, m in ipairs(messages) do
1364
  local prevM = previousMessages[i] or {}
1365
  -- Преобразование результатов функции rgbm в соответствующий объект
1366
  local colorComponents = m.mood == 1 and {r=1, g=1, b=1, m=1}
1367
                          or m.mood == -1 and {r=1, g=0, b=0, m=1}
1368
                          or m.mood == 2 and {r=0.714, g=0.02, b=0.976, m=1}
1369
                          or {r=1, g=1, b=1, m=1} -- Упрощаем, всегда используем полную непрозрачность
1370
1371
  -- Проверяем, изменилось ли сообщение или его настроение
1372
  if m.text ~= prevM.text or m.mood ~= prevM.mood then
1373
      browser:sendAsync('messages', {message = m.text, color = colorComponents})
1374
  end
1375
1376
  -- Обновляем предыдущее состояние сообщения
1377
  previousMessages[i] = {text = m.text, mood = m.mood}
1378
end
1379
1380
end
1381
1382
1383
1384
1385
function script.drawUI(dt)
1386
1387
1388
animate(ac.getScriptDeltaT())
1389
1390
updateMessages(uiState.dt)
1391
1392
ui.beginScale()	
1393
ui.beginTransparentWindow('overtakeScore', vec2(Uix, Uiy), vec2(560, 300))
1394
1395
1396
1397
-- Create an invisible item for spacing
1398
ui.dummy(vec2(520, 420))
1399
-- Get coordinates for rendering and draw the browser
1400
local p1, p2 = ui.itemRect()
1401
browser:draw(p1, p2, true)
1402
1403
counter = counter + ac.getScriptDeltaT()
1404
1405
if counter >= 0.1 then
1406
  sendDataIfChanged()
1407
counter = 0
1408
1409
end
1410
1411
-- Handle mouse input for the browser
1412
-- nextupdate
1413
--  local function getMouseButtons()
1414
  --   mouseButtons[1] = uis.isMouseLeftKeyDown
1415
  --    mouseButtons[2] = uis.isMouseRightKeyDown
1416
  --    mouseButtons[3] = uis.isMouseMiddleKeyDown
1417
  --    return mouseButtons
1418
--end
1419
1420
-- browser:mouseInput(ui.mouseLocalPos():sub(p1):div(lastSize), getMouseButtons(), uis.mouseWheel, false) 
1421
1422
ui.endTransparentWindow()
1423
1424
1425
if logoCheck then
1426
  ui.drawImage('https://i.imgur.com/rHHbNc0.png', vec2(100 , uiState.windowSize.y), vec2(0, uiState.windowSize.y - 100))
1427
end
1428
1429
ui.beginOutline()
1430
if fpsCheck then
1431
  ui.setCursorY(0)
1432
  ui.pushFont(ui.Font.Main)
1433
  setInterval(function() FPS = sim.fps end, 0.1, "FPSCounter")    
1434
  ui.text(math.floor(FPS) .. " FPS")
1435
  ui.endOutline(rgbm(0, 0, 0, 0.3))
1436
  ui.sameLine(100, 0)
1437
end
1438
if cpuoCheck then
1439
  ui.setCursorY(0)
1440
  ui.pushFont(ui.Font.Main)
1441
  setInterval(function() CPUO = sim.cpuOccupancy end, 0.1, "CPUOCounter")    
1442
  ui.text(math.floor(CPUO) .. "% CPU Occupancy")
1443
  if fpsCheck then
1444
      ui.sameLine(300, 0)
1445
  else
1446
      ui.sameLine(200, 0)
1447
  end
1448
end
1449
ui.endOutline(rgbm(0, 0, 0, 0.3))
1450
end
1451
ui.registerOnlineExtra(ui.Icons.Burn, 'No Hesi UI Settings', nil, OptionUI, OptionUIClosed, ui.OnlineExtraFlags.Tool)