game.lua 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. local love = require("compat")
  2. local game = {
  3. GAMESTATE = "",
  4. SERVERGAMESTATE = "",
  5. usersMoved = {},
  6. newUserPositions = {},
  7. crashedUsers = {}, -- remembers how many rounds the user has to wait
  8. time = 0,
  9. maxTime = 0,
  10. timerEvent = nil,
  11. time2 = 0,
  12. maxTime2 = 0,
  13. timerEvent2 = nil,
  14. roundTime = 10,
  15. winnerID = nil
  16. }
  17. local tease = {
  18. "Server: Hah, you crashed, ",
  19. "Server: Drunk driving, ",
  20. "Server: Come on, ",
  21. "Server: ",
  22. }
  23. local tease2 = {
  24. "!",
  25. "!",
  26. ", what was that?!",
  27. ", that was embarrassing...",
  28. }
  29. -- Possible gamestates:
  30. -- "startup": camera should move to start line
  31. -- "move": players are allowed to make their move.
  32. -- "wait": waiting for server or other players, or for animtion
  33. local scr = nil
  34. local panel = require( "panel" )
  35. function game:init()
  36. scr = ui:newScreen( "game" )
  37. scr:addPanel( "topPanel",
  38. 0, 0,
  39. love.graphics.getWidth(), 35 )
  40. scr:addFunction( "topPanel", "leave", 20, 0, "Leave", "q", game.close )
  41. scr:addFunction( "topPanel", "help", love.graphics.getWidth() -160, 0, "Help", "h", game.toggleHelp )
  42. self.winnerPanel = panel:new( love.graphics.getWidth()/3, love.graphics.getHeight()/2 - 100,
  43. love.graphics.getWidth()/3, love.graphics.getFont():getHeight() + 10 )
  44. self.userPanel = panel:new( 0, 0, 450, 19 )
  45. end
  46. function game:toggleHelp()
  47. if scr:panelByName( "helpPanel" ) ~= nil then
  48. scr:removePanel( "helpPanel" )
  49. else
  50. local width = 300
  51. local x = love.graphics.getWidth() - 370
  52. local y = 0
  53. scr:addPanel( "helpPanel",
  54. x,
  55. 80,
  56. width, 410 )
  57. scr:addHeader( "helpPanel", "h1", 0, y, "Help:" )
  58. y = y + 30
  59. scr:addText( "helpPanel", "helpText", 10, y, nil, 7, "Move your car by clicking the fields around it. Once every player has planned their move, the cars start driving. The places you can drive to this round depends on how your carr drove during the last movement phase: The same vector you moved last round will be added onto your car's position; from the resulting point, all neighbouring fields are available.\nBe the first to finsih the race, but don't leave the road or crash into other cars!\n\nUse W-A-S-D or cursor keys to move the camera, use mouse wheel or + and - to zoom.\n\nCrashes:\nStay on the road! If you leave the road, you will crash. The time you have to wait afterwards depends on how fast you were.\nYou can crash into other player's cars if you click on the same position. Whoever clicks second, crashes." )
  60. scr:addFunction( "helpPanel", "close", 10, 360, "Close", "h", nil )
  61. end
  62. end
  63. function game:show()
  64. STATE = "Game"
  65. game.winnerID = nil
  66. map:removeAllCars()
  67. if server then
  68. -- For exery player who's playing, add one start position to the list of available positions:
  69. local availablePositions = {}
  70. local i = 1
  71. for id, u in pairs( server:getUsers() ) do
  72. if u.customData.ingame then
  73. -- add one start position to the list of available positions:
  74. table.insert( availablePositions, map.startPositions[i] )
  75. i = i + 1
  76. end
  77. end
  78. for id, u in pairs( server:getUsers() ) do
  79. if u.customData.ingame then
  80. local col = {
  81. u.customData.red,
  82. u.customData.green,
  83. u.customData.blue,
  84. 255
  85. }
  86. local x, y = 0,0
  87. local startPosNum = math.random(1,#availablePositions)
  88. if availablePositions[startPosNum] then
  89. x, y = availablePositions[startPosNum].x, availablePositions[startPosNum].y
  90. table.remove( availablePositions, startPosNum )
  91. end
  92. map:newCar( u.id, x, y, col )
  93. server:send( CMD.NEW_CAR, u.id .. "|" .. x .. "|" .. y )
  94. else
  95. -- If the client is not racing this round, let him look like he's
  96. -- already moved:
  97. server:setUserValue( u, "moved", true )
  98. end
  99. server:setUserValue( u, "crashed", false )
  100. server:setUserValue( u, "numCrashes", 0 )
  101. server:setUserValue( u, "topCrashSpeed", 0 )
  102. server:setUserValue( u, "speed", 0 )
  103. server:setUserValue( u, "maxSpeed", 0 )
  104. end
  105. game.crashedUsers = {}
  106. -- Start the round after 3 seconds!
  107. self.timerEvent = function()
  108. game:startMovementRound()
  109. end
  110. self.maxTime = 3
  111. self.time = 0
  112. server:send( CMD.SERVERCHAT,
  113. "Game starting. You have " .. ROUND_TIME .. " seconds for each move." )
  114. updateAdvertisementInfo()
  115. end
  116. if not DEDICATED then
  117. ui:setActiveScreen( nil )
  118. stats:clear()
  119. -- Do a cool camera startup swing:
  120. map:camSwingAbort()
  121. map:camSwingToPos( map.startProjPoint.x, map.startProjPoint.y, 1.5 )
  122. map:camZoom( 0.6, 1.5 )
  123. self.timerEvent2 = function()
  124. if client then
  125. game:camToCar( client:getID() )
  126. end
  127. end
  128. self.maxTime2 = 2
  129. self.time2 = 0
  130. ui:setActiveScreen( scr )
  131. end
  132. end
  133. function game:camToCar( id )
  134. if client then
  135. if map:hasCar( id ) then
  136. local x, y = map:getCarPos( id )
  137. x = x*GRIDSIZE
  138. y = y*GRIDSIZE
  139. map:camSwingToPos( x, y, 1 )
  140. map:camZoom( 0.5, 1 )
  141. end
  142. end
  143. end
  144. function game:update( dt )
  145. map:update( dt )
  146. -- Timer1:
  147. if self.maxTime > 0 then
  148. self.time = self.time + dt
  149. if self.time >= self.maxTime then
  150. self.maxTime = 0
  151. self.time = 0
  152. self.timerEvent()
  153. end
  154. end
  155. -- Timer2:
  156. if self.maxTime2 > 0 then
  157. self.time2 = self.time2 + dt
  158. if self.time2 >= self.maxTime2 then
  159. self.maxTime2 = 0
  160. self.time2 = 0
  161. self.timerEvent2()
  162. end
  163. end
  164. end
  165. function game:draw()
  166. if client then
  167. map:draw()
  168. if self.GAMESTATE == "move" then
  169. map:drawTargetPoints( client:getID() )
  170. end
  171. --[[if love.keyboard.isDown( "space" ) then
  172. map:drawCarInfo()
  173. end]]
  174. game:drawUserList()
  175. if game.winnerID then
  176. local users = network:getUsers()
  177. if users and users[game.winnerID] then
  178. self.winnerPanel:draw()
  179. love.graphics.setColor( 64,255,64, 255 )
  180. love.graphics.printf( users[game.winnerID].playerName .. " wins the round!",
  181. love.graphics.getWidth()/3, love.graphics.getHeight()/2-95,
  182. love.graphics.getWidth()/3, "center" )
  183. end
  184. end
  185. map:drawDebug()
  186. end
  187. end
  188. function game:drawUserList()
  189. -- Print list of users:
  190. local users, num = network:getUsers()
  191. local x, y = 20, 60
  192. local i = 1
  193. if client and users then
  194. for k, u in pairs( users ) do
  195. self.userPanel:draw( x - 5, y - 3 )
  196. love.graphics.setColor( 255,255,255, 255 )
  197. love.graphics.printf( i .. ":", x, y, 20, "right" )
  198. love.graphics.printf( u.playerName, x + 25, y, 250, "left" )
  199. local dx = love.graphics.getFont():getWidth( u.playerName ) + 40
  200. local lapString = ""
  201. if map:hasCar( u.id ) then
  202. local speedString = (u.customData.speed or 0).. " km/h"
  203. love.graphics.print( speedString, x + dx, y )
  204. dx = dx + love.graphics.getFont():getWidth( speedString ) + 15
  205. lapString = "Lap: " .. map:getCarRound( u.id ) .. "/" .. (self.numberOfLaps or LAPS)
  206. love.graphics.print( lapString, x + dx, y )
  207. end
  208. -- Show crashed users in list:
  209. if u.customData.crashed == true then
  210. love.graphics.setColor( 255, 128, 128, 255 )
  211. dx = dx + love.graphics.getFont():getWidth( lapString ) + 20
  212. local rounds = u.customData.waitingRounds or 1
  213. love.graphics.print( "[Crashed! (" .. rounds .. ")]", x + dx, y )
  214. elseif not u.customData.moved == true then
  215. love.graphics.setColor( 255, 255, 128, 255 )
  216. dx = dx + love.graphics.getFont():getWidth( lapString ) + 20
  217. love.graphics.print( "[Waiting for move]", x + dx, y )
  218. elseif not u.customData.ingame == true then
  219. love.graphics.setColor( 128, 128, 255, 255 )
  220. dx = dx + love.graphics.getFont():getWidth( lapString )
  221. love.graphics.print( "[spectate]", x + dx, y )
  222. end
  223. if map:hasCar( u.id) then
  224. map:getCar( u.id ):drawOnUI( 435, y + 5, 0.2 )
  225. end
  226. y = y + 20
  227. i = i + 1
  228. end
  229. end
  230. end
  231. function game:keypressed( key )
  232. end
  233. function game:mousepressed( x, y, button )
  234. if button == "l" then
  235. if client then
  236. if self.GAMESTATE == "move" then
  237. -- Turn screen coordinates into grid coordinates:
  238. local gX, gY = map:screenToGrid( x, y )
  239. gX = math.floor( gX + 0.5 )
  240. gY = math.floor( gY + 0.5 )
  241. print( "Grid position clicked:", gX, gY )
  242. if map:isThisAValidTargetPos( client:getID(), gX, gY ) then
  243. print("\t->is valid.")
  244. self:sendNewCarPosition( gX, gY )
  245. end
  246. end
  247. end
  248. end
  249. end
  250. function game:setState( state )
  251. self.GAMESTATE = state
  252. --[[if self.GAMESTATE == "move" then
  253. if client then
  254. map:resetCarNextMovement( client:getID() )
  255. end
  256. end]]
  257. end
  258. function game:newCar( msg )
  259. if not server then
  260. local id, x, y = msg:match( "(.*)|(.*)|(.*)")
  261. id = tonumber(id)
  262. x = tonumber(x)
  263. y = tonumber(y)
  264. local users = client:getUsers()
  265. local u = users[id]
  266. if u then
  267. local col = {
  268. u.customData.red,
  269. u.customData.green,
  270. u.customData.blue,
  271. 255
  272. }
  273. map:newCar( id, x, y, col )
  274. end
  275. end
  276. end
  277. function game:sendNewCarPosition( x, y )
  278. -- CLIENT ONLY!
  279. if client then
  280. client:send( CMD.MOVE_CAR, x .. "|" .. y )
  281. print("\t->Sent:", CMD.MOVE_CAR, x .. "|" .. y, os.time())
  282. --map:setCarNextMovement( client:getID(), x, y )
  283. end
  284. end
  285. function game:startMovementRound()
  286. --SERVER ONLY!
  287. if server then
  288. self.SERVERGAMESTATE = "move"
  289. game.usersMoved = {}
  290. for k, u in pairs( server:getUsers() ) do
  291. -- On all crashed users, count one down because we're starting a new round...
  292. if game.crashedUsers[u.id] then
  293. -- If this is the first crash round:
  294. --[[if game.crashedUsers[u.id] == SKIP_ROUNDS_ON_CRASH + 1 then
  295. if math.random(20) == 1 then
  296. local i = math.random(#tease)
  297. server:send( CMD.SERVERCHAT, tease[i] .. u.playerName .. tease2[i] )
  298. end
  299. end]]
  300. game.crashedUsers[u.id] = game.crashedUsers[u.id] - 1
  301. if game.crashedUsers[u.id] <= 0 then
  302. -- If I've waited long enough, let me rejoin the game:
  303. server:setUserValue( u, "crashed", false )
  304. game.crashedUsers[u.id] = nil
  305. end
  306. end
  307. -- If a user crashed, let everyone know:
  308. if game.crashedUsers[u.id] then
  309. server:setUserValue( u, "waitingRounds", game.crashedUsers[u.id] )
  310. server:setUserValue( u, "crashed", true )
  311. -- Consider this user to be finished...
  312. game.usersMoved[u.id] = true
  313. else
  314. -- Only let users move if they haven't crashed and aren't spectating:
  315. if u.customData.ingame then
  316. server:send( CMD.GAMESTATE, "move", u )
  317. server:setUserValue( u, "moved", false )
  318. end
  319. end
  320. end
  321. self.timerEvent = function() game:roundTimeout() end
  322. self.maxTime = ROUND_TIME
  323. self.time = 0
  324. print("----------------------------------------")
  325. print("New round starting:", os.time())
  326. -- If all users crashed, continue:
  327. game:checkForRoundEnd()
  328. end
  329. end
  330. function game:roundTimeout()
  331. local found = false
  332. if server then
  333. for k, u in pairs( server:getUsers() ) do
  334. -- If the user did not move their car in time, move it according to last velocity:
  335. if not self.usersMoved[u.id] and u.customData.ingame == true then
  336. print("Round timeout for user:", u.id )
  337. print("\tUser Data:")
  338. for k, v in pairs( u.customData ) do
  339. print("\t", k, v)
  340. end
  341. local x, y = map:getCarCenterVel( u.id )
  342. -- Check for crashes:
  343. game:validateCarMovement( u.id, x, y )
  344. found = true
  345. end
  346. end
  347. if found then
  348. server:send( CMD.SERVERCHAT, "Server: Time up. Moving on..." )
  349. end
  350. end
  351. end
  352. function game:moveAll()
  353. if server then
  354. for k, u in pairs( server:getUsers() ) do
  355. if u.customData.ingame and map:hasCar( u.id ) then
  356. --local x, y = map:getCarPos( u.id )
  357. local x,y = self.newUserPositions[u.id].x, self.newUserPositions[u.id].y
  358. server:send( CMD.MOVE_CAR, u.id .. "|" .. x .. "|" .. y )
  359. -- Calculate and send car speed:
  360. local car = map:getCar( u.id )
  361. local vX = x - map:TransCoordPtG(car.x)
  362. local vY = y - map:TransCoordPtG(car.y)
  363. local speed = math.floor( math.sqrt(vX*vX + vY*vY)*100 )/10
  364. server:setUserValue( u, "speed", speed )
  365. if not u.customData.maxSpeed or u.customData.maxSpeed < speed then
  366. server:setUserValue( u, "maxSpeed", speed )
  367. end
  368. if DEDICATED then
  369. map:setCarPosDirectly( u.id, x, y )
  370. end
  371. end
  372. end
  373. self.timerEvent = function()
  374. game:checkForWinner()
  375. if not game.winnerID then
  376. game:startMovementRound()
  377. else
  378. game:sendWinner( game.winnerID )
  379. local winner = server:getUsers()[game.winnerID]
  380. if winner then
  381. server:setUserValue( winner, "roundsWon", winner.customData.roundsWon + 1 )
  382. end
  383. self.timerEvent = game.sendBackToLobby
  384. self.maxTime = 5
  385. self.time = 0
  386. if DEDICATED then
  387. if server:getUsers()[game.winnerID] and
  388. server:getUsers()[game.winnerID].playerName then
  389. utility.log( "[" .. os.time() .. "] Winner: " ..
  390. server:getUsers()[game.winnerID].playerName )
  391. end
  392. end
  393. end
  394. end
  395. self.maxTime = 1.2
  396. self.time = 0
  397. end
  398. end
  399. function game:validateCarMovement( id, x, y )
  400. --SERVER ONLY!
  401. if server then
  402. -- if this user has not moved yet:
  403. if self.usersMoved[id] == nil then
  404. -- map:setCarPos( id, x, y )
  405. --map:setCarPosDirectly(id, x, y) --car-id as number, pos as Gridpos
  406. local oldX, oldY = map:getCarPos( id )
  407. local user = server:getUsers()[id]
  408. print("\tValidating at:", os.time())
  409. print("\tPrevious positions:", id, map:getCar(id), oldX, oldY)
  410. if map:isThisAValidTargetPos( id, x, y ) then
  411. print("\tPossition is valid.")
  412. else
  413. print("\tPossition is NOT valid. Traceback:", debug.traceback())
  414. server:send( CMD.SERVERCHAT,
  415. "DEBUG: Something went wrong. " .. server:getUsers()[id].playerName .. "'s movement was invalid.")
  416. end
  417. if not oldX or not oldY then
  418. print("\tWARNING: oldX or oldY aren't set!", debug.traceback())
  419. server:send( CMD.SERVERCHAT, "DEBUG: somthing went wrong with the car position of " .. server:getUsers()[id].playerName .. "." )
  420. oldX, oldY = 0, 0
  421. end
  422. -- Step along the path and check if there's a collision. If so, stop there.
  423. local p = {x = oldX, y = oldY }
  424. local diff = {x = x-oldX, y = y-oldY}
  425. local dist = utility.length( diff )
  426. local speed = math.floor( dist*100 )/10
  427. print("\tDelta:", diff.x, diff.y)
  428. print("\tSpeed:", speed )
  429. diff = utility.normalize(diff)
  430. print("\tDist:", dist)
  431. -- Step forward in steps of 0.5 length - this makes sure no small gaps are jumped!
  432. local crashed, crashSiteFound = false, false
  433. local movedDist = 0
  434. for l = 0.5, dist, 0.5 do
  435. p = {x = oldX + l*diff.x, y = oldY + l*diff.y }
  436. if not map:isPointOnRoad( p.x*GRIDSIZE, p.y*GRIDSIZE, 0 ) then
  437. crashed = true
  438. break
  439. end
  440. movedDist = l
  441. end
  442. -- Also check the end position!!
  443. if not crashed then
  444. -- I have managed to move the entire distance!
  445. movedDist = dist
  446. if not map:isPointOnRoad( x*GRIDSIZE, y*GRIDSIZE, 0 ) then
  447. crashed = true
  448. end
  449. end
  450. local crashedIntoCar = false
  451. -- If I managed to move the full distance, then check if there's already a car there
  452. if movedDist == dist then
  453. for id2, bool in pairs( self.usersMoved ) do
  454. if bool then
  455. if self.newUserPositions[id2] then
  456. if self.newUserPositions[id2].x == x and
  457. self.newUserPositions[id2].y == y then
  458. crashedIntoCar = true
  459. crashed = true
  460. break
  461. end
  462. end
  463. end
  464. end
  465. end
  466. if crashed then
  467. print("\tCrashed")
  468. -- Step backwards:
  469. for lBack = movedDist-0.5, 0, -0.5 do
  470. p = {x = oldX + lBack*diff.x, y = oldY + lBack*diff.y }
  471. p.x = math.floor(p.x)
  472. p.y = math.floor(p.y)
  473. if map:isPointOnRoad( p.x*GRIDSIZE, p.y*GRIDSIZE, 0 ) then
  474. crashSiteFound = true
  475. x, y = p.x, p.y
  476. break
  477. end
  478. end
  479. -- remembers how many rounds the user has to wait
  480. if crashedIntoCar then
  481. game.crashedUsers[id] = SKIP_ROUNDS_CAR_CAR + 1
  482. else
  483. game.crashedUsers[id] = game:speedToCrashTimeout( speed )
  484. end
  485. if user then
  486. server:setUserValue( user, "numCrashes", user.customData.numCrashes + 1 )
  487. if speed > user.customData.topCrashSpeed then
  488. server:setUserValue( user, "topCrashSpeed", speed )
  489. end
  490. end
  491. end
  492. if crashed and not crashSiteFound then
  493. x, y = oldX, oldY
  494. print("\tNo crash site found, placed to old pos:", oldX, oldY)
  495. end
  496. self.usersMoved[id] = true
  497. self.newUserPositions[id] = {x=x, y=y}
  498. if user then
  499. -- tell this user to wait!
  500. server:send( CMD.GAMESTATE, "wait", user )
  501. -- Let all users know this user has already moved:
  502. server:setUserValue( user, "moved", true )
  503. end
  504. game:checkForRoundEnd()
  505. end
  506. end
  507. end
  508. function game:checkForRoundEnd()
  509. -- Check if all users have sent their move:
  510. local doneMoving = true
  511. for k, u in pairs( server:getUsers() ) do
  512. if u.customData.ingame and not self.usersMoved[u.id] then
  513. doneMoving = false
  514. break
  515. end
  516. end
  517. -- If all users have sent the move, go on to next round:
  518. if doneMoving then
  519. self:moveAll()
  520. end
  521. end
  522. -- This function checks for a winner. A winner is found if a user has passed the startline
  523. -- LAP times. If more than one players have done so, the winner is the one who is _furthest_
  524. -- from the start line (i.e. crossed the line with the most speed/first)
  525. function game:checkForWinner()
  526. if server and not game.winnerID then
  527. local potentialWinners = {}
  528. for k, u in pairs( server:getUsers() ) do
  529. if u.customData.ingame then
  530. if map:getCarRound( u.id ) >= LAPS + 1 then
  531. table.insert( potentialWinners, u.id )
  532. end
  533. end
  534. end
  535. -- Find the winner who has the least
  536. local winnerID
  537. local maxDist = -math.huge
  538. for k, id in pairs( potentialWinners ) do
  539. local car = map:getCar( id )
  540. if car then
  541. local x,y = car:getPos()
  542. local p = {x=x, y=y}
  543. local dist = utility.linePointDist( map.startLine.p1,map.startLine.p2, p )
  544. print( x, y, map.startLine.p1.x, map.startLine.p1.y )
  545. if dist > maxDist then -- so far the first player:
  546. winnerID = id
  547. maxDist = dist
  548. end
  549. end
  550. end
  551. if winnerID then
  552. game.winnerID = winnerID
  553. print("WINNER FOUND!", game.winnerID )
  554. else
  555. -- Fallback, just in case:
  556. if #potentialWinners > 0 then
  557. game.winnerID = potentialWinners[1]
  558. end
  559. end
  560. end
  561. end
  562. function game:moveCar( msg )
  563. -- CLIENT ONLY!
  564. if client then
  565. local id, x, y = msg:match( "(.*)|(.*)|(.*)" )
  566. id = tonumber(id)
  567. x = tonumber(x)
  568. y = tonumber(y)
  569. map:setCarPos( id, x, y )
  570. end
  571. end
  572. function game:playerWins( msg )
  573. if client then
  574. game.winnerID = tonumber(msg)
  575. game:camToCar( game.winnerID )
  576. self.timerEvent2 = game.zoomOut
  577. self.maxTime2 = 3
  578. self.time2 = 0
  579. end
  580. end
  581. function game:sendWinner()
  582. if server then
  583. server:send( CMD.PLAYER_WINS, game.winnerID )
  584. end
  585. end
  586. function game:sendBackToLobby()
  587. if server then
  588. -- Create and send statustics:
  589. game:sendStats()
  590. server:send( CMD.BACK_TO_LOBBY, "" )
  591. if DEDICATED then
  592. lobby:show() -- must be called AFTER generating the stats!
  593. end
  594. end
  595. end
  596. function game:sendStats()
  597. -- SERVER ONLY!
  598. if not server then return end
  599. local users = network:getUsers()
  600. -- Create and send top speed string:
  601. local statStr = "Top Speed:|km/h|"
  602. for k, u in pairs( users ) do
  603. if u.customData.ingame then
  604. statStr = statStr .. u.id .. " " .. u.customData.maxSpeed .. "|"
  605. end
  606. end
  607. server:send( CMD.STAT, statStr )
  608. -- Create and send crashes string:
  609. local statStr = "Crashes:||"
  610. for k, u in pairs( users ) do
  611. if u.customData.ingame then
  612. statStr = statStr .. u.id .. " " .. u.customData.numCrashes .. "|"
  613. end
  614. end
  615. server:send( CMD.STAT, statStr )
  616. -- Create and send top speed at crashes:
  617. local statStr = "Worst crash:|km/h|"
  618. for k, u in pairs( users ) do
  619. if u.customData.ingame then
  620. statStr = statStr .. u.id .. " " .. u.customData.topCrashSpeed .. "|"
  621. end
  622. end
  623. server:send( CMD.STAT, statStr )
  624. -- Create and send top speed at crashes:
  625. local statStr = "Rounds won:||"
  626. for k, u in pairs( users ) do
  627. if u.customData.ingame then
  628. statStr = statStr .. u.id .. " " .. u.customData.roundsWon .. "|"
  629. end
  630. end
  631. server:send( CMD.STAT, statStr )
  632. end
  633. function game:zoomOut()
  634. local cX = map.Boundary.minX + (map.Boundary.maxX - map.Boundary.minX)*0.5
  635. local cY = map.Boundary.minY + (map.Boundary.maxY - map.Boundary.minY)*0.5
  636. map:camSwingToPos( cX, cY )
  637. end
  638. function game:synchronizeCars( user )
  639. if server then
  640. for k, u in pairs( server:getUsers() ) do
  641. if u.customData.ingame then
  642. if map:hasCar( u.id ) then
  643. local c = map:getCar( u.id )
  644. server:send( CMD.NEW_CAR, u.id .. "|" .. c.targetX .. "|" .. c.targetY, user )
  645. end
  646. end
  647. end
  648. end
  649. end
  650. function game:speedToCrashTimeout( speed )
  651. return SKIP_ROUNDS_COLLISION_MIN + math.floor(speed*SKIP_ROUNDS_COLLISION_PER_10_KMH/10) + 1
  652. end
  653. function game:getNumUsersPlaying()
  654. local num = 0
  655. for k, u in pairs( server:getUsers() ) do
  656. if u.customData.ingame == true then
  657. num = num + 1
  658. end
  659. end
  660. return num
  661. end
  662. function game:close()
  663. local commands = {}
  664. commands[1] = { txt = "Yes", key = "y", event = function() network:closeConnection() end }
  665. commands[2] = { txt = "No", key = "n" }
  666. scr:newMsgBox( "Game in progress!", "Are you sure you want to leave?", nil, nil, nil, commands)
  667. end
  668. return game