added manyloc test suite; --path now relative to project dir if not absolute

This commit is contained in:
Araq 2013-04-13 21:55:02 +02:00
commit 75b508032b
51 changed files with 9286 additions and 5 deletions

View file

@ -0,0 +1,75 @@
import
math,
sfml, chipmunk,
sg_assets, sfml_stuff, math_helpers
type
PAnimation* = ref TAnimation
TAnimation* = object
sprite*: PSprite
record*: PAnimationRecord
delay*: float
index*: int
direction*: int
spriteRect*: TIntRect
style*: TAnimationStyle
TAnimationStyle* = enum
AnimLoop = 0'i8, AnimBounce, AnimOnce
proc setPos*(obj: PAnimation; pos: TVector) {.inline.}
proc setPos*(obj: PAnimation; pos: TVector2f) {.inline.}
proc setAngle*(obj: PAnimation; radians: float) {.inline.}
proc free*(obj: PAnimation) =
obj.sprite.destroy()
obj.record = nil
proc newAnimation*(src: PAnimationRecord; style: TAnimationStyle): PAnimation =
new(result, free)
result.sprite = src.spriteSheet.sprite.copy()
result.record = src
result.delay = src.delay
result.index = 0
result.direction = 1
result.spriteRect = result.sprite.getTextureRect()
result.style = style
proc newAnimation*(src: PAnimationRecord; style: TAnimationStyle;
pos: TVector2f; angle: float): PAnimation =
result = newAnimation(src, style)
result.setPos pos
setAngle(result, angle)
proc next*(obj: PAnimation; dt: float): bool {.discardable.} =
## step the animation. Returns false if the object is out of frames
result = true
obj.delay -= dt
if obj.delay <= 0.0:
obj.delay += obj.record.delay
obj.index += obj.direction
#if obj.index > (obj.record.spriteSheet.cols - 1) or obj.index < 0:
if not(obj.index in 0..(obj.record.spriteSheet.cols - 1)):
case obj.style
of AnimOnce:
return false
of AnimBounce:
obj.direction *= -1
obj.index += obj.direction * 2
of AnimLoop:
obj.index = 0
obj.spriteRect.left = obj.index.cint * obj.record.spriteSheet.frameW.cint
obj.sprite.setTextureRect obj.spriteRect
proc setPos*(obj: PAnimation; pos: TVector) =
setPosition(obj.sprite, pos.floor())
proc setPos*(obj: PAnimation; pos: TVector2f) =
setPosition(obj.sprite, pos)
proc setAngle*(obj: PAnimation; radians: float) =
let rads = (radians + obj.record.angle).wmod(TAU)
if obj.record.spriteSheet.rows > 1:
## (rotation percent * rows).floor * frameheight
obj.spriteRect.top = (rads / TAU * obj.record.spriteSheet.rows.float).floor.cint * obj.record.spriteSheet.frameh.cint
obj.sprite.setTextureRect obj.spriteRect
else:
setRotation(obj.sprite, degrees(rads)) #stupid sfml, who uses degrees these days? -__-
proc draw*(window: PRenderWindow; obj: PAnimation) {.inline.} =
window.draw(obj.sprite)

View file

@ -0,0 +1,142 @@
import
tables, sg_packets, enet, estreams, sg_gui, sfml,
zlib_helpers, md5, sg_assets, os
type
PServer* = ptr TServer
TServer* = object
connected*: bool
addy: enet.TAddress
host*: PHost
peer*: PPeer
handlers*: TTable[char, TScPktHandler]
TScPktHandler* = proc(serv: PServer; buffer: PBuffer)
TFileTransfer = object
fileName: string
assetType: TAssetType
fullLen: int
pos: int32
data: string
readyToSave: bool
var
currentFileTransfer: TFileTransfer
downloadProgress* = newButton(nil, "", vec2f(0,0), nil)
currentFileTransfer.data = ""
proc addHandler*(serv: PServer; packetType: char; handler: TScPktHandler) =
serv.handlers[packetType] = handler
proc newServer*(): PServer =
result = cast[PServer](alloc0(sizeof(TServer)))
result.connected = false
result.host = createHost(nil, 1, 2, 0, 0)
result.handlers = initTable[char, TScPktHandler](32)
proc connect*(serv: PServer; host: string; port: int16; error: var string): bool =
if setHost(serv.addy, host) != 0:
error = "Could not resolve host "
error.add host
return false
serv.addy.port = port.cushort
serv.peer = serv.host.connect(serv.addy, 2, 0)
if serv.peer.isNil:
error = "Could not connect to host "
error.add host
return false
return true
proc send*[T](serv: PServer; packetType: char; pkt: var T) =
if serv.connected:
var b = newBuffer(100)
b.write packetType
b.pack pkt
serv.peer.send(0.cuchar, b, FlagUnsequenced)
proc sendPubChat*(server: PServer; msg: string) =
var chat = newCsChat("", msg)
server.send HChat, chat
proc handlePackets*(server: PServer; buf: PBuffer) =
while not buf.atEnd():
let typ = readChar(buf)
if server.handlers.hasKey(typ):
server.handlers[typ](server, buf)
else:
break
proc updateFileProgress*() =
let progress = currentFileTransfer.pos / currentFileTransfer.fullLen
downloadProgress.bg.setSize(vec2f(progress * 100, 20))
downloadProgress.setString($currentFileTransfer.pos &'/'& $currentFileTransfer.fullLen)
## HFileTransfer
proc handleFilePartRecv*(serv: PServer; buffer: PBuffer) {.procvar.} =
var
f = readScFileTransfer(buffer)
updateFileProgress()
if not(f.pos == currentFileTransfer.pos):
echo "returning early from filepartrecv"
return ##issues, probably
if currentFileTransfer.data.len == 0:
echo "setting current file size"
currentFileTransfer.data.setLen f.fileSize
let len = f.data.len
copymem(
addr currentFileTransfer.data[f.pos],
addr f.data[0],
len)
currentFileTransfer.pos = f.pos + len.int32
if currentFileTransfer.pos == f.fileSize: #file should be done, rizzight
currentFileTransfer.data = uncompress(
currentFileTransfer.data, currentFileTransfer.fullLen)
currentFileTransfer.readyToSave = true
var resp: CsFileChallenge
resp.checksum = toMD5(currentFileTransfer.data)
serv.send HFileChallenge, resp
echo "responded with challenge (ready to save)"
else:
var resp = newCsFilepartAck(currentFileTransfer.pos)
serv.send HFileTransfer, resp
echo "responded for next part"
proc saveCurrentFile() =
if not currentFileTransfer.readyToSave: return
let
path = expandPath(currentFileTransfer.assetType, currentFileTransfer.fileName)
parent = parentDir(path)
if not existsDir(parent):
createDir(parent)
echo("Created dir")
writeFile path, currentFIleTransfer.data
echo "Write file"
## HChallengeResult
proc handleFileChallengeResult*(serv: PServer; buffer: PBuffer) {.procvar.} =
var res = readScChallengeResult(buffer).status
echo "got challnege result: ", res
if res and currentFileTransfer.readyToSave:
echo "saving"
saveCurrentFile()
else:
currentFileTransfer.readyToSave = false
currentFileTransfer.pos = 0
echo "REsetting current file"
## HFileCHallenge
proc handleFileChallenge*(serv: PServer; buffer: PBuffer) {.procvar.} =
var
challenge = readScFileChallenge(buffer)
path = expandPath(challenge)
resp: CsFileChallenge
if not existsFile(path):
resp.needFile = true
echo "Got file challenge, need file."
else:
resp.checksum = toMD5(readFile(path))
echo "got file challenge, sending sum"
currentFileTransfer.fileName = challenge.file
currentFileTransfer.assetType = challenge.assetType
currentFileTransfer.fullLen = challenge.fullLen.int
currentFileTransfer.pos = 0
currentFileTransfer.data.setLen 0
currentFileTransfer.readyToSave = false
serv.send HFileChallenge, resp

View file

@ -0,0 +1,126 @@
import endians
proc swapEndian16*(outp, inp: pointer) =
## copies `inp` to `outp` swapping bytes. Both buffers are supposed to
## contain at least 2 bytes.
var i = cast[cstring](inp)
var o = cast[cstring](outp)
o[0] = i[1]
o[1] = i[0]
when cpuEndian == bigEndian:
proc bigEndian16(outp, inp: pointer) {.inline.} = copyMem(outp, inp, 2)
else:
proc bigEndian16*(outp, inp: pointer) {.inline.} = swapEndian16(outp, inp)
import enet
type
PBuffer* = ref object
data*: string
pos: int
proc free(b: PBuffer) =
GCunref b.data
proc newBuffer*(len: int): PBuffer =
new result, free
result.data = newString(len)
proc newBuffer*(pkt: PPacket): PBuffer =
new result, free
result.data = newString(pkt.dataLength)
copyMem(addr result.data[0], pkt.data, pkt.dataLength)
proc toPacket*(buffer: PBuffer; flags: TPacketFlag): PPacket =
buffer.data.setLen buffer.pos
result = createPacket(cstring(buffer.data), buffer.pos, flags)
proc isDirty*(buffer: PBuffer): bool {.inline.} =
result = (buffer.pos != 0)
proc atEnd*(buffer: PBuffer): bool {.inline.} =
result = (buffer.pos == buffer.data.len)
proc reset*(buffer: PBuffer) {.inline.} =
buffer.pos = 0
proc flush*(buf: PBuffer) =
buf.pos = 0
buf.data.setLen(0)
proc send*(peer: PPeer; channel: cuchar; buf: PBuffer; flags: TPacketFlag): cint {.discardable.} =
result = send(peer, channel, buf.toPacket(flags))
proc read*[T: int16|uint16](buffer: PBuffer; outp: var T) =
bigEndian16(addr outp, addr buffer.data[buffer.pos])
inc buffer.pos, 2
proc read*[T: float32|int32|uint32](buffer: PBuffer; outp: var T) =
bigEndian32(addr outp, addr buffer.data[buffer.pos])
inc buffer.pos, 4
proc read*[T: float64|int64|uint64](buffer: PBuffer; outp: var T) =
bigEndian64(addr outp, addr buffer.data[buffer.pos])
inc buffer.pos, 8
proc read*[T: int8|uint8|byte|bool|char](buffer: PBuffer; outp: var T) =
copyMem(addr outp, addr buffer.data[buffer.pos], 1)
inc buffer.pos, 1
proc writeBE*[T: int16|uint16](buffer: PBuffer; val: var T) =
setLen buffer.data, buffer.pos + 2
bigEndian16(addr buffer.data[buffer.pos], addr val)
inc buffer.pos, 2
proc writeBE*[T: int32|uint32|float32](buffer: PBuffer; val: var T) =
setLen buffer.data, buffer.pos + 4
bigEndian32(addr buffer.data[buffer.pos], addr val)
inc buffer.pos, 4
proc writeBE*[T: int64|uint64|float64](buffer: PBuffer; val: var T) =
setLen buffer.data, buffer.pos + 8
bigEndian64(addr buffer.data[buffer.pos], addr val)
inc buffer.pos, 8
proc writeBE*[T: char|int8|uint8|byte|bool](buffer: PBuffer; val: var T) =
setLen buffer.data, buffer.pos + 1
copyMem(addr buffer.data[buffer.pos], addr val, 1)
inc buffer.pos, 1
proc write*(buffer: PBuffer; val: var string) =
var length = len(val).uint16
writeBE buffer, length
setLen buffer.data, buffer.pos + length.int
copyMem(addr buffer.data[buffer.pos], addr val[0], length.int)
inc buffer.pos, length.int
proc write*[T: TNumber|bool|char|byte](buffer: PBuffer; val: T) =
var v: T
shallowCopy v, val
writeBE buffer, v
proc readInt8*(buffer: PBuffer): int8 =
read buffer, result
proc readInt16*(buffer: PBuffer): int16 =
read buffer, result
proc readInt32*(buffer: PBuffer): int32 =
read buffer, result
proc readInt64*(buffer: PBuffer): int64 =
read buffer, result
proc readFloat32*(buffer: PBuffer): float32 =
read buffer, result
proc readFloat64*(buffer: PBuffer): float64 =
read buffer, result
proc readStr*(buffer: PBuffer): string =
let len = readInt16(buffer).int
result = ""
if len > 0:
result.setLen len
copyMem(addr result[0], addr buffer.data[buffer.pos], len)
inc buffer.pos, len
proc readChar*(buffer: PBuffer): char {.inline.} = return readInt8(buffer).char
proc readBool*(buffer: PBuffer): bool {.inline.} = return readInt8(buffer).bool
when isMainModule:
var b = newBuffer(100)
var str = "hello there"
b.write str
echo(repr(b))
b.pos = 0
echo(repr(b.readStr()))
b.flush()
echo "flushed"
b.writeC([1,2,3])
echo(repr(b))

View file

@ -0,0 +1,34 @@
import chipmunk, sfml, animations, sg_assets
type
PGameObject* = ref TGameObject
TGameObject = object
body*: chipmunk.PBody
shape*: chipmunk.PShape
record*: PObjectRecord
anim*: PAnimation
proc `$`*(obj: PGameObject): string =
result = "<Object "
result.add obj.record.name
result.add ' '
result.add($obj.body.getpos())
result.add '>'
proc free(obj: PGameObject) =
obj.record = nil
free(obj.anim)
obj.anim = nil
proc newObject*(record: PObjectRecord): PGameObject =
if record.isNil: return nil
new(result, free)
result.record = record
result.anim = newAnimation(record.anim, AnimLoop)
when false:
result.sprite = record.anim.spriteSheet.sprite.copy()
result.body = newBody(result.record.physics.mass, 10.0)
result.shape = chipmunk.newCircleShape(result.body, result.record.physics.radius, vectorZero)
result.body.setPos(vector(100, 100))
proc newObject*(name: string): PGameObject =
result = newObject(fetchObj(name))
proc draw*(window: PRenderWindow, obj: PGameObject) {.inline.} =
window.draw(obj.anim.sprite)

View file

@ -0,0 +1,23 @@
type
PIDGen*[T: Ordinal] = ref TIDGen[T]
TIDGen*[T: Ordinal] = object
max: T
freeIDs: seq[T]
EOutOfIDs* = object of EInvalidKey
#proc free[T](idg: PIDgen[T]) =
# result.freeIDs = nil
proc newIDGen*[T: Ordinal](): PIDGen[T] =
new(result)#, free)
result.max = 0.T
result.freeIDs = @[]
proc next*[T](idg: PIDGen[T]): T =
if idg.freeIDs.len > 0:
result = idg.freeIDs.pop
elif idg.max < high(T)-T(1):
inc idg.max
result = idg.max
else:
raise newException(EOutOfIDs, "ID generator hit max value")
proc del*[T](idg: PIDGen[T]; id: T) =
idg.freeIDs.add id

View file

@ -0,0 +1,138 @@
import
sfml, tables, hashes
type
TKeyEventKind* = enum down, up
TInputFinishedProc* = proc()
TKeyCallback = proc()
PKeyClient* = ref object
onKeyDown: TTable[int32, TKeyCallback]
onKeyUp: TTable[int32, TKeyCallback]
name: string
PTextInput* = ref object
text*: string
cursor: int
onEnter: TInputFinishedProc
var
keyState: array[-MouseButtonCount.int32 .. KeyCount.int32, bool]
mousePos: TVector2f
activeClient: PKeyClient = nil
activeInput: PTextInput = nil
proc setActive*(client: PKeyClient) =
activeClient = client
echo("** set active client ", client.name)
proc newKeyClient*(name: string = "unnamed", setactive = false): PKeyClient =
new(result)
result.onKeyDown = initTable[int32, TKeyCallback](16)
result.onKeyUp = initTable[int32, TKeyCallback](16)
result.name = name
if setActive:
setActive(result)
proc keyPressed*(key: TKeyCode): bool {.inline.} =
return keyState[key.int32]
proc buttonPressed*(btn: TMouseButton): bool {.inline.} =
return keyState[-btn.int32]
proc clearKey*(key: TKeyCode) {.inline.} =
keyState[key.int32] = false
proc clearButton*(btn: TMouseButton) {.inline.} =
keyState[-btn.int32] = false
proc addKeyEvent*(key: TKeyCode, ev: TKeyEventKind) {.inline.} =
if activeClient.isNil: return
let k = key.int32
case ev
of down:
keyState[k] = true
if activeClient.onKeyDown.hasKey(k):
activeClient.onKeyDown[k]()
else:
keyState[k] = false
if activeClient.onKeyUp.hasKey(k):
activeClient.onKeyUp[k]()
proc addButtonEvent*(btn: TMouseButton, ev: TKeyEventKind) {.inline.} =
if activeClient.isNil: return
let b = -btn.int32
case ev
of down:
keyState[b] = true
if activeClient.onKeyDown.hasKey(b):
activeClient.onKeyDown[b]()
else:
keyState[b] = false
if activeClient.onKeyUp.hasKey(b):
activeClient.onKeyUp[b]()
proc registerHandler*(client: PKeyClient; key: TKeyCode;
ev: TKeyEventKind; fn: TKeyCallback) =
case ev
of down: client.onKeyDown[key.int32] = fn
of up: client.onKeyUp[key.int32] = fn
proc registerHandler*(client: PKeyClient; btn: TMouseButton;
ev: TKeyEventKind; fn: TKeyCallback) =
case ev
of down: client.onKeyDown[-btn.int32] = fn
of up: client.onKeyUp[-btn.int32] = fn
proc newTextInput*(text = "", pos = 0, onEnter: TInputFinishedProc = nil): PTextInput =
new(result)
result.text = text
result.cursor = pos
result.onEnter = onEnter
proc setActive*(i: PTextInput) =
activeInput = i
proc stopCapturingText*() =
activeInput = nil
proc clear*(i: PTextInput) =
i.text.setLen 0
i.cursor = 0
proc recordText*(i: PTextInput; c: cint) =
if c > 127 or i.isNil: return
if c in 32..126: ##printable
if i.cursor == i.text.len: i.text.add(c.int.chr)
else:
let rem = i.text.substr(i.cursor)
i.text.setLen(i.cursor)
i.text.add(chr(c.int))
i.text.add(rem)
inc(i.cursor)
elif c == 8: ## \b backspace
if i.text.len > 0 and i.cursor > 0:
dec(i.cursor)
let rem = i.text.substr(i.cursor + 1)
i.text.setLen(i.cursor)
i.text.add(rem)
elif c == 10 or c == 13:## \n, \r enter
if not i.onEnter.isNil: i.onEnter()
proc recordText*(i: PTextInput; e: TTextEvent) {.inline.} =
recordText(i, e.unicode)
proc setMousePos*(x, y: cint) {.inline.} =
mousePos.x = x.float
mousePos.y = y.float
proc getMousePos*(): TVector2f {.inline.} = result = mousePos
var event: TEvent
# Handle and filter input-related events
iterator filterEvents*(window: PRenderWindow): PEvent =
while window.pollEvent(addr event):
case event.kind
of EvtKeyPressed: addKeyEvent(event.key.code, down)
of EvtKeyReleased: addKeyEvent(event.key.code, up)
of EvtMouseButtonPressed: addButtonEvent(event.mouseButton.button, down)
of EvtMouseButtonReleased: addButtonEvent(event.mouseButton.button, up)
of EvtTextEntered: recordText(activeInput, event.text)
of EvtMouseMoved: setMousePos(event.mouseMove.x, event.mouseMove.y)
else: yield(addr event)
# Handle and return input-related events
iterator pollEvents*(window: PRenderWindow): PEvent =
while window.pollEvent(addr event):
case event.kind
of EvtKeyPressed: addKeyEvent(event.key.code, down)
of EvtKeyReleased: addKeyEvent(event.key.code, up)
of EvtMouseButtonPressed: addButtonEvent(event.mouseButton.button, down)
of EvtMouseButtonReleased: addButtonEvent(event.mouseButton.button, up)
of EvtTextEntered: recordText(activeInput, event.text)
of EvtMouseMoved: setMousePos(event.mouseMove.x, event.mouseMove.y)
else: nil
yield(addr event)

View file

@ -0,0 +1,41 @@
template filterIt2*(seq, pred: expr, body: stmt): stmt {.immediate, dirty.} =
## sequtils defines a filterIt() that returns a new seq, but this one is called
## with a statement body to iterate directly over it
for it in items(seq):
if pred: body
proc map*[A, B](x: seq[A], func: proc(y: A): B {.closure.}): seq[B] =
result = @[]
for item in x.items:
result.add func(item)
proc mapInPlace*[A](x: var seq[A], func: proc(y: A): A {.closure.}) =
for i in 0..x.len-1:
x[i] = func(x[i])
template unless*(condition: expr; body: stmt): stmt {.dirty.} =
if not(condition):
body
when isMainModule:
proc dumpSeq[T](x: seq[T]) =
for index, item in x.pairs:
echo index, " ", item
echo "-------"
var t= @[1,2,3,4,5]
var res = t.map(proc(z: int): int = result = z * 10)
dumpSeq res
from strutils import toHex, repeatStr
var foocakes = t.map(proc(z: int): string =
result = toHex((z * 23).biggestInt, 4))
dumpSeq foocakes
t.mapInPlace(proc(z: int): int = result = z * 30)
dumpSeq t
var someSeq = @[9,8,7,6,5,4,3,2,1] ## numbers < 6 or even
filterIt2 someSeq, it < 6 or (it and 1) == 0:
echo(it)
echo "-----------"

View file

@ -0,0 +1,10 @@
import strutils, math
proc degrees*(rad: float): float =
return rad * 180.0 / PI
proc radians*(deg: float): float =
return deg * PI / 180.0
## V not math, sue me
proc ff*(f: float, precision = 2): string {.inline.} =
return formatFloat(f, ffDecimal, precision)

View file

@ -0,0 +1,37 @@
import
math, strutils,
sfml, input_helpers
when not defined(NoChipmunk):
import chipmunk
proc floor*(a: TVector): TVector2f {.inline.} =
result.x = a.x.floor
result.y = a.y.floor
proc sfml2cp*(a: TVector2f): TVector {.inline.} =
result.x = a.x
result.y = a.y
proc cp2sfml*(a: TVector): TVector2f {.inline.} =
result.x = a.x
result.y = a.y
proc vec2f*(a: TVector2i): TVector2f =
result.x = a.x.cfloat
result.y = a.y.cfloat
proc vec2i*(a: TVector2f): TVector2i =
result.x = a.x.cint
result.y = a.y.cint
proc vec3f*(x, y, z: float): TVector3f =
result.x = x.cfloat
result.y = y.cfloat
result.z = z.cfloat
proc `$`*(a: var TIntRect): string =
result = "[TIntRect $1,$2 $3x$4]".format($a.left, $a.top, $a.width, $a.height)
proc `$`*(a: TKeyEvent): string =
return "KeyEvent: code=$1 alt=$2 control=$3 shift=$4 system=$5".format(
$a.code, $a.alt, $a.control, $a.shift, $a.system)
proc `wmod`*(x, y: float): float = return x - y * (x/y).floor
proc move*(a: var TIntRect, left, top: cint): bool =
if a.left != left or a.top != top: result = true
a.left = left
a.top = top

View file

@ -0,0 +1,609 @@
import
re, json, strutils, tables, math, os, math_helpers,
sg_packets, md5, zlib_helpers
when defined(NoSFML):
import server_utils
type TVector2i = object
x*, y*: int32
proc vec2i(x, y: int32): TVector2i =
result.x = x
result.y = y
else:
import sfml, sfml_audio, sfml_stuff
when not defined(NoChipmunk):
import chipmunk
type
TChecksumFile* = object
unpackedSize*: int
sum*: MD5Digest
compressed*: string
PZoneSettings* = ref TZoneSettings
TZoneSettings* = object
vehicles: seq[PVehicleRecord]
items: seq[PItemRecord]
objects: seq[PObjectRecord]
bullets: seq[PBulletRecord]
levelSettings: PLevelSettings
PLevelSettings* = ref TLevelSettings
TLevelSettings* = object
size*: TVector2i
starfield*: seq[PSpriteSheet]
PVehicleRecord* = ref TVehicleRecord
TVehicleRecord* = object
id*: int16
name*: string
playable*: bool
anim*: PAnimationRecord
physics*: TPhysicsRecord
handling*: THandlingRecord
TItemKind* = enum
Projectile, Utility, Ammo
PObjectRecord* = ref TObjectRecord
TObjectRecord* = object
id*: int16
name*: string
anim*: PAnimationRecord
physics*: TPhysicsRecord
PItemRecord* = ref TItemRecord
TItemRecord* = object
id*: int16
name*: string
anim*: PAnimationRecord
physics*: TPhysicsRecord ##apply when the item is dropped in the arena
cooldown*: float
energyCost*: float
useSound*: PSoundRecord
case kind*: TItemKind
of Projectile:
bullet*: PBulletRecord
else:
nil
PBulletRecord* = ref TBulletRecord
TBulletRecord* = object
id*: int16
name*: string
anim*: PAnimationRecord
physics*: TPhysicsRecord
lifetime*, inheritVelocity*, baseVelocity*: float
explosion*: TExplosionRecord
trail*: TTrailRecord
TTrailRecord* = object
anim*: PAnimationRecord
timer*: float ##how often it should be created
TPhysicsRecord* = object
mass*: float
radius*: float
moment*: float
THandlingRecord = object
thrust*, top_speed*: float
reverse*, strafe*, rotation*: float
TSoulRecord = object
energy*: int
health*: int
TExplosionRecord* = object
anim*: PAnimationRecord
sound*: PSoundRecord
PAnimationRecord* = ref TAnimationRecord
TAnimationRecord* = object
spriteSheet*: PSpriteSheet
angle*: float
delay*: float ##animation delay
PSoundRecord* = ref TSoundRecord
TSoundRecord* = object
file*: string
when defined(NoSFML):
contents*: TChecksumFile
else:
soundBuf*: PSoundBuffer
PSpriteSheet* = ref TSpriteSheet
TSpriteSheet* = object
file*: string
framew*,frameh*: int
rows*, cols*: int
when defined(NoSFML):
contents*: TChecksumFile
when not defined(NoSFML):
sprite*: PSprite
tex*: PTexture
TGameState* = enum
Lobby, Transitioning, Field
const
TAU* = PI * 2.0
MomentMult* = 0.62 ## global moment of inertia multiplier
var
cfg: PZoneSettings
SpriteSheets* = initTable[string, PSpriteSheet](64)
SoundCache * = initTable[string, PSoundRecord](64)
nameToVehID*: TTable[string, int]
nameToItemID*: TTable[string, int]
nameToObjID*: TTable[string, int]
nameToBulletID*: TTable[string, int]
activeState = Lobby
proc newSprite(filename: string; errors: var seq[string]): PSpriteSheet
proc load*(ss: PSpriteSheet): bool {.discardable.}
proc newSound(filename: string; errors: var seq[string]): PSoundRecord
proc load*(s: PSoundRecord): bool {.discardable.}
proc validateSettings*(settings: PJsonNode; errors: var seq[string]): bool
proc loadSettings*(rawJson: string, errors: var seq[string]): bool
proc loadSettingsFromFile*(filename: string, errors: var seq[string]): bool
proc fetchVeh*(name: string): PVehicleRecord
proc fetchItm*(itm: string): PItemRecord
proc fetchObj*(name: string): PObjectRecord
proc fetchBullet(name: string): PBulletRecord
proc importLevel(data: PJsonNode; errors: var seq[string]): PLevelSettings
proc importVeh(data: PJsonNode; errors: var seq[string]): PVehicleRecord
proc importObject(data: PJsonNode; errors: var seq[string]): PObjectRecord
proc importItem(data: PJsonNode; errors: var seq[string]): PItemRecord
proc importPhys(data: PJsonNode): TPhysicsRecord
proc importAnim(data: PJsonNode; errors: var seq[string]): PAnimationRecord
proc importHandling(data: PJsonNode): THandlingRecord
proc importBullet(data: PJsonNode; errors: var seq[string]): PBulletRecord
proc importSoul(data: PJsonNode): TSoulRecord
proc importExplosion(data: PJsonNode; errors: var seq[string]): TExplosionRecord
proc importSound(data: PJsonNode; errors: var seq[string]; fieldName: string = nil): PSoundRecord
## this is the only pipe between lobby and main.nim
proc getActiveState*(): TGameState =
result = activeState
proc transition*() =
assert activeState == Lobby, "Transition() called from a state other than lobby!"
activeState = Transitioning
proc doneWithSaidTransition*() =
assert activeState == Transitioning, "Finished() called from a state other than transitioning!"
activeState = Field
proc checksumFile*(filename: string): TChecksumFile =
let fullText = readFile(filename)
result.unpackedSize = fullText.len
result.sum = toMD5(fullText)
result.compressed = compress(fullText)
proc checksumStr*(str: string): TChecksumFile =
result.unpackedSize = str.len
result.sum = toMD5(str)
result.compressed = compress(str)
##at this point none of these should ever be freed
proc free*(obj: PZoneSettings) =
echo "Free'd zone settings"
proc free*(obj: PSpriteSheet) =
echo "Free'd ", obj.file
proc free*(obj: PSoundRecord) =
echo "Free'd ", obj.file
proc loadAllAssets*() =
var
loaded = 0
failed = 0
for name, ss in SpriteSheets.pairs():
if load(ss):
inc loaded
else:
inc failed
echo loaded," sprites loaded. ", failed, " sprites failed."
loaded = 0
failed = 0
for name, s in SoundCache.pairs():
if load(s):
inc loaded
else:
inc failed
echo loaded, " sounds loaded. ", failed, " sounds failed."
proc getLevelSettings*(): PLevelSettings =
result = cfg.levelSettings
iterator playableVehicles*(): PVehicleRecord =
for v in cfg.vehicles.items():
if v.playable:
yield v
template allAssets*(body: stmt) {.dirty.}=
block:
var assetType = FGraphics
for file, asset in pairs(SpriteSheets):
body
assetType = FSound
for file, asset in pairs(SoundCache):
body
template cacheImpl(procName, cacheName, resultType: expr; body: stmt) {.dirty, immediate.} =
proc procName*(filename: string; errors: var seq[string]): resulttype =
if hasKey(cacheName, filename):
return cacheName[filename]
new(result, free)
body
cacheName[filename] = result
template checkFile(path: expr): stmt {.dirty, immediate.} =
if not existsFile(path):
errors.add("File missing: "& path)
cacheImpl newSprite, SpriteSheets, PSpriteSheet:
result.file = filename
if not(filename =~ re"\S+_(\d+)x(\d+)\.\S\S\S"):
errors.add "Bad file: "&filename&" must be in format name_WxH.png"
return
result.framew = strutils.parseInt(matches[0])
result.frameh = strutils.parseInt(matches[1])
checkFile("data/gfx"/result.file)
cacheImpl newSound, SoundCache, PSoundRecord:
result.file = filename
checkFile("data/sfx"/result.file)
proc expandPath*(assetType: TAssetType; fileName: string): string =
result = "data/"
case assetType
of FGraphics: result.add "gfx/"
of FSound: result.add "sfx/"
else: nil
result.add fileName
proc expandPath*(fc: ScFileChallenge): string {.inline.} =
result = expandPath(fc.assetType, fc.file)
when defined(NoSFML):
proc load*(ss: PSpriteSheet): bool =
if not ss.contents.unpackedSize == 0: return
ss.contents = checksumFile(expandPath(FGraphics, ss.file))
result = true
proc load*(s: PSoundRecord): bool =
if not s.contents.unpackedSize == 0: return
s.contents = checksumFile(expandPath(FSound, s.file))
result = true
else:
proc load*(ss: PSpriteSheet): bool =
if not ss.sprite.isNil:
return
var image = sfml.newImage("data/gfx/"/ss.file)
if image == nil:
echo "Image could not be loaded"
return
let size = image.getSize()
ss.rows = int(size.y / ss.frameh) #y is h
ss.cols = int(size.x / ss.framew) #x is w
ss.tex = newTexture(image)
image.destroy()
ss.sprite = newSprite()
ss.sprite.setTexture(ss.tex, true)
ss.sprite.setTextureRect(intrect(0, 0, ss.framew.cint, ss.frameh.cint))
ss.sprite.setOrigin(vec2f(ss.framew / 2, ss.frameh / 2))
result = true
proc load*(s: PSoundRecord): bool =
s.soundBuf = newSoundBuffer("data/sfx"/s.file)
if not s.soundBuf.isNil:
result = true
template addError(e: expr): stmt {.immediate.} =
errors.add(e)
result = false
proc validateSettings*(settings: PJsonNode, errors: var seq[string]): bool =
result = true
if settings.kind != JObject:
addError("Settings root must be an object")
return
if not settings.existsKey("vehicles"):
addError("Vehicles section missing")
if not settings.existsKey("objects"):
errors.add("Objects section is missing")
result = false
if not settings.existsKey("level"):
errors.add("Level settings section is missing")
result = false
else:
let lvl = settings["level"]
if lvl.kind != JObject or not lvl.existsKey("size"):
errors.add("Invalid level settings")
result = false
elif not lvl.existsKey("size") or lvl["size"].kind != JArray or lvl["size"].len != 2:
errors.add("Invalid/missing level size")
result = false
if not settings.existsKey("items"):
errors.add("Items section missing")
result = false
else:
let items = settings["items"]
if items.kind != JArray or items.len == 0:
errors.add "Invalid or empty item list"
else:
var id = 0
for i in items.items:
if i.kind != JArray: errors.add("Item #$1 is not an array"% $id)
elif i.len != 3: errors.add("($1) Item record should have 3 fields"%($id))
elif i[0].kind != JString or i[1].kind != JString or i[2].kind != JObject:
errors.add("($1) Item should be in form [name, type, {item: data}]"% $id)
result = false
inc id
proc loadSettingsFromFile*(filename: string, errors: var seq[string]): bool =
if not existsFile(filename):
errors.add("File does not exist: "&filename)
else:
result = loadSettings(readFile(filename), errors)
proc loadSettings*(rawJson: string, errors: var seq[string]): bool =
var settings: PJsonNode
try:
settings = parseJson(rawJson)
except EJsonParsingError:
errors.add("JSON parsing error: "& getCurrentExceptionMsg())
return
except:
errors.add("Unknown exception: "& getCurrentExceptionMsg())
return
if not validateSettings(settings, errors):
return
if cfg != nil: #TODO try this
echo("Overwriting zone settings")
free(cfg)
cfg = nil
new(cfg, free)
cfg.levelSettings = importLevel(settings, errors)
cfg.vehicles = @[]
cfg.items = @[]
cfg.objects = @[]
cfg.bullets = @[]
nameToVehID = initTable[string, int](32)
nameToItemID = initTable[string, int](32)
nameToObjID = initTable[string, int](32)
nameToBulletID = initTable[string, int](32)
var
vID = 0'i16
bID = 0'i16
for vehicle in settings["vehicles"].items:
var veh = importVeh(vehicle, errors)
veh.id = vID
cfg.vehicles.add veh
nameToVehID[veh.name] = veh.id
inc vID
vID = 0
if settings.existsKey("bullets"):
for blt in settings["bullets"].items:
var bullet = importBullet(blt, errors)
bullet.id = bID
cfg.bullets.add bullet
nameToBulletID[bullet.name] = bullet.id
inc bID
for item in settings["items"].items:
var itm = importItem(item, errors)
itm.id = vID
cfg.items.add itm
nameToItemID[itm.name] = itm.id
inc vID
if itm.kind == Projectile:
if itm.bullet.isNil:
errors.add("Projectile #$1 has no bullet!"% $vID)
elif itm.bullet.id == -1:
## this item has an anonymous bullet, fix the ID and name
itm.bullet.id = bID
itm.bullet.name = itm.name
cfg.bullets.add itm.bullet
nameToBulletID[itm.bullet.name] = itm.bullet.id
inc bID
vID = 0
for obj in settings["objects"].items:
var o = importObject(obj, errors)
o.id = vID
cfg.objects.add o
nameToObjID[o.name] = o.id
inc vID
result = (errors.len == 0)
proc `$`*(obj: PSpriteSheet): string =
return "<Sprite $1 ($2x$3) $4 rows $5 cols>" % [obj.file, $obj.framew, $obj.frameh, $obj.rows, $obj.cols]
proc fetchVeh*(name: string): PVehicleRecord =
return cfg.vehicles[nameToVehID[name]]
proc fetchItm*(itm: string): PItemRecord =
return cfg.items[nameToItemID[itm]]
proc fetchObj*(name: string): PObjectRecord =
return cfg.objects[nameToObjID[name]]
proc fetchBullet(name: string): PBulletRecord =
return cfg.bullets[nameToBulletID[name]]
proc getField(node: PJsonNode, field: string, target: var float) =
if not node.existsKey(field):
return
if node[field].kind == JFloat:
target = node[field].fnum
elif node[field].kind == JInt:
target = node[field].num.float
proc getField(node: PJsonNode, field: string, target: var int) =
if not node.existsKey(field):
return
if node[field].kind == JInt:
target = node[field].num.int
elif node[field].kind == JFloat:
target = node[field].fnum.int
proc getField(node: PJsonNode; field: string; target: var bool) =
if not node.existsKey(field):
return
case node[field].kind
of JBool:
target = node[field].bval
of JInt:
target = (node[field].num != 0)
of JFloat:
target = (node[field].fnum != 0.0)
else: nil
template checkKey(node: expr; key: string): stmt =
if not existsKey(node, key):
return
proc importTrail(data: PJsonNode; errors: var seq[string]): TTrailRecord =
checkKey(data, "trail")
result.anim = importAnim(data["trail"], errors)
result.timer = 1000.0
getField(data["trail"], "timer", result.timer)
result.timer /= 1000.0
proc importLevel(data: PJsonNode; errors: var seq[string]): PLevelSettings =
new(result)
result.size = vec2i(5000, 5000)
result.starfield = @[]
checkKey(data, "level")
var level = data["level"]
if level.existsKey("size") and level["size"].kind == JArray and level["size"].len == 2:
result.size.x = level["size"][0].num.cint
result.size.y = level["size"][1].num.cint
if level.existsKey("starfield"):
for star in level["starfield"].items:
result.starfield.add(newSprite(star.str, errors))
proc importPhys(data: PJsonNode): TPhysicsRecord =
result.radius = 20.0
result.mass = 10.0
if data.existsKey("physics") and data["physics"].kind == JObject:
let phys = data["physics"]
phys.getField("radius", result.radius)
phys.getField("mass", result.mass)
when not defined(NoChipmunk):
result.moment = momentForCircle(result.mass, 0.0, result.radius, vectorZero) * MomentMult
proc importHandling(data: PJsonNode): THandlingRecord =
result.thrust = 45.0
result.topSpeed = 100.0 #unused
result.reverse = 30.0
result.strafe = 30.0
result.rotation = 2200.0
checkKey(data, "handling")
if data["handling"].kind != JObject:
return
let hand = data["handling"]
hand.getField("thrust", result.thrust)
hand.getField("top_speed", result.topSpeed)
hand.getField("reverse", result.reverse)
hand.getField("strafe", result.strafe)
hand.getField("rotation", result.rotation)
proc importAnim(data: PJsonNode, errors: var seq[string]): PAnimationRecord =
new(result)
result.angle = 0.0
result.delay = 1000.0
result.spriteSheet = nil
if data.existsKey("anim"):
let anim = data["anim"]
if anim.kind == JObject:
if anim.existsKey("file"):
result.spriteSheet = newSprite(anim["file"].str, errors)
anim.getField "angle", result.angle
anim.getField "delay", result.delay
elif data["anim"].kind == JString:
result.spriteSheet = newSprite(anim.str, errors)
result.angle = radians(result.angle) ## comes in as degrees
result.delay /= 1000 ## delay comes in as milliseconds
proc importSoul(data: PJsonNode): TSoulRecord =
result.energy = 10000
result.health = 1
checkKey(data, "soul")
let soul = data["soul"]
soul.getField("energy", result.energy)
soul.getField("health", result.health)
proc importExplosion(data: PJsonNode; errors: var seq[string]): TExplosionRecord =
checkKey(data, "explode")
let expl = data["explode"]
result.anim = importAnim(expl, errors)
result.sound = importSound(expl, errors, "sound")
proc importSound*(data: PJsonNode; errors: var seq[string]; fieldName: string = nil): PSoundRecord =
if data.kind == JObject:
checkKey(data, fieldName)
result = newSound(data[fieldName].str, errors)
elif data.kind == JString:
result = newSound(data.str, errors)
proc importVeh(data: PJsonNode; errors: var seq[string]): PVehicleRecord =
new(result)
result.playable = false
if data.kind != JArray or data.len != 2 or
(data.kind == JArray and
(data[0].kind != JString or data[1].kind != JObject)):
result.name = "(broken)"
errors.add "Vehicle record is malformed"
return
var vehData = data[1]
result.name = data[0].str
result.anim = importAnim(vehdata, errors)
result.physics = importPhys(vehdata)
result.handling = importHandling(vehdata)
vehdata.getField("playable", result.playable)
if result.anim.spriteSheet.isNil and result.playable:
result.playable = false
proc importObject(data: PJsonNode; errors: var seq[string]): PObjectRecord =
new(result)
if data.kind != JArray or data.len != 2:
result.name = "(broken)"
return
result.name = data[0].str
result.anim = importAnim(data[1], errors)
result.physics = importPhys(data[1])
proc importItem(data: PJsonNode; errors: var seq[string]): PItemRecord =
new(result)
if data.kind != JArray or data.len != 3:
result.name = "(broken)"
errors.add "Item record is malformed"
return
result.name = data[0].str
result.anim = importAnim(data[2], errors)
result.physics = importPhys(data[2])
result.cooldown = 100.0
data[2].getField("cooldown", result.cooldown)
result.cooldown /= 1000.0 ##cooldown is stored in ms
result.useSound = importSound(data[2], errors, "useSound")
case data[1].str.toLower
of "projectile":
result.kind = Projectile
if data[2]["bullet"].kind == JString:
result.bullet = fetchBullet(data[2]["bullet"].str)
elif data[2]["bullet"].kind == JInt:
result.bullet = cfg.bullets[data[2]["bullet"].num.int]
elif data[2]["bullet"].kind == JObject:
result.bullet = importBullet(data[2]["bullet"], errors)
else:
errors.add "UNKNOWN BULLET TYPE for item "& result.name
of "ammo":
result.kind = Ammo
of "utility":
nil
else:
errors.add "Invalid item type \""& data[1].str &"\" for item "& result.name
proc importBullet(data: PJsonNode; errors: var seq[string]): PBulletRecord =
new(result)
result.id = -1
var bdata: PJsonNode
if data.kind == JArray:
result.name = data[0].str
bdata = data[1]
elif data.kind == JObject:
bdata = data
else:
errors.add "Malformed bullet record"
return
result.anim = importAnim(bdata, errors)
result.physics = importPhys(bdata)
result.lifetime = 2000.0
result.inheritVelocity = 1000.0
result.baseVelocity = 30.0
getField(bdata, "lifetime", result.lifetime)
getField(bdata, "inheritVelocity", result.inheritVelocity)
getField(bdata, "baseVelocity", result.baseVelocity)
result.lifetime /= 1000.0 ## lifetime is stored as milliseconds
result.inheritVelocity /= 1000.0 ## inherit velocity 1000 = 1.0 (100%)
result.explosion = importExplosion(bdata, errors)
result.trail = importTrail(bdata, errors)

View file

@ -0,0 +1,281 @@
import
sfml, sfml_colors,
input_helpers, sg_packets
from strutils import countlines
{.deadCodeElim: on.}
type
PGuiContainer* = ref TGuiContainer
TGuiContainer* = object of TObject
position: TVector2f
activeEntry: PTextEntry
widgets: seq[PGuiObject]
buttons: seq[PButton]
PGuiObject* = ref TGuiObject
TGuiObject* = object of TObject
PButton* = ref TButton
TButton* = object of TGuiObject
enabled: bool
bg*: sfml.PRectangleShape
text*: PText
onClick*: TButtonClicked
bounds: TFloatRect
PButtonCollection* = ref TButtonCollection
TButtonCollection* = object of TGuiContainer
PTextEntry* = ref TTextEntry
TTextEntry* = object of TButton
inputClient: input_helpers.PTextInput
PMessageArea* = ref TMessageArea
TMessageArea* = object of TGuiObject
pos: TVector2f
messages: seq[TMessage]
texts: seq[PText]
scrollBack*: int
sizeVisible*: int
direction*: int
TMessage = object
color: TColor
text: string
lines: int
TButtonClicked = proc(button: PButton)
var
guiFont* = newFont("data/fnt/LiberationMono-Regular.ttf")
messageProto* = newText("", guiFont, 16)
let
vectorZeroF* = vec2f(0.0, 0.0)
proc newGuiContainer*(): PGuiContainer
proc newGuiContainer*(pos: TVector2f): PGuiContainer {.inline.}
proc free*(container: PGuiContainer)
proc add*(container: PGuiContainer; widget: PGuiObject)
proc clearButtons*(container: PGuiContainer)
proc click*(container: PGuiContainer; position: TVector2f)
proc setActive*(container: PGuiContainer; entry: PTextEntry)
proc setPosition*(container: PGuiContainer; position: TVector2f)
proc update*(container: PGuiContainer; dt: float)
proc draw*(window: PRenderWindow; container: PGuiContainer) {.inline.}
proc newMessageArea*(container: PGuiContainer; position: TVector2f): PMessageArea {.discardable.}
proc add*(m: PMessageArea; msg: ScChat)
proc draw*(window: PRenderWindow; b: PButton) {.inline.}
proc click*(b: PButton; p: TVector2f)
proc setPosition*(b: PButton; p: TVector2f)
proc setString*(b: PButton; s: string) {.inline.}
proc newButton*(container: PGuiContainer; text: string; position: TVector2f;
onClick: TButtonClicked; startEnabled: bool = true): PButton {.discardable.}
proc init(b: PButton; text: string; position: TVector2f; onClick: TButtonClicked)
proc setEnabled*(b: PButton; enabled: bool)
proc disable*(b: PButton) {.inline.}
proc enable*(b: PButton) {.inline.}
proc newTextEntry*(container: PGuiContainer; text: string;
position: TVector2f; onEnter: TInputFinishedProc = nil): PTextEntry {.discardable.}
proc init(t: PTextEntry; text: string; onEnter: TInputFinishedProc)
proc draw*(window: PRenderWindow, t: PTextEntry) {.inline.}
proc setActive*(t: PTextEntry) {.inline.}
proc clearText*(t: PTextEntry) {.inline.}
proc getText*(t: PTextEntry): string {.inline.}
proc update*(m: PMessageArea)
if guiFont == nil:
echo("Could not load font, crying softly to myself.")
quit(1)
proc newGuiContainer*(): PGuiContainer =
new(result, free)
result.widgets = @[]
result.buttons = @[]
proc newGuiContainer*(pos: TVector2f): PGuiContainer =
result = newGuiContainer()
result.setPosition pos
proc free*(container: PGuiContainer) =
container.widgets = nil
container.buttons = nil
proc add*(container: PGuiContainer; widget: PGuiObject) =
container.widgets.add(widget)
proc add*(container: PGuiContainer; button: PButton) =
if container.isNil: return
container.buttons.add(button)
proc clearButtons*(container: PGuiContainer) =
container.buttons.setLen 0
proc click*(container: PGuiContainer; position: TVector2f) =
for b in container.buttons:
click(b, position)
proc setActive*(container: PGuiContainer; entry: PTextEntry) =
container.activeEntry = entry
setActive(entry)
proc setPosition*(container: PGuiContainer; position: TVector2f) =
container.position = position
proc update*(container: PGuiContainer; dt: float) =
if not container.activeEntry.isNil:
container.activeEntry.setString(container.activeEntry.getText())
proc draw*(window: PRenderWindow; container: PGuiContainer) =
for b in container.buttons:
window.draw b
proc free(c: PButton) =
c.bg.destroy()
c.text.destroy()
c.bg = nil
c.text = nil
c.onClick = nil
proc newButton*(container: PGuiContainer; text: string;
position: TVector2f; onClick: TButtonClicked;
startEnabled: bool = true): PButton =
new(result, free)
init(result,
text,
if not container.isNil: position + container.position else: position,
onClick)
container.add result
if not startEnabled: disable(result)
proc init(b: PButton; text: string; position: TVector2f; onClick: TButtonClicked) =
b.bg = newRectangleShape()
b.bg.setSize(vec2f(80.0, 16.0))
b.bg.setFillColor(color(20, 30, 15))
b.text = newText(text, guiFont, 16)
b.onClick = onClick
b.setPosition(position)
b.enabled = true
proc copy*(c: PButton): PButton =
new(result, free)
result.bg = c.bg.copy()
result.text = c.text.copy()
result.onClick = c.onClick
result.setPosition(result.bg.getPosition())
proc setEnabled*(b: PButton; enabled: bool) =
b.enabled = enabled
if enabled:
b.text.setColor(White)
else:
b.text.setColor(Gray)
proc enable*(b: PButton) = setEnabled(b, true)
proc disable*(b: PButton) = setEnabled(b, false)
proc draw*(window: PRenderWindow; b: PButton) =
window.draw b.bg
window.draw b.text
proc setPosition*(b: PButton, p: TVector2f) =
b.bg.setPosition(p)
b.text.setPosition(p)
b.bounds = b.text.getGlobalBounds()
proc setString*(b: PButton; s: string) =
b.text.setString(s)
proc click*(b: PButton, p: TVector2f) =
if b.enabled and (addr b.bounds).contains(p.x, p.y):
b.onClick(b)
proc free(obj: PTextEntry) =
free(PButton(obj))
proc newTextEntry*(container: PGuiContainer; text: string;
position: TVector2F; onEnter: TInputFinishedProc = nil): PTextEntry =
new(result, free)
init(PButton(result), text, position + container.position, proc(b: PButton) =
setActive(container, PTextEntry(b)))
init(result, text, onEnter)
container.add result
proc init(t: PTextEntry; text: string; onEnter: TInputFinishedProc) =
t.inputClient = newTextInput(text, text.len, onEnter)
proc draw(window: PRenderWindow; t: PTextEntry) =
window.draw PButton(t)
proc clearText*(t: PTextEntry) =
t.inputClient.clear()
proc getText*(t: PTextEntry): string =
return t.inputClient.text
proc setActive*(t: PTextEntry) =
if not t.isNil and not t.inputClient.isNil:
input_helpers.setActive(t.inputClient)
discard """proc newMessageArea*(container: PGuiContainer; position: TVector2f): PMessageArea =
new(result)
result.messages = @[]
result.pos = position
container.add(result)
proc add*(m: PMessageArea, text: string): PText =
result = messageProto.copy()
result.setString(text)
m.messages.add(result)
let nmsgs = len(m.messages)
var pos = vec2f(m.pos.x, m.pos.y)
for i in countdown(nmsgs - 1, max(nmsgs - 30, 0)):
setPosition(m.messages[i], pos)
pos.y -= 16.0
proc draw*(window: PRenderWindow; m: PMessageArea) =
let nmsgs = len(m.messages)
if nmsgs == 0: return
for i in countdown(nmsgs - 1, max(nmsgs - 30, 0)):
window.draw(m.messages[i])
"""
proc newMessageArea*(container: PGuiContainer; position: TVector2f): PMessageArea =
new(result)
result.messages = @[]
result.texts = @[]
result.pos = position + container.position
result.sizeVisible = 10
result.scrollBack = 0
result.direction = -1 ## to push old messages up
container.add(result)
proc add*(m: PMessageArea, msg: ScChat) =
const prependName = {CPub, CPriv}
var mmm: TMessage
if msg.kind in prependName:
mmm.text = "<"
mmm.text.add msg.fromPlayer
mmm.text.add "> "
mmm.text.add msg.text
else:
mmm.text = msg.text
case msg.kind
of CPub: mmm.color = RoyalBlue
of CPriv, CSystem: mmm.color = Green
of CError: mmm.color = Red
mmm.lines = countLines(mmm.text)+1
m.messages.add mmm
update m
proc add*(m: PMessageArea, msg: string) {.inline.} =
var chat = newScChat(kind = CSystem, text = msg)
add(m, chat)
proc proctor*(m: PText; msg: ptr TMessage; pos: ptr TVector2f) =
m.setString msg.text
m.setColor msg.color
m.setPosition pos[]
proc update*(m: PMessageArea) =
if m.texts.len < m.sizeVisible:
echo "adding ", m.sizeVisible - m.texts.len, " fields"
for i in 1..m.sizeVisible - m.texts.len:
var t = messageProto.copy()
m.texts.add messageProto.copy()
elif m.texts.len > m.sizeVisible:
echo "cutting ", m.texts.len - m.sizeVisible, " fields"
for i in m.sizeVisible.. < m.texts.len:
m.texts.pop().destroy()
let nmsgs = m.messages.len()
if m.sizeVisible == 0 or nmsgs == 0:
echo "no messages? ", m.sizeVisible, ", ", nmsgs
return
var pos = vec2f(m.pos.x, m.pos.y)
for i in 0.. min(m.sizeVisible, nmsgs)-1:
##echo nmsgs - i - 1 - m.scrollBack
let msg = addr m.messages[nmsgs - i - 1 - m.scrollBack]
proctor(m.texts[i], msg, addr pos)
pos.y += (16 * m.direction * msg.lines).cfloat
proc draw*(window: PRenderWindow; m: PMessageArea) =
let nmsgs = len(m.texts)
if nmsgs == 0: return
for i in countdown(nmsgs - 1, max(nmsgs - m.sizeVisible, 0)):
window.draw m.texts[i]

View file

@ -0,0 +1,106 @@
import genpacket_enet, sockets, md5, enet
defPacketImports()
type
PacketID* = char
template idpacket(pktName, id, s2c, c2s: expr): stmt {.immediate, dirty.} =
let `H pktName`* {.inject.} = id
defPacket(`Sc pktName`, s2c)
defPacket(`Cs pktName`, c2s)
forwardPacketT(Uint8, int8)
forwardPacketT(Uint16, int16)
forwardPacketT(TPort, int16)
idPacket(Login, 'a',
tuple[id: int32; alias: string; sessionKey: string],
tuple[alias: string, passwd: string])
let HZoneJoinReq* = 'j'
defPacket(CsZoneJoinReq, tuple[session: ScLogin])
defPacket(ScZoneRecord, tuple[
name: string = "", desc: string = "",
ip: string = "", port: TPort = 0.Tport])
idPacket(ZoneList, 'z',
tuple[network: string = "", zones: seq[ScZoneRecord]],
tuple[time: string])
let HPoing* = 'p'
defPacket(Poing, tuple[id: int32, time: float32])
type ChatType* = enum
CPub = 0'i8, CPriv, CSystem, CError
forwardPacketT(ChatType, int8)
idPacket(Chat, 'C',
tuple[kind: ChatType = CPub; fromPlayer: string = ""; text: string = ""],
tuple[target: string = ""; text: string = ""])
idPacket(Hello, 'h',
tuple[resp: string],
tuple[i: int8 = 14])
let HPlayerList* = 'P'
defPacket(ScPlayerRec, tuple[id: int32; alias: string = ""])
defPacket(ScPlayerList, tuple[players: seq[ScPlayerRec]])
let HTeamList* = 'T'
defPacket(ScTeam, tuple[id: int8; name: string = ""])
defPacket(ScTeamList, tuple[teams: seq[ScTeam]])
let HTeamChange* = 't'
idPacket(ZoneQuery, 'Q',
tuple[playerCount: Uint16], ##i should include a time here or something
tuple[pad: char = '\0'])
type SpawnKind = enum
SpawnItem = 1'i8, SpawnVehicle, SpawnObject
forwardPacketT(SpawnKind, int8)
defPacket(ScSpawn, tuple[
kind: SpawnKind; id: uint16; record: uint16; amount: uint16])
type TAssetType* = enum
FZoneCfg = 1'i8, FGraphics, FSound
forwardPacketT(TAssetType, int8)
forwardPacket(MD5Digest, array[0..15, int8])
idPacket(FileChallenge, 'F',
tuple[file: string; assetType: TAssetType; fullLen: int32],
tuple[needFile: bool; checksum: MD5Digest])
let HChallengeResult* = '('
defPacket(ScChallengeResult, tuple[status: bool])
let HFileTransfer* = 'f'
defPacket(ScFileTransfer, tuple[fileSize: int32; pos: int32; data: string])
defPacket(CsFilepartAck, tuple[lastpos: int32])
##dir server messages
let HZoneLogin* = 'u'
defPacket(SdZoneLogin, tuple[name: string; key: string; record: ScZoneRecord])
defPacket(DsZoneLogin, tuple[status: bool])
let HDsMsg* = 'c'
defPacket(DsMsg, tuple[msg: string])
let HVerifyClient* = 'v'
defPacket(SdVerifyClient, tuple[session: ScLogin])
when isMainModule:
var buf = newBuffer(100)
var m = toMd5("hello there")
echo(repr(m))
buf.pack m
echo(repr(buf.data))
echo(len(buf.data))
buf.reset()
var x = buf.readMD5Digest()
echo(repr(x))

View file

@ -0,0 +1,38 @@
when defined(NoSFML) or defined(NoChipmunk):
{.error.}
import sfml_audio, sfml_stuff, sg_assets, chipmunk
const
MinDistance* = 350.0
Attenuation* = 20.0
var
liveSounds: seq[PSound] = @[]
deadSounds: seq[PSound] = @[]
proc playSound*(sound: PSoundRecord, pos: TVector) =
if sound.isNil or sound.soundBuf.isNil: return
var s: PSound
if deadSounds.len == 0:
s = sfml_audio.newSound()
s.setLoop false
s.setRelativeToListener true
s.setAttenuation Attenuation
s.setMinDistance MinDistance
else:
s = deadSounds.pop()
s.setPosition(vec3f(pos.x, 0, pos.y))
s.setBuffer(sound.soundBuf)
s.play()
liveSounds.add s
proc updateSoundBuffer*() =
var i = 0
while i < len(liveSounds):
if liveSounds[i].getStatus == Stopped:
deadSounds.add liveSounds[i]
liveSounds.del i
else:
inc i
proc report*() =
echo "live: ", liveSounds.len
echo "dead: ", deadSounds.len

View file

@ -0,0 +1,35 @@
import
sfml, chipmunk,
sg_assets, sfml_stuff, keineschweine
proc accel*(obj: PVehicle, dt: float) =
#obj.velocity += vec2f(
# cos(obj.angle) * obj.record.handling.thrust.float * dt,
# sin(obj.angle) * obj.record.handling.thrust.float * dt)
obj.body.applyImpulse(
vectorForAngle(obj.body.getAngle()) * dt * obj.record.handling.thrust,
vectorZero)
proc reverse*(obj: PVehicle, dt: float) =
#obj.velocity += vec2f(
# -cos(obj.angle) * obj.record.handling.reverse.float * dt,
# -sin(obj.angle) * obj.record.handling.reverse.float * dt)
obj.body.applyImpulse(
-vectorForAngle(obj.body.getAngle()) * dt * obj.record.handling.reverse,
vectorZero)
proc strafe_left*(obj: PVehicle, dt: float) =
obj.body.applyImpulse(
vectorForAngle(obj.body.getAngle()).perp() * obj.record.handling.strafe * dt,
vectorZero)
proc strafe_right*(obj: PVehicle, dt: float) =
obj.body.applyImpulse(
vectorForAngle(obj.body.getAngle()).rperp()* obj.record.handling.strafe * dt,
vectorZero)
proc turn_right*(obj: PVehicle, dt: float) =
#obj.angle = (obj.angle + (obj.record.handling.rotation.float / 10.0 * dt)) mod TAU
obj.body.setTorque(obj.record.handling.rotation)
proc turn_left*(obj: PVehicle, dt: float) =
#obj.angle = (obj.angle - (obj.record.handling.rotation.float / 10.0 * dt)) mod TAU
obj.body.setTorque(-obj.record.handling.rotation)
proc offsetAngle*(obj: PVehicle): float {.inline.} =
return (obj.record.anim.angle + obj.body.getAngle())

View file

@ -0,0 +1,40 @@
import zlib
proc compress*(source: string): string =
var
sourcelen = source.len
destlen = sourcelen + (sourcelen.float * 0.1).int + 16
result = ""
result.setLen destLen
var res = zlib.compress(cstring(result), addr destLen, cstring(source), sourceLen)
if res != Z_OK:
echo "Error occured: ", res
elif destLen < result.len:
result.setLen(destLen)
proc uncompress*(source: string, destLen: var int): string =
result = ""
result.setLen destLen
var res = zlib.uncompress(cstring(result), addr destLen, cstring(source), source.len)
if res != Z_OK:
echo "Error occured: ", res
when isMainModule:
import strutils
var r = compress("Hello")
echo repr(r)
var l = "Hello".len
var rr = uncompress(r, l)
echo repr(rr)
assert rr == "Hello"
proc `*`(a: string; b: int): string {.inline.} = result = repeatStr(b, a)
var s = "yo dude sup bruh homie" * 50
r = compress(s)
echo s.len, " -> ", r.len
l = s.len
rr = uncompress(r, l)
echo r.len, " -> ", rr.len
assert rr == s