import os, math, strutils, gl, tables, sfml, sfml_audio, sfml_colors, chipmunk, math_helpers, input_helpers, animations, game_objects, sfml_stuff, map_filter, sg_gui, sg_assets, sound_buffer, enet_client when defined(profiler): import nimprof {.deadCodeElim: on.} type PPlayer* = ref TPlayer TPlayer* = object id: uint16 vehicle: PVehicle spectator: bool alias: string nameTag: PText items: seq[PItem] PVehicle* = ref TVehicle TVehicle* = object body*: chipmunk.PBody shape*: chipmunk.PShape record*: PVehicleRecord sprite*: PSprite spriteRect*: TIntRect occupant: PPlayer when false: position*: TVector2f velocity*: TVector2f angle*: float PItem* = ref object record: PItemRecord cooldown: float PLiveBullet* = ref TLiveBullet ##represents a live bullet in the arena TLiveBullet* = object lifetime*: float dead: bool anim*: PAnimation record*: PBulletRecord fromPlayer*: PPlayer trailDelay*: float body: chipmunk.PBody shape: chipmunk.PShape include vehicles const LGrabbable* = (1 shl 0).TLayers LBorders* = (1 shl 1).TLayers LPlayer* = ((1 shl 2) and LBorders.int).TLayers LEnemy* = ((1 shl 4) and LBorders.int).TLayers LEnemyFire* = (LPlayer).TLayers LPlayerFire* = (LEnemy).TLayers CTBullet = 1.TCollisionType CTVehicle= 2.TCollisionType ##temporary constants W_LIMIT = 2.3 V_LIMIT = 35 MaxLocalBots = 3 var localPlayer: PPlayer localBots: seq[PPlayer] = @[] activeVehicle: PVehicle myVehicles: seq[PVehicle] = @[] objects: seq[PGameObject] = @[] liveBullets: seq[PLiveBullet] = @[] explosions: seq[PAnimation] = @[] gameRunning = true frameRate = newClock() showStars = off levelArea: TIntRect videoMode: TVideoMode window: PRenderWindow worldView: PView guiView: PView space = newSpace() ingameClient = newKeyClient("ingame") specInputClient = newKeyClient("spec") specGui = newGuiContainer() stars: seq[PSpriteSheet] = @[] playBtn: PButton shipSelect = newGuiContainer() delObjects: seq[int] = @[] showShipSelect = false myPosition: array[0..1, TVector3f] ##for audio positioning let nameTagOffset = vec2f(0.0, 1.0) when defined(escapeMenuTest): import browsers var escMenu = newGuiContainer(vec2f(100, 100)) escMenuOpen = false pos = vec2f(0, 0) escMenu.newButton("Some Website", pos, proc(b: PButton) = openDefaultBrowser(getClientSettings().website)) pos.y += 20.0 escMenu.newButton("Back to Lobby", pos, proc(b: PButton) = echo "herro") proc toggleEscape() = escMenuOpen = not escMenuOpen ingameClient.registerHandler(KeyEscape, down, toggleEscape) specInputClient.registerHandler(KeyEscape, down, toggleEscape) when defined(foo): var mouseSprite: sfml.PCircleShape when defined(recordMode): var snapshots: seq[PImage] = @[] isRecording = false proc startRecording() = if snapshots.len > 100: return echo "Started recording" isRecording = true proc stopRecording() = if isRecording: echo "Stopped recording. ", snapshots.len, " images." isRecording = false proc zeroPad*(s: string; minLen: int): string = if s.len < minLen: result = repeat(0, minLen - s.len) result.add s else: result = s var recordButton = newButton( nil, text = "Record", position = vec2f(680, 50), onClick = proc(b: PButton) = startRecording()) proc newNameTag*(text: string): PText = result = newText() result.setFont(guiFont) result.setCharacterSize(14) result.setColor(Red) result.setString(text) var debugText = newNameTag("Loading...") debugText.setPosition(vec2f(0.0, 600.0 - 50.0)) when defined(showFPS): var fpsText = newNameTag("0") #fpsText.setCharacterSize(16) fpsText.setPosition(vec2f(300.0, (800 - 50).float)) proc mouseToSpace*(): TVector = result = window.convertCoords(vec2i(getMousePos()), worldView).sfml2cp() proc explode*(b: PLiveBullet) ## TCollisionBeginFunc proc collisionBulletPlayer(arb: PArbiter; space: PSpace; data: pointer): bool{.cdecl.} = var bullet = cast[PLiveBullet](arb.a.data) target = cast[PVehicle](arb.b.data) if target.occupant.isNil or target.occupant == bullet.fromPlayer: return bullet.explode() proc angularDampingSim(body: PBody, gravity: TVector, damping, dt: CpFloat){.cdecl.} = body.w -= (body.w * 0.98 * dt) body.UpdateVelocity(gravity, damping, dt) proc initLevel() = loadAllAssets() if not space.isNil: space.destroy() space = newSpace() space.addCollisionHandler CTBullet, CTVehicle, collisionBulletPlayer, nil, nil, nil, nil let levelSettings = getLevelSettings() levelArea.width = levelSettings.size.x levelArea.height= levelSettings.size.y let borderSeq = @[ vector(0, 0), vector(levelArea.width.float, 0.0), vector(levelArea.width.float, levelArea.height.float), vector(0.0, levelArea.height.float)] for i in 0..3: var seg = space.addShape( newSegmentShape( space.staticBody, borderSeq[i], borderSeq[(i + 1) mod 4], 8.0)) seg.setElasticity 0.96 seg.setLayers(LBorders) if levelSettings.starfield.len > 0: showStars = true for sprite in levelSettings.starfield: sprite.tex.setRepeated(true) sprite.sprite.setTextureRect(levelArea) sprite.sprite.setOrigin(vec2f(0, 0)) stars.add(sprite) var pos = vec2f(0.0, 0.0) for veh in playableVehicles(): shipSelect.newButton( veh.name, position = pos, onClick = proc(b: PButton) = echo "-__-") pos.y += 18.0 proc newItem*(record: PItemRecord): PItem = new(result) result.record = record proc newItem*(name: string): PItem {.inline.} = return newItem(fetchItm(name)) proc canUse*(itm: PItem): bool = if itm.cooldown > 0.0: return return true proc update*(itm: PItem; dt: float) = if itm.cooldown > 0: itm.cooldown -= dt proc free(obj: PLiveBullet) = obj.shape.free obj.body.free obj.record = nil template newExplosion(obj, animation) = explosions.add(newAnimation(animation, AnimOnce, obj.body.getPos.cp2sfml, obj.body.getAngle)) template newExplosion(obj, animation, angle) = explosions.add(newAnimation(animation, AnimOnce, obj.body.getPos.cp2sfml, angle)) proc explode*(b: PLiveBullet) = if b.dead: return b.dead = true space.removeShape b.shape space.removeBody b.body if not b.record.explosion.anim.isNil: newExplosion(b, b.record.explosion.anim) playSound(b.record.explosion.sound, b.body.getPos()) proc bulletUpdate(body: PBody, gravity: TVector, damping, dt: CpFloat){.cdecl.} = body.UpdateVelocity(gravity, damping, dt) template getPhysical() {.immediate.} = result.body = space.addBody(newBody( record.physics.mass, record.physics.moment)) result.shape = space.addShape( chipmunk.newCircleShape( result.body, record.physics.radius, VectorZero)) proc newBullet*(record: PBulletRecord; fromPlayer: PPlayer): PLiveBullet = new(result, free) result.anim = newAnimation(record.anim, AnimLoop) result.fromPlayer = fromPlayer result.lifetime = record.lifetime result.record = record getPhysical() if fromPlayer == localPlayer: result.shape.setLayers(LPlayerFire) else: result.shape.setLayers(LEnemyFire) result.shape.setCollisionType CTBullet result.shape.setUserData(cast[ptr TLiveBullet](result)) let fireAngle = fromPlayer.vehicle.body.getAngle() fireAngleV = vectorForAngle(fireAngle) result.body.setAngle fireAngle result.body.setPos(fromPlayer.vehicle.body.getPos() + (fireAngleV * fromPlayer.vehicle.shape.getCircleRadius())) #result.body.velocityFunc = bulletUpdate result.body.setVel((fromPlayer.vehicle.body.getVel() * record.inheritVelocity) + (fireAngleV * record.baseVelocity)) proc update*(b: PLiveBullet; dt: float): bool = if b.dead: return true b.lifetime -= dt b.anim.next(dt) #b.anim.sprite.setPosition(b.body.getPos.floor()) b.anim.setPos(b.body.getPos) b.anim.setAngle(b.body.getAngle()) if b.lifetime <= 0.0: b.explode() return true b.trailDelay -= dt if b.trailDelay <= 0.0: b.trailDelay += b.record.trail.timer if b.record.trail.anim.isNil: return newExplosion(b, b.record.trail.anim) proc draw*(window: PRenderWindow; b: PLiveBullet) {.inline.} = draw(window, b.anim.sprite) proc free*(veh: PVehicle) = ("Destroying vehicle "& veh.record.name).echo destroy(veh.sprite) if veh.shape.isNil: "Free'd vehicle's shape was NIL!".echo else: space.removeShape(veh.shape) if veh.body.isNil: "Free'd vehicle's BODY was NIL!".echo else: space.removeBody(veh.body) veh.body.free() veh.shape.free() veh.sprite = nil veh.body = nil veh.shape = nil proc newVehicle*(record: PVehicleRecord): PVehicle = echo("Creating "& record.name) new(result, free) result.record = record result.sprite = result.record.anim.spriteSheet.sprite.copy() result.spriteRect = result.sprite.getTextureRect() getPhysical() result.body.setAngVelLimit W_LIMIT result.body.setVelLimit result.record.handling.topSpeed result.body.velocityFunc = angularDampingSim result.shape.setCollisionType CTVehicle result.shape.setUserData(cast[ptr TVehicle](result)) proc newVehicle*(name: string): PVehicle = result = newVehicle(fetchVeh(name)) proc update*(obj: PVehicle) = obj.sprite.setPosition(obj.body.getPos.floor) 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 obj.spriteRect.top = ((obj.offsetAngle.wmod(TAU) / TAU) * obj.record.anim.spriteSheet.rows.float).floor.cint * obj.record.anim.spriteSheet.frameh.cint obj.sprite.setTextureRect(obj.spriteRect) proc newPlayer*(alias: string = "poo"): PPlayer = new(result) result.spectator = true result.alias = alias result.nameTag = newNameTag(result.alias) result.items = @[] proc updateItems*(player: PPlayer, dt: float) = for i in items(player.items): update(i, dt) proc addItem*(player: PPlayer; name: string) = player.items.add newItem(name) proc useItem*(player: PPlayer; slot: int) = if slot > player.items.len - 1: return let item = player.items[slot] if item.canUse: item.cooldown += item.record.cooldown let b = newBullet(item.record.bullet, player) liveBullets.add(b) sound_buffer.playSound(item.record.useSound, b.body.getPos) proc update*(obj: PPlayer) = if not obj.spectator: obj.vehicle.update() obj.nameTag.setPosition(obj.vehicle.body.getPos.floor + (nameTagOffset * (obj.vehicle.record.physics.radius + 5).cfloat)) proc draw(window: PRenderWindow, player: PPlayer) {.inline.} = if not player.spectator: if player.vehicle != nil: window.draw(player.vehicle.sprite) window.draw(player.nameTag) proc setVehicle(p: PPlayer; v: PVehicle) = p.vehicle = v #sorry mom, this is just how things worked out ;( if not v.isNil: v.occupant = p proc createBot() = if localBots.len < MaxLocalBots: var bot = newPlayer("Dodo Brown") bot.setVehicle(newVehicle("Turret0")) if bot.isNil: echo "BOT IS NIL" return elif bot.vehicle.isNil: echo "BOT VEH IS NIL" return localBots.add(bot) bot.vehicle.body.setPos(vector(100, 100)) echo "new bot at ", $bot.vehicle.body.getPos() var inputCursor = newVertexArray(sfml.Lines, 2) inputCursor[0].position = vec2f(10.0, 10.0) inputCursor[1].position = vec2f(50.0, 90.0) proc hasVehicle(p: PPlayer): bool {.inline.} = result = not p.spectator and not p.vehicle.isNil proc setMyVehicle(v: PVehicle) {.inline.} = activeVehicle = v localPlayer.setVehicle v proc unspec() = var veh = newVehicle("Turret0") if not veh.isNil: setMyVehicle veh localPlayer.spectator = false ingameClient.setActive veh.body.setPos vector(100, 100) veh.shape.setLayers(LPlayer) when defined(debugWeps): localPlayer.addItem("Mass Driver") localPlayer.addItem("Neutron Bomb") localPlayer.additem("Dem Lasers") localPlayer.addItem("Mold Spore Beam") localPlayer.addItem("Genericorp Mine") localPlayer.addItem("Gravitic Bomb") proc spec() = setMyVehicle nil localPlayer.spectator = true specInputClient.setActive var specLimiter = newClock() timeBetweenSpeccing = 1.0 #seconds proc toggleSpec() {.inline.} = if specLimiter.getElapsedTime.asSeconds < timeBetweenSpeccing: return specLimiter.restart() if localPlayer.isNil: echo("OMG WTF PLAYER IS NILL!!") elif localPlayer.spectator: unspec() else: spec() proc addObject*(name: string) = var o = newObject(name) if not o.isNil: echo "Adding object ", o discard space.addBody(o.body) discard space.addShape(o.shape) o.shape.setLayers(LGrabbable) objects.add(o) proc explode(obj: PGameObject) = echo obj, " exploded" let ind = objects.find(obj) if ind != -1: delObjects.add ind proc update(obj: PGameObject; dt: float) = if not(obj.anim.next(dt)): obj.explode() else: obj.anim.setPos(obj.body.getPos) obj.anim.setAngle(obj.body.getAngle) proc toggleShipSelect() = showShipSelect = not showShipSelect proc handleLClick() = let pos = input_helpers.getMousePos() when defined(escapeMenuTest): if escMenuOpen: escMenu.click(pos) return if showShipSelect: shipSelect.click(pos) if localPlayer.spectator: specGui.click(pos) ingameClient.registerHandler(KeyF12, down, proc() = toggleSpec()) ingameClient.registerHandler(KeyF11, down, toggleShipSelect) ingameClient.registerHandler(MouseLeft, down, handleLClick) when defined(recordMode): if not existsDir("data/snapshots"): createDir("data/snapshots") ingameClient.registerHandler(keynum9, down, proc() = if not isRecording: startRecording() else: stopRecording()) ingameClient.registerHandler(keynum0, down, proc() = if snapshots.len > 0 and not isRecording: echo "Saving images (LOL)" for i in 0..high(snapshots): if not(snapshots[i].save("data/snapshots/image"&(zeroPad($i, 3))&".jpg")): echo "Could not save" snapshots[i].destroy() snapshots.setLen 0) when defined(DebugKeys): ingameClient.registerHandler MouseRight, down, proc() = echo($activevehicle.body.getAngle.vectorForAngle()) ingameClient.registerHandler KeyBackslash, down, proc() = createBot() ingameClient.registerHandler(KeyNum1, down, proc() = if localPlayer.items.len == 0: localPlayer.addItem("Mass Driver") echo "Gave you a mass driverz") ingameClient.registerHandler(KeyL, down, proc() = echo("len(livebullets) = ", len(livebullets))) ingameClient.registerHandler(KeyRShift, down, proc() = if keyPressed(KeyR): echo("Friction: ", ff(activeVehicle.shape.getFriction())) echo("Damping: ", ff(space.getDamping())) elif keypressed(KeyM): echo("Mass: ", activeVehicle.body.getMass.ff()) echo("Moment: ", activeVehicle.body.getMoment.ff()) elif keypressed(KeyI): echo(repr(activeVehicle.record)) elif keyPressed(KeyH): activeVehicle.body.setPos(vector(100.0, 100.0)) activeVehicle.body.setVel(VectorZero) elif keyPressed(KeyComma): activeVehicle.body.setPos mouseToSpace()) ingameClient.registerHandler(KeyY, down, proc() = const looloo = ["Asteroid1", "Asteroid2"] addObject(looloo[random(looloo.len)])) ingameClient.registerHandler(KeyO, down, proc() = if objects.len == 0: echo "Objects is empty" return for i, o in pairs(objects): echo i, " ", o) ingameClient.registerHandler(KeyLBracket, down, sound_buffer.report) var mouseJoint: PConstraint mouseBody = space.addBody(newBody(CpInfinity, CpInfinity)) ingameClient.registerHandler(MouseMiddle, down, proc() = var point = mouseToSpace() var shape = space.pointQueryFirst(point, LGrabbable, 0) if not mouseJoint.isNil: space.removeConstraint mouseJoint mouseJoint.destroy() mouseJoint = nil if shape.isNil: return let body = shape.getBody() mouseJoint = space.addConstraint( newPivotJoint(mouseBody, body, VectorZero, body.world2local(point))) mouseJoint.maxForce = 50000.0 mouseJoint.errorBias = pow(1.0 - 0.15, 60)) var specCameraSpeed = 5.0 specInputClient.registerHandler(MouseLeft, down, handleLClick) specInputClient.registerHandler(KeyF11, down, toggleShipSelect) specInputClient.registerHandler(KeyF12, down, proc() = toggleSpec()) specInputClient.registerHandler(KeyLShift, down, proc() = specCameraSpeed *= 2) specInputClient.registerHandler(KeyLShift, up, proc() = specCameraSpeed /= 2) specInputClient.registerHandler(KeyP, down, proc() = echo("addObject(solar mold)") addObject("Solar Mold")) proc resetForcesCB(body: PBody; data: pointer) {.cdecl.} = body.resetForces() var frameCount= 0 proc mainUpdate(dt: float) = if localPlayer.spectator: if keyPressed(KeyLeft): worldView.move(vec2f(-1.0, 0.0) * specCameraSpeed) elif keyPressed(KeyRight): worldView.move(vec2f( 1.0, 0.0) * specCameraSpeed) if keyPressed(KeyUp): worldView.move(vec2f(0.0, -1.0) * specCameraSpeed) elif keyPressed(KeyDown): worldView.move(vec2f(0.0, 1.0) * specCameraSpeed) elif not activeVehicle.isNil: if keyPressed(KeyUp): activeVehicle.accel(dt) elif keyPressed(KeyDown): activeVehicle.reverse(dt) if keyPressed(KeyRight): activeVehicle.turn_right(dt) elif keyPressed(KeyLeft): activeVehicle.turn_left(dt) if keyPressed(Keyz): activeVehicle.strafe_left(dt) elif keyPressed(Keyx): activeVehicle.strafe_right(dt) if keyPressed(KeyLControl): localPlayer.useItem 0 if keyPressed(KeyTab): localPlayer.useItem 1 if keyPressed(KeyQ): localPlayer.useItem 2 if keyPressed(KeyW): localPlayer.useItem 3 if keyPressed(KeyA): localPlayer.useItem 4 if keyPressed(sfml.KeyS): localPlayer.useItem 5 if keyPressed(KeyD): localPlayer.useItem 6 worldView.setCenter(activeVehicle.body.getPos.floor)#cp2sfml) if localPlayer != nil: localPlayer.update() localPlayer.updateItems(dt) for b in localBots: b.update() for o in items(objects): o.update(dt) for i in countdown(high(delObjects), 0): objects.del i delObjects.setLen 0 var i = 0 while i < len(liveBullets): if liveBullets[i].update(dt): liveBullets.del i else: inc i i = 0 while i < len(explosions): if explosions[i].next(dt): inc i else: explosions.del i when defined(DebugKeys): mouseBody.setPos(mouseToSpace()) space.step(dt) space.eachBody(resetForcesCB, nil) when defined(foo): var coords = window.convertCoords(vec2i(getMousePos()), worldView) mouseSprite.setPosition(coords) if localPlayer != nil and localPlayer.vehicle != nil: let pos = localPlayer.vehicle.body.getPos() ang = localPlayer.vehicle.body.getAngle.vectorForAngle() myPosition[0].x = pos.x myPosition[0].z = pos.y myPosition[1].x = ang.x myPosition[1].z = ang.y listenerSetPosition(myPosition[0]) listenerSetDirection(myPosition[1]) inc frameCount when defined(showFPS): if frameCount mod 60 == 0: fpsText.setString($(1.0/dt).round) if frameCount mod 250 == 0: updateSoundBuffer() frameCount = 0 proc mainRender() = window.clear(Black) window.setView(worldView) if showStars: for star in stars: window.draw(star.sprite) window.draw(localPlayer) for b in localBots: window.draw(b) for o in objects: window.draw(o) for b in explosions: window.draw(b) for b in liveBullets: window.draw(b) when defined(Foo): window.draw(mouseSprite) window.setView(guiView) when defined(EscapeMenuTest): if escMenuOpen: window.draw escMenu when defined(showFPS): window.draw(fpsText) when defined(recordMode): window.draw(recordButton) if localPlayer.spectator: window.draw(specGui) if showShipSelect: window.draw shipSelect window.display() when defined(recordMode): if isRecording: if snapshots.len < 100: if frameCount mod 5 == 0: snapshots.add(window.capture()) else: stopRecording() proc readyMainState() = specInputClient.setActive() when isMainModule: import parseopt localPlayer = newPlayer() lobbyInit() videoMode = getClientSettings().resolution window = newRenderWindow(videoMode, "sup", sfDefaultStyle) window.setFrameRateLimit 60 worldView = window.getView.copy() guiView = worldView.copy() shipSelect.setPosition vec2f(665.0, 50.0) when defined(foo): mouseSprite = sfml.newCircleShape(14) mouseSprite.setFillColor Transparent mouseSprite.setOutlineColor RoyalBlue mouseSprite.setOutlineThickness 1.4 mouseSprite.setOrigin vec2f(14, 14) lobbyReady() playBtn = specGui.newButton( "Unspec - F12", position = vec2f(680.0, 8.0), onClick = proc(b: PButton) = toggleSpec()) block: var bPlayOffline = false for kind, key, val in getOpt(): case kind of cmdArgument: if key == "offline": bPlayOffline = true else: echo "Invalid argument ", key, " ", val if bPlayOffline: playoffline(nil) gameRunning = true while gameRunning: for event in window.filterEvents: if event.kind == EvtClosed: gameRunning = false break elif event.kind == EvtMouseWheelMoved and getActiveState() == Field: if event.mouseWheel.delta == 1: worldView.zoom(0.9) else: worldView.zoom(1.1) let dt = frameRate.restart.asMilliSeconds().float / 1000.0 case getActiveState() of Field: mainUpdate(dt) mainRender() of Lobby: lobbyUpdate(dt) lobbyDraw(window) else: initLevel() echo("Done? lol") doneWithSaidTransition() readyMainState()