View difference between Paste ID: 45zHRhfG and s3QwHM2i
SHOW: | | - or go back to the newest paste.
1
local config = ac.configValues({
2
  hubUrl = ""
3
})
4-
4+
 
5
---@class NoHesiClient
6
local NoHesiClient = class("NoHesiClient")
7-
7+
 
8-
8+
 
9
local closestcar = 1
10-
10+
 
11-
11+
 
12
-- Import the WebBrowser library
13
local WebBrowser = require('shared/web/browser')
14
ui.setAsynchronousImagesLoading(true)
15-
15+
 
16
-- Configure the WebBrowser
17
WebBrowser.configure({
18
  targetFPS = 240,
19
})
20-
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-
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-
33+
 
34-
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-
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-
50+
 
51
        -- Path to the file to move
52
        local sourceFile = folder .. "\\2_90FPS_2.wmv"
53
        print("Source file path: " .. sourceFile)
54-
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-
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-
80+
 
81-
81+
 
82-
82+
 
83-
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-
92+
 
93
<style>
94-
94+
 
95-
95+
 
96-
96+
 
97
.shake-top {
98
  animation: shake-top 0.8s cubic-bezier(0.455, 0.030, 0.515, 0.955) both;
99
}
100-
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-
122+
 
123-
123+
 
124-
124+
 
125-
125+
 
126-
126+
 
127-
127+
 
128
.blink-2 {
129
  animation: blink-2 0.4s both;
130
}
131-
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-
143+
 
144-
144+
 
145-
145+
 
146-
146+
 
147-
147+
 
148-
148+
 
149
:root {
150
  --font-family: 'Roboto Flex', sans-serif;
151
  --second-family: 'Roboto', sans-serif;
152
  --main-color: #B130FF; /* Purple color */
153
}
154-
154+
 
155-
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-
163+
 
164
}
165-
165+
 
166
.dashboard {
167
  justify-content: flex-start; /* Это изменение выровняет все содержимое dashboard, включая logo, слева */
168
}
169-
169+
 
170
.logo {
171
  justify-content: flex-start;
172
  width: 100%;
173
}
174-
174+
 
175-
175+
 
176-
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-
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-
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-
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-
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-
226+
 
227-
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-
247+
 
248-
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-
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-
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-
272+
 
273-
273+
 
274
.item {
275
display: flex;
276
flex-direction: column; /* Устанавливает направление элементов в колонку */
277
align-items: center; /* Центрирует элементы по горизонтали */
278
}
279-
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-
291+
 
292
}
293-
293+
 
294-
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-
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-
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-
329+
 
330
}
331-
331+
 
332-
332+
 
333-
333+
 
334
.content {
335
visibility: hidden; /* Содержимое скрыто, но прогружается */
336
opacity: 0;
337
transition: visibility 0s, opacity 1s linear; /* Плавное появление */
338
transition-delay: 2s; /* Задержка перед началом анимации появления */
339
}
340-
340+
 
341
.logo-text {
342
position: absolute;
343-
343+
 
344
opacity: 0; /* Изначально текст невидим */
345
margin-top: 30px
346-
346+
 
347
}
348-
348+
 
349-
349+
 
350-
350+
 
351-
351+
 
352
</style>
353
</head>
354
<body>
355-
355+
 
356-
356+
 
357-
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='28' viewBox='0 0 90 28' 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='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"/> 
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-
382+
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-
406+
407
</div>
408
</div>
409-
409+
410
 
411
<script>
412
// Функция для плавного обновления числа
413
// Функция для плавного обновления числа с поддержкой условного форматирования текста
414
// Универсальная функция анимации для обновления числовых значений
415-
415+
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-
423+
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-
431+
432
  }
433-
433+
 
434
  requestAnimationFrame(updateNumber);
435
}
436
 
437
// Обработка обновления различных значений
438-
438+
439
  const selectorMap = {
440
    bestScore: '.score-text',
441-
441+
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-
451+
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-
458+
459
const updateQueue = [];
460
let isAnimating = false; // Флаг, показывающий, идет ли в данный момент анимация
461-
461+
 
462-
462+
463
  if (updateQueue.length === 0 || isAnimating) {
464
    return; // Выходим, если анимация уже идёт или нет заданий в очереди
465
  }
466-
466+
 
467
  isAnimating = true; // Устанавливаем флаг анимации
468
  const data = updateQueue.shift(); // Извлекаем первый элемент очереди
469
 
470
  const speedUpElement = document.querySelector('.speed-up');
471-
471+
472
    console.error("Элемент для обновления сообщения о ускорении не найден.");
473
    isAnimating = false;
474-
474+
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-
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-
487+
488
  }, 200); // 200ms для достижения 50% анимации blink-2
489
 
490
  // Обновляем текст в середине анимации
491
  setTimeout(() => {
492
    speedUpElement.textContent = data.message;
493
  }, 200); // Обновляем текст одновременно с изменением цвета
494-
494+
 
495
  // Завершаем анимацию и удаляем классы анимации с небольшой задержкой после полного цикла
496
  setTimeout(() => {
497
    speedUpElement.classList.remove('blink-2', 'shake-top');
498
    isAnimating = false;
499-
499+
500
  }, 400); // Полная длительность анимации blink-2
501
}
502
 
503
function updateSpeedUpMessage(data) {
504
  updateQueue.push(data); // Добавляем данные в очередь
505
  if (!isAnimating) {
506
    processNextUpdate(); // Запускаем обработку очереди, если анимация не активна
507-
507+
508
}
509
 
510
 
511
 
512
 
513
 
514-
514+
 
515-
515+
 
516-
516+
 
517-
517+
 
518-
518+
519-
519+
520-
520+
 
521-
521+
522-
522+
523
if (!placeElement) {
524
  console.error("Элемент для обновления места не найден.");
525-
525+
526
}
527
 
528
// Обновление текста элемента
529
placeElement.textContent = `${newPlace}`;
530
}
531
 
532-
532+
533
const badge = document.querySelector('.score-badge');
534
const badgeText = badge.querySelector('.score-badge-text');
535
const svg = badge.querySelector('svg');
536-
536+
 
537
// Определение стилей для разных позиций
538
const styleConfigs = {
539
  1: {
540
    backgroundColor: 'rgba(221, 3, 85, 1)',
541-
541+
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-
569+
570
const finalFontSize = config.fontSize instanceof Array ? config.fontSize[fontSizeIndex] : config.fontSize;
571
 
572-
572+
573
badge.style.background = config.backgroundColor;
574
badgeText.style.color = config.textColor;
575
badgeText.style.fontSize = finalFontSize;
576-
576+
577
}
578
 
579
 
580
// Функция для управления видимостью предупреждения о скорости
581
function speedWarningDisplay(show) {
582
const speedWarningElement = document.querySelector('.speed-warning');
583-
583+
584-
584+
 
585
// Устанавливаем свойство display в зависимости от аргумента show
586
speedWarningElement.style.display = show ? 'block' : 'none';
587
}
588
 
589-
589+
 
590
 
591
// Обработчик события, получающий данные от AC
592
AC.onReceive('speedblock', arg => {
593-
593+
594-
594+
595-
595+
596
});
597
 
598
 
599
 
600
 
601
function setupEventHandlers() {
602-
602+
603-
603+
604-
604+
605-
605+
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-
618+
619
}
620
 
621
function showContent() {
622
const content = document.querySelector('.content');
623
if(content) {
624
  content.style.visibility = 'visible';
625-
625+
626
}
627
}
628
 
629
document.addEventListener('DOMContentLoaded', setupEventHandlers);
630
 
631
 
632
</script>
633-
633+
 
634
 
635-
635+
 
636-
636+
 
637
</body>
638-
638+
639-
639+
640-
640+
 
641-
641+
 
642
 
643
function checkAndLoadVideo()
644
local rootFolder = ac.getFolder(ac.FolderID.Root) .. "\\cache\\remote_assets\\nohesivideo"
645-
645+
646-
646+
647-
647+
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-
659+
660
            io.createDir(rootFolder)
661
        end
662-
662+
 
663
        -- Move the file
664
        if io.move(sourceFile, targetFile) then
665
            print("File successfully moved to " .. targetFile)
666
            loadVideoInPlayer(targetFile)
667-
667+
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-
683+
684-
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-
689+
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-
705+
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-
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-
723+
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-
736+
 
737
function NoHesiClient:getScore(callback)
738
  local payload = ac.getUserSteamID()
739
  local success, err = pcall(function()
740
      ac.signBlob(payload, function (data)
741-
741+
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-
754+
 
755
function urlEncode(str)
756
  if str then
757
    str = string.gsub(str, "\n", "\r\n")
758
    str = string.gsub(str, "([^%w ])",
759-
759+
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-
769+
 
770
local nohesiclient = NoHesiClient(config.hubUrl)
771
local requiredSpeed = 95
772
local sim = ac.getSim()
773
 
774-
774+
775
local averageSpeed = 0
776
local startdistance = 0
777
local rundistance = 0
778-
778+
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-
818+
819-
819+
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-
836+
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-
878+
879
  local uiState = ac.getUI()
880-
880+
881
      Uix = uiState.windowSize.y * 0.005
882-
882+
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-
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-
908+
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-
917+
918
updateTopScores()
919
end)
920
 
921
local function broadcastPersonalBest()
922
  if totalScore > personalBest then
923
      personalBestEvent({ score = totalScore })
924
  end
925-
925+
926
 
927
local function savePersonalBest()
928
  if totalScore > personalBest then
929
      personalBest = totalScore
930
      nohesiclient:postScore(totalScore, maxcombo, averageSpeed, runtime, rundistance, function (score)
931-
931+
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-
941+
942
end
943
 
944
local leaderboardSize = vec2(250, 80)
945
 
946
local uiState = ac.getUI()
947
 
948-
948+
949
if timePassed == 0 then
950-
950+
951
end
952-
952+
 
953
local player = ac.getCarState(1)
954
if player.engineLifeLeft < 1 then
955
  ac.console('Overtake score: ' .. totalScore)
956
  return
957-
957+
958
 
959
distancedriven = player.distanceDrivenSessionKm
960
timePassed = timePassed + dt
961
speedMessageTimer = speedMessageTimer + dt
962
mackMessageTimer = mackMessageTimer + dt
963-
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-
968+
 
969-
969+
 
970
local sim = ac.getSim()
971
while sim.carsCount > #carsState do
972
  carsState[#carsState + 1] = {}
973-
973+
974-
974+
 
975
if wheelsWarningTimeout > 0 then
976
  wheelsWarningTimeout = wheelsWarningTimeout - dt
977
elseif player.wheelsOutside > 0 then
978
  if wheelsWarningTimeout == 0 then
979-
979+
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-
988+
989
      totalScore = 0
990
 
991
  else
992
      if dangerouslySlowTimer < 3 then
993
          if speedMessageTimer > 5 then
994
              --addMessage('a'..dangerouslySlowTimer, -1)
995-
995+
996
          end
997
      end
998
 
999
      if dangerouslySlowTimer == 0 then
1000
          addMessage('Speed up!', -1)
1001
      end
1002
 
1003-
1003+
1004
  dangerouslySlowTimer = dangerouslySlowTimer + dt
1005
  comboMeter = 1
1006
  if totalScore > personalBest and dangerouslySlowTimer > 3 then
1007-
1007+
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-
      --	ac.sendChatMessage('just scored a ' .. personalBest)
1013+
1014
 
1015
  return
1016
else
1017
  dangerouslySlowTimer = 0
1018
end
1019-
1019+
 
1020
if player.collidedWith == 0 then
1021
 
1022
  if totalScore > personalBest then
1023
      personalBest = totalScore
1024-
1024+
      --    ac.sendChatMessage('just scored a ' .. personalBest)
1025
      nohesiclient:postScore(totalScore, maxcombo, averageSpeed, runtime, rundistance, function (score)
1026-
1026+
1027
          ownRank = score.rank
1028
      end)
1029-
      --	ac.sendChatMessage('just scored a ' .. personalBest)
1029+
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-
1043+
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-
1057+
1058-
1058+
1059-
1059+
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-
1076+
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-
1080+
1081
      local dotProduct = closestcar.look:dot(carToPlayer)
1082
      local dotProduct2 = closestcar.side:dot(carToPlayer)
1083-
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-
1088+
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-
1100+
1101-
1101+
1102-
1102+
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-
1111+
1112
      end
1113
 
1114-
1114+
 
1115-
1115+
1116
          local posDir = (car.position - player.position):normalize()
1117
          local posDot = math.dot(posDir, car.look)
1118-
1118+
1119-
1119+
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-
1161+
1162
      state.nearMiss = false
1163
  end
1164
end
1165
end
1166
 
1167
local messages = {}
1168
local glitter = {}
1169
local glitterCount = 0
1170
 
1171-
1171+
1172
for i = math.min(#messages + 1, 1), 2, -1 do
1173
  messages[i] = messages[i - 1]
1174
  messages[i].targetPos = i
1175-
1175+
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-
1195+
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-
1205+
1206
  fontpath = response
1207
end)
1208-
1208+
 
1209-
1209+
 
1210
 
1211
 
1212
function loadVideoInPlayer(videoPath)
1213-
1213+
1214-
1214+
1215-
1215+
1216-
1216+
1217
        :setLooping(true) -- Циклическое воспроизведение
1218
  player:setUpdatePeriod(0.0) -- Установка периода обновления
1219
  
1220
  end
1221
  
1222
 
1223
checkAndLoadVideo()
1224
 
1225
 
1226
function add_suffix(position)
1227-
1227+
1228
  if last_two_digits >= 11 and last_two_digits <= 13 then
1229-
1229+
1230-
1230+
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-
1245+
 
1246-
1246+
 
1247-
1247+
 
1248
 
1249
 
1250-
1250+
1251-
1251+
1252-
1252+
1253-
1253+
1254-
1254+
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-
1265+
1266
    end
1267
end
1268
 
1269
-- Animation function
1270
  local function animate(dt)
1271
    -- Start the animation based on the condition
1272
    startAnimation()
1273-
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-
1287+
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-
1299+
1300
        image = player,
1301
        pos = vec2(0, 0), 
1302-
1302+
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-
1313+
 
1314
 
1315
 
1316
 
1317
 
1318-
1318+
1319-
1319+
1320-
1320+
 
1321-
1321+
 
1322-
1322+
1323
 
1324
if personalBest ~= previousBestScore then
1325-
1325+
1326-
1326+
1327
end
1328-
1328+
 
1329
if totalScore ~= previousTotalScore then
1330
    browser:sendAsync('updateScore', totalScore)
1331
    previousTotalScore = totalScore
1332
end
1333-
1333+
 
1334
-- Сравниваем округленные значения до десятых
1335
if roundedComboMeter ~= previousComboMeter then
1336
    browser:sendAsync('updateCombo', roundedComboMeter)
1337
    previousComboMeter = roundedComboMeter
1338-
1338+
1339
 
1340
if speedComboMeter ~= previousSpeedComboMeter then
1341
    browser:sendAsync('updateSpeed', speedComboMeter)
1342
    previousSpeedComboMeter = speedComboMeter
1343
end
1344-
1344+
 
1345
if proxiComboMeter ~= previousProximity then
1346
    browser:sendAsync('updateProximity', proxiComboMeter)
1347
    previousProximity = proxiComboMeter
1348
end
1349-
1349+
 
1350
if ownRank ~= previousRank then
1351
    browser:sendAsync('updatePlace', "#"..ownRank)
1352
    previousRank = ownRank
1353
 
1354-
1354+
1355
 
1356
-- Проверка и отправка состояния предупреждения о скорости
1357
if speedWarningShown ~= previousSpeedWarningShown then
1358-
1358+
1359
    previousSpeedWarningShown = speedWarningShown
1360-
1360+
1361
 
1362
 
1363
for i, m in ipairs(messages) do
1364
  local prevM = previousMessages[i] or {}
1365
  -- Преобразование результатов функции rgbm в соответствующий объект
1366-
1366+
1367-
1367+
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-
1375+
 
1376
  -- Обновляем предыдущее состояние сообщения
1377
  previousMessages[i] = {text = m.text, mood = m.mood}
1378
end
1379
 
1380-
1380+
1381
 
1382
 
1383
 
1384-
1384+
 
1385
function script.drawUI(dt)
1386-
1386+
 
1387-
1387+
 
1388-
1388+
1389-
1389+
 
1390
updateMessages(uiState.dt)
1391-
1391+
 
1392-
1392+
ui.beginScale() 
1393
ui.beginTransparentWindow('overtakeScore', vec2(Uix, Uiy), vec2(560, 300))
1394-
1394+
 
1395
 
1396-
1396+
 
1397-
ui.beginScale()	
1397+
1398
ui.dummy(vec2(520, 420))
1399-
1399+
1400-
1400+
1401-
1401+
1402
 
1403
counter = counter + ac.getScriptDeltaT()
1404
 
1405
if counter >= 0.1 then
1406
  sendDataIfChanged()
1407-
1407+
1408
 
1409-
1409+
1410
 
1411
-- Handle mouse input for the browser
1412
-- nextupdate
1413-
1413+
1414
  --   mouseButtons[1] = uis.isMouseLeftKeyDown
1415-
1415+
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-
1424+
 
1425
if logoCheck then
1426-
1426+
1427
end
1428-
1428+
 
1429-
1429+
1430
if fpsCheck then
1431
  ui.setCursorY(0)
1432
  ui.pushFont(ui.Font.Main)
1433-
1433+
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)