1import
2  os, math, strutils, gl, tables,
3  sfml, sfml_audio, sfml_colors, chipmunk, math_helpers,
4  input_helpers, animations, game_objects, sfml_stuff, map_filter,
5  sg_gui, sg_assets, sound_buffer, enet_client
6when defined(profiler):
7  import nimprof
8
9type
10  PPlayer* = ref TPlayer
11  TPlayer* = object
12    id: uint16
13    vehicle: PVehicle
14    spectator: bool
15    alias: string
16    nameTag: PText
17    items: seq[PItem]
18  PVehicle* = ref TVehicle
19  TVehicle* = object
20    body*:      chipmunk.PBody
21    shape*:     chipmunk.PShape
22    record*:   PVehicleRecord
23    sprite*:   PSprite
24    spriteRect*: TIntRect
25    occupant: PPlayer
26    when false:
27      position*: TVector2f
28      velocity*: TVector2f
29      angle*:    float
30  PItem* = ref object
31    record: PItemRecord
32    cooldown: float
33  PLiveBullet* = ref TLiveBullet ##represents a live bullet in the arena
34  TLiveBullet* = object
35    lifetime*: float
36    dead: bool
37    anim*: PAnimation
38    record*: PBulletRecord
39    fromPlayer*: PPlayer
40    trailDelay*: float
41    body: chipmunk.PBody
42    shape: chipmunk.PShape
43include vehicles
44const
45  LGrabbable*  = (1 shl 0).TLayers
46  LBorders*    = (1 shl 1).TLayers
47  LPlayer*     = ((1 shl 2) and LBorders.int).TLayers
48  LEnemy*      = ((1 shl 4) and LBorders.int).TLayers
49  LEnemyFire*  = (LPlayer).TLayers
50  LPlayerFire* = (LEnemy).TLayers
51  CTBullet = 1.TCollisionType
52  CTVehicle= 2.TCollisionType
53  ##temporary constants
54  W_LIMIT = 2.3
55  V_LIMIT = 35
56  MaxLocalBots = 3
57var
58  localPlayer: PPlayer
59  localBots: seq[PPlayer] = @[]
60  activeVehicle: PVehicle
61  myVehicles: seq[PVehicle] = @[]
62  objects: seq[PGameObject] = @[]
63  liveBullets: seq[PLiveBullet] = @[]
64  explosions: seq[PAnimation] = @[]
65  gameRunning = true
66  frameRate = newClock()
67  showStars = off
68  levelArea: TIntRect
69  videoMode: TVideoMode
70  window: PRenderWindow
71  worldView: PView
72  guiView: PView
73  space = newSpace()
74  ingameClient = newKeyClient("ingame")
75  specInputClient = newKeyClient("spec")
76  specGui = newGuiContainer()
77  stars: seq[PSpriteSheet] = @[]
78  playBtn: PButton
79  shipSelect = newGuiContainer()
80  delObjects: seq[int] = @[]
81  showShipSelect = false
82  myPosition: array[0..1, TVector3f] ##for audio positioning
83let
84  nameTagOffset = vec2f(0.0, 1.0)
85when defined(escapeMenuTest):
86  import browsers
87  var
88    escMenu = newGuiContainer(vec2f(100, 100))
89    escMenuOpen = false
90    pos = vec2f(0, 0)
91  escMenu.newButton("Some Website", pos, proc(b: PButton) =
92    openDefaultBrowser(getClientSettings().website))
93  pos.y += 20.0
94  escMenu.newButton("Back to Lobby", pos, proc(b: PButton) =
95    echo "herro")
96  proc toggleEscape() =
97    escMenuOpen = not escMenuOpen
98  ingameClient.registerHandler(KeyEscape, down, toggleEscape)
99  specInputClient.registerHandler(KeyEscape, down, toggleEscape)
100when defined(foo):
101  var mouseSprite: sfml.PCircleShape
102when defined(recordMode):
103  var
104    snapshots: seq[PImage] = @[]
105    isRecording = false
106  proc startRecording() =
107    if snapshots.len > 100: return
108    echo "Started recording"
109    isRecording = true
110  proc stopRecording() =
111    if isRecording:
112      echo "Stopped recording. ", snapshots.len, " images."
113    isRecording = false
114  proc zeroPad*(s: string; minLen: int): string =
115    if s.len < minLen:
116      result = repeat(0, minLen - s.len)
117      result.add s
118    else:
119      result = s
120  var
121    recordButton = newButton(
122      nil, text = "Record", position = vec2f(680, 50),
123      onClick = proc(b: PButton) = startRecording())
124
125proc newNameTag*(text: string): PText =
126  result = newText()
127  result.setFont(guiFont)
128  result.setCharacterSize(14)
129  result.setColor(Red)
130  result.setString(text)
131
132var debugText = newNameTag("Loading...")
133debugText.setPosition(vec2f(0.0, 600.0 - 50.0))
134
135when defined(showFPS):
136  var fpsText = newNameTag("0")
137  #fpsText.setCharacterSize(16)
138  fpsText.setPosition(vec2f(300.0, (800 - 50).float))
139
140proc mouseToSpace*(): TVector =
141  result = window.convertCoords(vec2i(getMousePos()), worldView).sfml2cp()
142
143proc explode*(b: PLiveBullet)
144## TCollisionBeginFunc
145proc collisionBulletPlayer(arb: PArbiter; space: PSpace;
146                            data: pointer): bool{.cdecl.} =
147  var
148    bullet = cast[PLiveBullet](arb.a.data)
149    target = cast[PVehicle](arb.b.data)
150  if target.occupant.isNil or target.occupant == bullet.fromPlayer: return
151  bullet.explode()
152
153proc angularDampingSim(body: PBody, gravity: TVector, damping, dt: CpFloat){.cdecl.} =
154  body.w -= (body.w * 0.98 * dt)
155  body.UpdateVelocity(gravity, damping, dt)
156
157proc initLevel() =
158  loadAllAssets()
159
160  if not space.isNil: space.destroy()
161  space = newSpace()
162  space.addCollisionHandler CTBullet, CTVehicle, collisionBulletPlayer,
163    nil, nil, nil, nil
164
165  let levelSettings = getLevelSettings()
166  levelArea.width = levelSettings.size.x
167  levelArea.height= levelSettings.size.y
168  let borderSeq = @[
169    vector(0, 0), vector(levelArea.width.float, 0.0),
170    vector(levelArea.width.float, levelArea.height.float), vector(0.0, levelArea.height.float)]
171  for i in 0..3:
172    var seg = space.addShape(
173      newSegmentShape(
174        space.staticBody,
175        borderSeq[i],
176        borderSeq[(i + 1) mod 4],
177        8.0))
178    seg.setElasticity 0.96
179    seg.setLayers(LBorders)
180  if levelSettings.starfield.len > 0:
181    showStars = true
182    for sprite in levelSettings.starfield:
183      sprite.tex.setRepeated(true)
184      sprite.sprite.setTextureRect(levelArea)
185      sprite.sprite.setOrigin(vec2f(0, 0))
186      stars.add(sprite)
187  var pos = vec2f(0.0, 0.0)
188  for veh in playableVehicles():
189    shipSelect.newButton(
190      veh.name,
191      position = pos,
192      onClick = proc(b: PButton) =
193        echo "-__-")
194    pos.y += 18.0
195
196
197proc newItem*(record: PItemRecord): PItem =
198  new(result)
199  result.record = record
200proc newItem*(name: string): PItem {.inline.} =
201  return newItem(fetchItm(name))
202proc canUse*(itm: PItem): bool =
203  if itm.cooldown > 0.0: return
204  return true
205proc update*(itm: PItem; dt: float) =
206  if itm.cooldown > 0:
207    itm.cooldown -= dt
208
209proc free(obj: PLiveBullet) =
210  obj.shape.free
211  obj.body.free
212  obj.record = nil
213
214
215template newExplosion(obj, animation) =
216  explosions.add(newAnimation(animation, AnimOnce, obj.body.getPos.cp2sfml, obj.body.getAngle))
217
218template newExplosion(obj, animation, angle) =
219  explosions.add(newAnimation(animation, AnimOnce, obj.body.getPos.cp2sfml, angle))
220
221proc explode*(b: PLiveBullet) =
222  if b.dead: return
223  b.dead = true
224  space.removeShape b.shape
225  space.removeBody b.body
226  if not b.record.explosion.anim.isNil:
227    newExplosion(b, b.record.explosion.anim)
228  playSound(b.record.explosion.sound, b.body.getPos())
229
230proc bulletUpdate(body: PBody, gravity: TVector, damping, dt: CpFloat){.cdecl.} =
231  body.UpdateVelocity(gravity, damping, dt)
232
233template getPhysical() {.dirty.} =
234  result.body = space.addBody(newBody(
235    record.physics.mass,
236    record.physics.moment))
237  result.shape = space.addShape(
238    chipmunk.newCircleShape(
239      result.body,
240      record.physics.radius,
241      VectorZero))
242
243proc newBullet*(record: PBulletRecord; fromPlayer: PPlayer): PLiveBullet =
244  new(result, free)
245  result.anim = newAnimation(record.anim, AnimLoop)
246  result.fromPlayer = fromPlayer
247  result.lifetime = record.lifetime
248  result.record = record
249  getPhysical()
250  if fromPlayer == localPlayer:
251    result.shape.setLayers(LPlayerFire)
252  else:
253    result.shape.setLayers(LEnemyFire)
254  result.shape.setCollisionType CTBullet
255  result.shape.setUserData(cast[ptr TLiveBullet](result))
256  let
257    fireAngle = fromPlayer.vehicle.body.getAngle()
258    fireAngleV = vectorForAngle(fireAngle)
259  result.body.setAngle fireAngle
260  result.body.setPos(fromPlayer.vehicle.body.getPos() + (fireAngleV * fromPlayer.vehicle.shape.getCircleRadius()))
261  #result.body.velocityFunc = bulletUpdate
262  result.body.setVel((fromPlayer.vehicle.body.getVel() * record.inheritVelocity) + (fireAngleV * record.baseVelocity))
263
264proc update*(b: PLiveBullet; dt: float): bool =
265  if b.dead: return true
266  b.lifetime -= dt
267  b.anim.next(dt)
268  #b.anim.sprite.setPosition(b.body.getPos.floor())
269  b.anim.setPos(b.body.getPos)
270  b.anim.setAngle(b.body.getAngle())
271  if b.lifetime <= 0.0:
272    b.explode()
273    return true
274  b.trailDelay -= dt
275  if b.trailDelay <= 0.0:
276    b.trailDelay += b.record.trail.timer
277    if b.record.trail.anim.isNil: return
278    newExplosion(b, b.record.trail.anim)
279proc draw*(window: PRenderWindow; b: PLiveBullet) {.inline.} =
280  draw(window, b.anim.sprite)
281
282
283proc free*(veh: PVehicle) =
284  ("Destroying vehicle " & veh.record.name).echo
285  destroy(veh.sprite)
286  if veh.shape.isNil: "Free'd vehicle's shape was NIL!".echo
287  else: space.removeShape(veh.shape)
288  if veh.body.isNil: "Free'd vehicle's BODY was NIL!".echo
289  else: space.removeBody(veh.body)
290  veh.body.free()
291  veh.shape.free()
292  veh.sprite = nil
293  veh.body = nil
294  veh.shape  = nil
295
296
297proc newVehicle*(record: PVehicleRecord): PVehicle =
298  echo("Creating " & record.name)
299  new(result, free)
300  result.record = record
301  result.sprite = result.record.anim.spriteSheet.sprite.copy()
302  result.spriteRect = result.sprite.getTextureRect()
303  getPhysical()
304  result.body.setAngVelLimit W_LIMIT
305  result.body.setVelLimit result.record.handling.topSpeed
306  result.body.velocityFunc = angularDampingSim
307  result.shape.setCollisionType CTVehicle
308  result.shape.setUserData(cast[ptr TVehicle](result))
309proc newVehicle*(name: string): PVehicle =
310  result = newVehicle(fetchVeh(name))
311
312proc update*(obj: PVehicle) =
313  obj.sprite.setPosition(obj.body.getPos.floor)
314  obj.spriteRect.left = (((-obj.body.getAngVel + W_LIMIT) / (W_LIMIT*2.0) * (obj.record.anim.spriteSheet.cols - 1).float).floor.int * obj.record.anim.spriteSheet.framew).cint
315  obj.spriteRect.top = ((obj.offsetAngle.wmod(TAU) / TAU) * obj.record.anim.spriteSheet.rows.float).floor.cint * obj.record.anim.spriteSheet.frameh.cint
316  obj.sprite.setTextureRect(obj.spriteRect)
317
318
319proc newPlayer*(alias: string = "poo"): PPlayer =
320  new(result)
321  result.spectator = true
322  result.alias     = alias
323  result.nameTag   = newNameTag(result.alias)
324  result.items     = @[]
325proc updateItems*(player: PPlayer, dt: float) =
326  for i in items(player.items):
327    update(i, dt)
328proc addItem*(player: PPlayer; name: string) =
329  player.items.add newItem(name)
330proc useItem*(player: PPlayer; slot: int) =
331  if slot > player.items.len - 1: return
332  let item = player.items[slot]
333  if item.canUse:
334    item.cooldown += item.record.cooldown
335    let b = newBullet(item.record.bullet, player)
336    liveBullets.add(b)
337    sound_buffer.playSound(item.record.useSound, b.body.getPos)
338
339proc update*(obj: PPlayer) =
340  if not obj.spectator:
341    obj.vehicle.update()
342    obj.nameTag.setPosition(obj.vehicle.body.getPos.floor + (nameTagOffset * (obj.vehicle.record.physics.radius + 5).cfloat))
343
344proc draw(window: PRenderWindow, player: PPlayer) {.inline.} =
345  if not player.spectator:
346    if player.vehicle != nil:
347      window.draw(player.vehicle.sprite)
348    window.draw(player.nameTag)
349
350proc setVehicle(p: PPlayer; v: PVehicle) =
351  p.vehicle = v  #sorry mom, this is just how things worked out ;(
352  if not v.isNil:
353    v.occupant = p
354
355proc createBot() =
356  if localBots.len < MaxLocalBots:
357    var bot = newPlayer("Dodo Brown")
358    bot.setVehicle(newVehicle("Turret0"))
359    if bot.isNil:
360      echo "BOT IS NIL"
361      return
362    elif bot.vehicle.isNil:
363      echo "BOT VEH IS NIL"
364      return
365    localBots.add(bot)
366    bot.vehicle.body.setPos(vector(100, 100))
367    echo "new bot at ", $bot.vehicle.body.getPos()
368
369var inputCursor = newVertexArray(sfml.Lines, 2)
370inputCursor[0].position = vec2f(10.0, 10.0)
371inputCursor[1].position = vec2f(50.0, 90.0)
372
373proc hasVehicle(p: PPlayer): bool {.inline.} =
374  result = not p.spectator and not p.vehicle.isNil
375
376proc setMyVehicle(v: PVehicle) {.inline.} =
377  activeVehicle = v
378  localPlayer.setVehicle v
379
380proc unspec() =
381  var veh = newVehicle("Turret0")
382  if not veh.isNil:
383    setMyVehicle veh
384    localPlayer.spectator = false
385    ingameClient.setActive
386    veh.body.setPos vector(100, 100)
387    veh.shape.setLayers(LPlayer)
388    when defined(debugWeps):
389      localPlayer.addItem("Mass Driver")
390      localPlayer.addItem("Neutron Bomb")
391      localPlayer.additem("Dem Lasers")
392      localPlayer.addItem("Mold Spore Beam")
393      localPlayer.addItem("Genericorp Mine")
394      localPlayer.addItem("Gravitic Bomb")
395proc spec() =
396  setMyVehicle nil
397  localPlayer.spectator = true
398  specInputClient.setActive
399
400var
401  specLimiter = newClock()
402  timeBetweenSpeccing = 1.0 #seconds
403proc toggleSpec() {.inline.} =
404  if specLimiter.getElapsedTime.asSeconds < timeBetweenSpeccing:
405    return
406  specLimiter.restart()
407  if localPlayer.isNil:
408    echo("OMG WTF PLAYER IS NILL!!")
409  elif localPlayer.spectator: unspec()
410  else: spec()
411
412proc addObject*(name: string) =
413  var o = newObject(name)
414  if not o.isNil:
415    echo "Adding object ", o
416    discard space.addBody(o.body)
417    discard space.addShape(o.shape)
418    o.shape.setLayers(LGrabbable)
419    objects.add(o)
420proc explode(obj: PGameObject) =
421  echo obj, " exploded"
422  let ind = objects.find(obj)
423  if ind != -1:
424    delObjects.add ind
425proc update(obj: PGameObject; dt: float) =
426  if not(obj.anim.next(dt)):
427    obj.explode()
428  else:
429    obj.anim.setPos(obj.body.getPos)
430    obj.anim.setAngle(obj.body.getAngle)
431
432proc toggleShipSelect() =
433  showShipSelect = not showShipSelect
434proc handleLClick() =
435  let pos = input_helpers.getMousePos()
436  when defined(escapeMenuTest):
437    if escMenuOpen:
438      escMenu.click(pos)
439      return
440  if showShipSelect:
441    shipSelect.click(pos)
442  if localPlayer.spectator:
443    specGui.click(pos)
444
445ingameClient.registerHandler(KeyF12, down, proc() = toggleSpec())
446ingameClient.registerHandler(KeyF11, down, toggleShipSelect)
447ingameClient.registerHandler(MouseLeft, down, handleLClick)
448when defined(recordMode):
449  if not dirExists("data/snapshots"):
450    createDir("data/snapshots")
451  ingameClient.registerHandler(keynum9, down, proc() =
452    if not isRecording: startRecording()
453    else: stopRecording())
454  ingameClient.registerHandler(keynum0, down, proc() =
455    if snapshots.len > 0 and not isRecording:
456      echo "Saving images (LOL)"
457      for i in 0..high(snapshots):
458        if not(snapshots[i].save("data/snapshots/image"&(zeroPad($i, 3))&".jpg")):
459          echo "Could not save"
460        snapshots[i].destroy()
461      snapshots.setLen 0)
462when defined(DebugKeys):
463  ingameClient.registerHandler MouseRight, down, proc() =
464    echo($activevehicle.body.getAngle.vectorForAngle())
465  ingameClient.registerHandler KeyBackslash, down, proc() =
466    createBot()
467  ingameClient.registerHandler(KeyNum1, down, proc() =
468    if localPlayer.items.len == 0:
469      localPlayer.addItem("Mass Driver")
470      echo "Gave you a mass driverz")
471  ingameClient.registerHandler(KeyL, down, proc() =
472    echo("len(livebullets) = ", len(livebullets)))
473  ingameClient.registerHandler(KeyRShift, down, proc() =
474    if keyPressed(KeyR):
475      echo("Friction: ", ff(activeVehicle.shape.getFriction()))
476      echo("Damping: ", ff(space.getDamping()))
477    elif keypressed(KeyM):
478      echo("Mass: ", activeVehicle.body.getMass.ff())
479      echo("Moment: ", activeVehicle.body.getMoment.ff())
480    elif keypressed(KeyI):
481      echo(repr(activeVehicle.record))
482    elif keyPressed(KeyH):
483      activeVehicle.body.setPos(vector(100.0, 100.0))
484      activeVehicle.body.setVel(VectorZero)
485    elif keyPressed(KeyComma):
486      activeVehicle.body.setPos mouseToSpace())
487  ingameClient.registerHandler(KeyY, down, proc() =
488    const looloo = ["Asteroid1", "Asteroid2"]
489    addObject(looloo[rand(looloo.len)]))
490  ingameClient.registerHandler(KeyO, down, proc() =
491    if objects.len == 0:
492      echo "Objects is empty"
493      return
494    for i, o in pairs(objects):
495      echo i, " ", o)
496  ingameClient.registerHandler(KeyLBracket, down, sound_buffer.report)
497  var
498    mouseJoint: PConstraint
499    mouseBody = space.addBody(newBody(CpInfinity, CpInfinity))
500  ingameClient.registerHandler(MouseMiddle, down, proc() =
501    var point = mouseToSpace()
502    var shape = space.pointQueryFirst(point, LGrabbable, 0)
503    if not mouseJoint.isNil:
504      space.removeConstraint mouseJoint
505      mouseJoint.destroy()
506      mouseJoint = nil
507    if shape.isNil:
508      return
509    let body = shape.getBody()
510    mouseJoint = space.addConstraint(
511      newPivotJoint(mouseBody, body, VectorZero, body.world2local(point)))
512    mouseJoint.maxForce = 50000.0
513    mouseJoint.errorBias = pow(1.0 - 0.15, 60))
514
515var specCameraSpeed = 5.0
516specInputClient.registerHandler(MouseLeft, down, handleLClick)
517specInputClient.registerHandler(KeyF11, down, toggleShipSelect)
518specInputClient.registerHandler(KeyF12, down, proc() = toggleSpec())
519specInputClient.registerHandler(KeyLShift, down, proc() = specCameraSpeed *= 2)
520specInputClient.registerHandler(KeyLShift, up, proc() = specCameraSpeed /= 2)
521
522specInputClient.registerHandler(KeyP, down, proc() =
523  echo("addObject(solar mold)")
524  addObject("Solar Mold"))
525
526proc resetForcesCB(body: PBody; data: pointer) {.cdecl.} =
527  body.resetForces()
528
529var frameCount= 0
530proc mainUpdate(dt: float) =
531  if localPlayer.spectator:
532    if keyPressed(KeyLeft):
533      worldView.move(vec2f(-1.0, 0.0) * specCameraSpeed)
534    elif keyPressed(KeyRight):
535      worldView.move(vec2f( 1.0, 0.0) * specCameraSpeed)
536    if keyPressed(KeyUp):
537      worldView.move(vec2f(0.0, -1.0) * specCameraSpeed)
538    elif keyPressed(KeyDown):
539      worldView.move(vec2f(0.0,  1.0) * specCameraSpeed)
540  elif not activeVehicle.isNil:
541    if keyPressed(KeyUp):
542      activeVehicle.accel(dt)
543    elif keyPressed(KeyDown):
544      activeVehicle.reverse(dt)
545    if keyPressed(KeyRight):
546      activeVehicle.turn_right(dt)
547    elif keyPressed(KeyLeft):
548      activeVehicle.turn_left(dt)
549    if keyPressed(Keyz):
550      activeVehicle.strafe_left(dt)
551    elif keyPressed(Keyx):
552      activeVehicle.strafe_right(dt)
553    if keyPressed(KeyLControl):
554      localPlayer.useItem 0
555    if keyPressed(KeyTab):
556      localPlayer.useItem 1
557    if keyPressed(KeyQ):
558      localPlayer.useItem 2
559    if keyPressed(KeyW):
560      localPlayer.useItem 3
561    if keyPressed(KeyA):
562      localPlayer.useItem 4
563    if keyPressed(sfml.KeyS):
564      localPlayer.useItem 5
565    if keyPressed(KeyD):
566      localPlayer.useItem 6
567    worldView.setCenter(activeVehicle.body.getPos.floor)#cp2sfml)
568
569  if localPlayer != nil:
570    localPlayer.update()
571    localPlayer.updateItems(dt)
572  for b in localBots:
573    b.update()
574
575  for o in items(objects):
576    o.update(dt)
577  for i in countdown(high(delObjects), 0):
578    objects.del i
579  delObjects.setLen 0
580
581  var i = 0
582  while i < len(liveBullets):
583    if liveBullets[i].update(dt):
584      liveBullets.del i
585    else:
586      inc i
587  i = 0
588  while i < len(explosions):
589    if explosions[i].next(dt): inc i
590    else: explosions.del i
591
592  when defined(DebugKeys):
593    mouseBody.setPos(mouseToSpace())
594
595  space.step(dt)
596  space.eachBody(resetForcesCB, nil)
597
598  when defined(foo):
599    var coords = window.convertCoords(vec2i(getMousePos()), worldView)
600    mouseSprite.setPosition(coords)
601
602  if localPlayer != nil and localPlayer.vehicle != nil:
603    let
604      pos = localPlayer.vehicle.body.getPos()
605      ang = localPlayer.vehicle.body.getAngle.vectorForAngle()
606    myPosition[0].x = pos.x
607    myPosition[0].z = pos.y
608    myPosition[1].x = ang.x
609    myPosition[1].z = ang.y
610    listenerSetPosition(myPosition[0])
611    listenerSetDirection(myPosition[1])
612
613  inc frameCount
614  when defined(showFPS):
615    if frameCount mod 60 == 0:
616      fpsText.setString($(1.0/dt).round)
617  if frameCount mod 250 == 0:
618    updateSoundBuffer()
619    frameCount = 0
620
621proc mainRender() =
622  window.clear(Black)
623  window.setView(worldView)
624
625  if showStars:
626    for star in stars:
627      window.draw(star.sprite)
628  window.draw(localPlayer)
629
630  for b in localBots:
631    window.draw(b)
632  for o in objects:
633    window.draw(o)
634
635  for b in explosions: window.draw(b)
636  for b in liveBullets: window.draw(b)
637
638  when defined(Foo):
639    window.draw(mouseSprite)
640
641  window.setView(guiView)
642
643  when defined(EscapeMenuTest):
644    if escMenuOpen:
645      window.draw escMenu
646  when defined(showFPS):
647    window.draw(fpsText)
648  when defined(recordMode):
649    window.draw(recordButton)
650
651  if localPlayer.spectator:
652    window.draw(specGui)
653  if showShipSelect: window.draw shipSelect
654  window.display()
655
656  when defined(recordMode):
657    if isRecording:
658      if snapshots.len < 100:
659        if frameCount mod 5 == 0:
660          snapshots.add(window.capture())
661      else: stopRecording()
662
663proc readyMainState() =
664  specInputClient.setActive()
665
666when true:
667  import parseopt
668
669  localPlayer = newPlayer()
670  lobbyInit()
671
672  videoMode = getClientSettings().resolution
673  window = newRenderWindow(videoMode, "sup", sfDefaultStyle)
674  window.setFrameRateLimit 60
675
676  worldView = window.getView.copy()
677  guiView = worldView.copy()
678  shipSelect.setPosition vec2f(665.0, 50.0)
679
680  when defined(foo):
681    mouseSprite = sfml.newCircleShape(14)
682    mouseSprite.setFillColor Transparent
683    mouseSprite.setOutlineColor RoyalBlue
684    mouseSprite.setOutlineThickness 1.4
685    mouseSprite.setOrigin vec2f(14, 14)
686
687  lobbyReady()
688  playBtn = specGui.newButton(
689    "Unspec - F12", position = vec2f(680.0, 8.0), onClick = proc(b: PButton) =
690      toggleSpec())
691
692  block:
693    var bPlayOffline = false
694    for kind, key, val in getOpt():
695      case kind
696      of cmdArgument:
697        if key == "offline": bPlayOffline = true
698      else:
699        echo "Invalid argument ", key, " ", val
700    if bPlayOffline:
701      playoffline(nil)
702
703  gameRunning = true
704  while gameRunning:
705    for event in window.filterEvents:
706      if event.kind == EvtClosed:
707        gameRunning = false
708        break
709      elif event.kind == EvtMouseWheelMoved and getActiveState() == Field:
710        if event.mouseWheel.delta == 1:
711          worldView.zoom(0.9)
712        else:
713          worldView.zoom(1.1)
714    let dt = frameRate.restart.asMilliSeconds().float / 1000.0
715    case getActiveState()
716    of Field:
717      mainUpdate(dt)
718      mainRender()
719    of Lobby:
720      lobbyUpdate(dt)
721      lobbyDraw(window)
722    else:
723      initLevel()
724      echo("Done? lol")
725      doneWithSaidTransition()
726      readyMainState()
727