Nitro Type - Racing Mini Map by MV Typing

Displays a mini map of the Race Track Version 2. 1st version is made by toonidy:) Subscribe to MV Typing on YouTube!

  1. // ==UserScript==
  2. // @name Nitro Type - Racing Mini Map by MV Typing
  3. // @version 0.2.0
  4. // @description Displays a mini map of the Race Track Version 2. 1st version is made by toonidy:) Subscribe to MV Typing on YouTube!
  5. // @author MV Typing
  6. // @match *://*.nitrotype.com/race
  7. // @match *://*.nitrotype.com/race/*
  8. // @icon https://i.ibb.co/YRs06pc/toonidy-userscript.png
  9. // @grant none
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/pixi.js/6.5.4/browser/pixi.min.js
  11. // @require https://greasyfork.org/scripts/443718-nitro-type-userscript-utils/code/Nitro%20Type%20Userscript%20Utils.js?version=1042360
  12. // @license MIT
  13. // @namespace https://greasyfork.org/users/858426
  14. // ==/UserScript==
  15.  
  16. /* globals PIXI findReact createLogger */
  17.  
  18. const config = {
  19. colors: {
  20. me: 0xf2887f,
  21. opponentPlayer: 0x9E00FF,
  22. opponentBot: 0xF3FF00,
  23. opponentWampus: 0xFF8000,
  24. nitro: 0xFF00D8,
  25. background: 0x696969,
  26. raceLane: 0xFF0000,
  27. startLine: 0x00F3FF,
  28. finishLine: 0x3200FF,
  29. },
  30. trackLocally: true, // whether to track your player's position without server data
  31. moveDestination: {
  32. enabled: true,
  33. alpha: 0.5, // range 0.0 - 1.0 (the lower the value, the more transparent)
  34. },
  35. }
  36.  
  37. const logging = createLogger("Nitro Type Racing Mini Map"),
  38. raceContainer = document.querySelector("#raceContainer"),
  39. raceObj = raceContainer ? findReact(raceContainer) : null
  40. if (!raceContainer || !raceObj) {
  41. logging.error("Init")("Could not find the race track")
  42. return
  43. }
  44. if (!raceObj.props.user.loggedIn) {
  45. logging.error("Init")("This userscript is not available for Guest Racing")
  46. return
  47. }
  48.  
  49. PIXI.utils.skipHello()
  50.  
  51. const style = document.createElement("style")
  52. style.appendChild(
  53. document.createTextNode(`
  54. .nt-racing-mini-map-root canvas {
  55. display: block;
  56. }`))
  57. document.head.appendChild(style)
  58.  
  59. const racingMiniMap = new PIXI.Application({ width: 1024, height: 100, backgroundColor: config.colors.background }),
  60. container = document.createElement("div");
  61.  
  62. container.className = "nt-racing-mini-map-root"
  63.  
  64. ///////////////////////
  65. // Prepare Objects //
  66. ///////////////////////
  67.  
  68. const RACER_WIDTH = 28,
  69. CROSSING_LINE_WIDTH = 32,
  70. PADDING = 2,
  71. racers = Array(5).fill(null),
  72. server = raceObj.server,
  73. currentUserID = raceObj.props.user.userID
  74.  
  75. // Draw mini racetrack
  76. const raceTrackBG = new PIXI.TilingSprite.from("/dist/site/images/backgrounds/bg-noise.png", { width: racingMiniMap.renderer.width, height: racingMiniMap.renderer.height} ),
  77. startLine = PIXI.Sprite.from(PIXI.Texture.WHITE),
  78. finishLine = PIXI.Sprite.from(PIXI.Texture.WHITE)
  79.  
  80. startLine.x = CROSSING_LINE_WIDTH
  81. startLine.y = 0
  82. startLine.width = 1
  83. startLine.height = racingMiniMap.renderer.height
  84. startLine.tint = config.colors.startLine
  85.  
  86. finishLine.x = racingMiniMap.renderer.width - CROSSING_LINE_WIDTH - 1
  87. finishLine.y = 0
  88. finishLine.width = 1
  89. finishLine.height = racingMiniMap.renderer.height
  90. finishLine.tint = config.colors.finishLine
  91.  
  92. raceTrackBG.addChild(startLine, finishLine)
  93.  
  94. for (let i = 1; i < 5; i++) {
  95. const lane = PIXI.Sprite.from(PIXI.Texture.WHITE)
  96. lane.x = 0
  97. lane.y = i * (racingMiniMap.renderer.height / 5)
  98. lane.width = racingMiniMap.renderer.width
  99. lane.height = 1
  100. lane.tint = config.colors.raceLane
  101. raceTrackBG.addChild(lane)
  102. }
  103.  
  104. racingMiniMap.stage.addChild(raceTrackBG)
  105.  
  106. /* Mini Map movement animation update. */
  107. function animateRacerTicker() {
  108. const r = this
  109. const lapse = Date.now() - r.lastUpdated
  110. if (r.sprite.x < r.toX) {
  111. const distance = r.toX - r.fromX
  112. r.sprite.x = r.fromX + Math.min(distance, distance * (lapse / r.moveMS))
  113. if (r.ghostSprite && r.sprite.x === r.ghostSprite.x) {
  114. r.ghostSprite.renderable = false
  115. }
  116. }
  117. if (r.skipped > 0) {
  118. const nitroTargetWidth = r.nitroToX - r.nitroFromX
  119. if (r.nitroSprite.width < nitroTargetWidth) {
  120. r.nitroSprite.width = Math.min(nitroTargetWidth, r.sprite.x - r.nitroFromX)
  121. } else if (r.nitroSprite.width === nitroTargetWidth && r.nitroSprite.alpha > 0 && !r.nitroDisableFade) {
  122. if (r.nitroSprite.alpha === 1) {
  123. r.nitroStartFadeStamp = Date.now() - 1
  124. }
  125. r.nitroSprite.alpha = Math.max(0, 1 - ((Date.now() - r.nitroStartFadeStamp) / 1e3))
  126. }
  127. }
  128. if (r.completeStamp !== null && r.sprite.x === r.toX && r.nitroSprite.alpha === 0) {
  129. racingMiniMap.ticker.remove(animateRacerTicker, this)
  130. }
  131. }
  132.  
  133. /* Handle adding in players on the mini map. */
  134. server.on("joined", (e) => {
  135. const { lane, userID } = e
  136.  
  137. let color = config.colors.opponentBot
  138. if (userID === currentUserID) {
  139. color = config.colors.me
  140. } else if (!e.robot) {
  141. color = config.colors.opponentPlayer
  142. } else if (e.profile.specialRobot === "wampus") {
  143. color = config.colors.opponentWampus
  144. }
  145.  
  146. if (racers[lane]) {
  147. racers[lane].ghostSprite.tint = color
  148. racers[lane].sprite.tint = color
  149. racers[lane].sprite.x = 0 - RACER_WIDTH + PADDING
  150. racers[lane].lastUpdated = Date.now()
  151. racers[lane].fromX = racers[lane].sprite.x
  152. racers[lane].toX = PADDING
  153. racers[lane].sprite.renderable = true
  154. return
  155. }
  156.  
  157. const r = PIXI.Sprite.from(PIXI.Texture.WHITE)
  158. r.x = 0 - RACER_WIDTH + PADDING
  159. r.y = PADDING + (lane > 0 ? 1 : 0) + (lane * (racingMiniMap.renderer.height / 5))
  160. r.tint = color
  161. r.width = RACER_WIDTH
  162. r.height = 16 - (lane > 0 ? 1 : 0)
  163.  
  164. const n = PIXI.Sprite.from(PIXI.Texture.WHITE)
  165. n.y = r.y + ((16 - (lane > 0 ? 1 : 0)) / 2) - 1
  166. n.renderable = false
  167. n.tint = config.colors.nitro
  168. n.width = 1
  169. n.height = 2
  170.  
  171. racers[lane] = {
  172. lane,
  173. sprite: r,
  174. userID: userID,
  175. ghostSprite: null,
  176. nitroSprite: n,
  177. lastUpdated: Date.now(),
  178. fromX: r.x,
  179. toX: PADDING,
  180. skipped: 0,
  181. nitroStartFadeStamp: null,
  182. nitroFromX: null,
  183. nitroToX: null,
  184. nitroDisableFade: false,
  185. moveMS: 500,
  186. completeStamp: null,
  187. }
  188.  
  189. if (config.moveDestination.enabled) {
  190. const g = PIXI.Sprite.from(PIXI.Texture.WHITE)
  191. g.x = PADDING
  192. g.y = PADDING + (lane > 0 ? 1 : 0) + (lane * (racingMiniMap.renderer.height / 5))
  193. g.tint = color
  194. g.alpha = config.moveDestination.alpha
  195. g.width = RACER_WIDTH
  196. g.height = 16 - (lane > 0 ? 1 : 0)
  197. g.renderable = false
  198.  
  199. racers[lane].ghostSprite = g
  200. racingMiniMap.stage.addChild(g)
  201. }
  202.  
  203. racingMiniMap.stage.addChild(n)
  204. racingMiniMap.stage.addChild(r)
  205.  
  206. racingMiniMap.ticker.add(animateRacerTicker, racers[lane])
  207. })
  208.  
  209. /* Handle any players leaving the race track. */
  210. server.on("left", (e) => {
  211. const lane = racers.findIndex((r) => r?.userID === e)
  212. if (racers[lane]) {
  213. racers[lane].sprite.renderable = false
  214. racers[lane].ghostSprite.renderable = false
  215. racers[lane].nitroSprite.renderable = false
  216. }
  217. })
  218.  
  219. /* Handle race map progress position updates. */
  220. server.on("update", (e) => {
  221. let moveFinishMS = 500
  222.  
  223. const payloadUpdateRacers = e.racers.slice().sort((a, b) => {
  224. if (a.progress.completeStamp === b.progress.completeStamp) {
  225. return 0
  226. }
  227. if (a.progress.completeStamp === null) {
  228. return 1
  229. }
  230. return a.progress.completeStamp > 0 && b.progress.completeStamp > 0 && a.progress.completeStamp > b.progress.completeStamp ? 1 : -1
  231. })
  232.  
  233. for (let i = 0; i < payloadUpdateRacers.length; i++) {
  234. const r = payloadUpdateRacers[i],
  235. { completeStamp, skipped } = r.progress,
  236. racerObj = racers[r.lane]
  237. if (!racerObj || racerObj.completeStamp > 0 || (r.userID === currentUserID && completeStamp <= 0 && config.trackLocally)) {
  238. continue
  239. }
  240.  
  241. if (r.disqualified) {
  242. racingMiniMap.ticker.remove(animateRacerTicker, racerObj)
  243. racingMiniMap.stage.removeChild(racerObj.sprite, racerObj.nitroSprite)
  244. if (racerObj.ghostSprite) {
  245. racingMiniMap.stage.removeChild(racerObj.ghostSprite)
  246. }
  247. racerObj.sprite.destroy()
  248. racerObj.ghostSprite.destroy()
  249. racerObj.nitroSprite.destroy()
  250.  
  251. racers[r.lane] = null
  252. continue
  253. }
  254.  
  255. racerObj.lastUpdated = Date.now()
  256. racerObj.fromX = racerObj.sprite.x
  257.  
  258. if (racerObj.completeStamp === null && completeStamp > 0) {
  259. racerObj.completeStamp = completeStamp
  260. racerObj.toX = racingMiniMap.renderer.width - RACER_WIDTH - PADDING
  261. racerObj.moveMS = moveFinishMS
  262.  
  263. if (racerObj.nitroDisableFade) {
  264. racerObj.nitroToX = racingMiniMap.renderer.width - RACER_WIDTH - PADDING
  265. racerObj.nitroDisableFade = false
  266. }
  267. } else {
  268. racerObj.moveMS = 1e3
  269. racerObj.toX = r.progress.percentageFinished * (racingMiniMap.renderer.width - RACER_WIDTH - CROSSING_LINE_WIDTH - PADDING - 1)
  270. racerObj.sprite.x = racerObj.fromX
  271. }
  272.  
  273. if (racerObj.ghostSprite) {
  274. racerObj.ghostSprite.x = racerObj.toX
  275. racerObj.ghostSprite.renderable = true
  276. }
  277.  
  278. if (skipped !== racerObj.skipped) {
  279. if (racerObj.skipped === 0) {
  280. racerObj.nitroFromX = racerObj.fromX
  281. racerObj.nitroSprite.x = racerObj.fromX
  282. racerObj.nitroSprite.renderable = true
  283. }
  284. racerObj.skipped = skipped // because infinite nitros exist? :/
  285. racerObj.nitroToX = racerObj.toX
  286. racerObj.nitroSprite.alpha = 1
  287. if (racerObj.completeStamp !== null) {
  288. racerObj.nitroToX = racingMiniMap.renderer.width - RACER_WIDTH - PADDING
  289. }
  290. }
  291.  
  292. if (completeStamp > 0 && i + 1 < payloadUpdateRacers.length) {
  293. const nextRacer = payloadUpdateRacers[i + 1],
  294. nextRacerObj = racers[nextRacer?.lane]
  295. if (nextRacerObj && nextRacerObj.completeStamp === null && nextRacer.progress.completeStamp > 0 && nextRacer.progress.completeStamp > completeStamp) {
  296. moveFinishMS += 250
  297. }
  298. }
  299. }
  300. })
  301.  
  302. if (config.trackLocally) {
  303. let lessonLength = 0
  304. server.on("status", (e) => {
  305. if (e.status === "countdown") {
  306. lessonLength = e.lessonLength
  307. }
  308. })
  309.  
  310. const originalSendPlayerUpdate = server.sendPlayerUpdate
  311. server.sendPlayerUpdate = (data) => {
  312. originalSendPlayerUpdate(data)
  313. const racerObj = racers.find((r) => r?.userID === currentUserID)
  314. if (!racerObj) {
  315. return
  316. }
  317.  
  318. const percentageFinished = (data.t / (lessonLength || 1))
  319. racerObj.lastUpdated = Date.now()
  320. racerObj.fromX = racerObj.sprite.x
  321. racerObj.moveMS = 500
  322. racerObj.toX = percentageFinished * (racingMiniMap.renderer.width - RACER_WIDTH - CROSSING_LINE_WIDTH - PADDING - 1)
  323. racerObj.sprite.x = racerObj.fromX
  324.  
  325. if (racerObj.ghostSprite) {
  326. racerObj.ghostSprite.x = racerObj.toX
  327. racerObj.ghostSprite.renderable = true
  328. }
  329.  
  330. if (data.s) {
  331. if (racerObj.skipped === 0) {
  332. racerObj.nitroFromX = racerObj.fromX
  333. racerObj.nitroSprite.x = racerObj.fromX
  334. racerObj.nitroSprite.renderable = true
  335. }
  336. racerObj.skipped = data.s // because infinite nitros exist? but I'm not going to test that! :/
  337. racerObj.nitroToX = racerObj.toX
  338. racerObj.nitroSprite.alpha = 1
  339. racerObj.nitroDisableFade = percentageFinished === 1
  340.  
  341. if (racerObj.completeStamp !== null) {
  342. racerObj.nitroToX = racingMiniMap.renderer.width - RACER_WIDTH - PADDING
  343. }
  344. }
  345. }
  346. }
  347.  
  348. /////////////
  349. // Final //
  350. /////////////
  351.  
  352. container.append(racingMiniMap.view)
  353. raceContainer.before(container)// ==UserScript==
  354. // @name New Userscript
  355. // @namespace http://tampermonkey.net/
  356. // @version 0.1
  357. // @description try to take over the world!
  358. // @author You
  359. // @match https://www.nitrotype.com/garage
  360. // @icon https://www.google.com/s2/favicons?sz=64&domain=nitrotype.com
  361. // @grant none
  362. // ==/UserScript==
  363.  
  364. (function() {
  365. 'use strict';
  366.  
  367. // Your code here...
  368. })();