diff options
Diffstat (limited to 'tests/manyloc/keineschweine/lib/sg_assets.nim')
-rw-r--r-- | tests/manyloc/keineschweine/lib/sg_assets.nim | 611 |
1 files changed, 611 insertions, 0 deletions
diff --git a/tests/manyloc/keineschweine/lib/sg_assets.nim b/tests/manyloc/keineschweine/lib/sg_assets.nim new file mode 100644 index 000000000..96c962dc3 --- /dev/null +++ b/tests/manyloc/keineschweine/lib/sg_assets.nim @@ -0,0 +1,611 @@ +import ../../../../dist/checksums/src/checksums/md5 + +import + re, json, strutils, tables, math, os, math_helpers, + sg_packets, 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: + discard + 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 + MomentMult* = 0.62 ## global moment of inertia multiplier +var + cfg: PZoneSettings + SpriteSheets* = initTable[string, PSpriteSheet](64) + SoundCache * = initTable[string, PSoundRecord](64) + nameToVehID*: Table[string, int] + nameToItemID*: Table[string, int] + nameToObjID*: Table[string, int] + nameToBulletID*: Table[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: JsonNode; 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: JsonNode; errors: var seq[string]): PLevelSettings +proc importVeh(data: JsonNode; errors: var seq[string]): PVehicleRecord +proc importObject(data: JsonNode; errors: var seq[string]): PObjectRecord +proc importItem(data: JsonNode; errors: var seq[string]): PItemRecord +proc importPhys(data: JsonNode): TPhysicsRecord +proc importAnim(data: JsonNode; errors: var seq[string]): PAnimationRecord +proc importHandling(data: JsonNode): THandlingRecord +proc importBullet(data: JsonNode; errors: var seq[string]): PBulletRecord +proc importSoul(data: JsonNode): TSoulRecord +proc importExplosion(data: JsonNode; errors: var seq[string]): TExplosionRecord +proc importSound*(data: JsonNode; errors: var seq[string]; fieldName: string = ""): 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: untyped) {.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, body: untyped) {.dirty.} = + 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: untyped) {.dirty.} = + if not fileExists(path): + errors.add("File missing: " & path) + +cacheImpl newSprite, SpriteSheets, PSpriteSheet: + result.file = filename + if filename =~ re"\S+_(\d+)x(\d+)\.\S\S\S": + result.framew = strutils.parseInt(matches[0]) + result.frameh = strutils.parseInt(matches[1]) + checkFile("data/gfx"/result.file) + else: + errors.add "Bad file: " & filename & " must be in format name_WxH.png" + return + +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: discard + 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: untyped) = + errors.add(e) + result = false +proc validateSettings*(settings: JsonNode, errors: var seq[string]): bool = + result = true + if settings.kind != JObject: + addError("Settings root must be an object") + return + if not settings.hasKey("vehicles"): + addError("Vehicles section missing") + if not settings.hasKey("objects"): + errors.add("Objects section is missing") + result = false + if not settings.hasKey("level"): + errors.add("Level settings section is missing") + result = false + else: + let lvl = settings["level"] + if lvl.kind != JObject or not lvl.hasKey("size"): + errors.add("Invalid level settings") + result = false + elif not lvl.hasKey("size") or lvl["size"].kind != JArray or lvl["size"].len != 2: + errors.add("Invalid/missing level size") + result = false + if not settings.hasKey("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 fileExists(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: JsonNode + try: + settings = parseJson(rawJson) + except JsonParsingError: + 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.hasKey("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: JsonNode, field: string, target: var float) = + if not node.hasKey(field): + return + if node[field].kind == JFloat: + target = node[field].fnum + elif node[field].kind == JInt: + target = node[field].num.float +proc getField(node: JsonNode, field: string, target: var int) = + if not node.hasKey(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: JsonNode; field: string; target: var bool) = + if not node.hasKey(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: discard + +template checkKey(node: untyped; key: string) = + if not hasKey(node, key): + return + +proc importTrail(data: JsonNode; 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: JsonNode; errors: var seq[string]): PLevelSettings = + new(result) + result.size = vec2i(5000, 5000) + result.starfield = @[] + + checkKey(data, "level") + var level = data["level"] + if level.hasKey("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.hasKey("starfield"): + for star in level["starfield"].items: + result.starfield.add(newSprite(star.str, errors)) +proc importPhys(data: JsonNode): TPhysicsRecord = + result.radius = 20.0 + result.mass = 10.0 + + if data.hasKey("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: JsonNode): 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: JsonNode, errors: var seq[string]): PAnimationRecord = + new(result) + result.angle = 0.0 + result.delay = 1000.0 + result.spriteSheet = nil + + if data.hasKey("anim"): + let anim = data["anim"] + if anim.kind == JObject: + if anim.hasKey("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: JsonNode): 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: JsonNode; 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: JsonNode; errors: var seq[string]; fieldName: string = ""): 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: JsonNode; 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: JsonNode; 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: JsonNode; 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.toLowerAscii + 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": + discard + else: + errors.add "Invalid item type \""&data[1].str&"\" for item "&result.name + +proc importBullet(data: JsonNode; errors: var seq[string]): PBulletRecord = + new(result) + result.id = -1 + + var bdata: JsonNode + 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) |