sg_assets.nim 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. import
  2. re, json, strutils, tables, math, os, math_helpers,
  3. sg_packets, md5, zlib_helpers
  4. when defined(NoSFML):
  5. import server_utils
  6. type TVector2i = object
  7. x*, y*: int32
  8. proc vec2i(x, y: int32): TVector2i =
  9. result.x = x
  10. result.y = y
  11. else:
  12. import sfml, sfml_audio, sfml_stuff
  13. when not defined(NoChipmunk):
  14. import chipmunk
  15. type
  16. TChecksumFile* = object
  17. unpackedSize*: int
  18. sum*: MD5Digest
  19. compressed*: string
  20. PZoneSettings* = ref TZoneSettings
  21. TZoneSettings* = object
  22. vehicles: seq[PVehicleRecord]
  23. items: seq[PItemRecord]
  24. objects: seq[PObjectRecord]
  25. bullets: seq[PBulletRecord]
  26. levelSettings: PLevelSettings
  27. PLevelSettings* = ref TLevelSettings
  28. TLevelSettings* = object
  29. size*: TVector2i
  30. starfield*: seq[PSpriteSheet]
  31. PVehicleRecord* = ref TVehicleRecord
  32. TVehicleRecord* = object
  33. id*: int16
  34. name*: string
  35. playable*: bool
  36. anim*: PAnimationRecord
  37. physics*: TPhysicsRecord
  38. handling*: THandlingRecord
  39. TItemKind* = enum
  40. Projectile, Utility, Ammo
  41. PObjectRecord* = ref TObjectRecord
  42. TObjectRecord* = object
  43. id*: int16
  44. name*: string
  45. anim*: PAnimationRecord
  46. physics*: TPhysicsRecord
  47. PItemRecord* = ref TItemRecord
  48. TItemRecord* = object
  49. id*: int16
  50. name*: string
  51. anim*: PAnimationRecord
  52. physics*: TPhysicsRecord ##apply when the item is dropped in the arena
  53. cooldown*: float
  54. energyCost*: float
  55. useSound*: PSoundRecord
  56. case kind*: TItemKind
  57. of Projectile:
  58. bullet*: PBulletRecord
  59. else:
  60. discard
  61. PBulletRecord* = ref TBulletRecord
  62. TBulletRecord* = object
  63. id*: int16
  64. name*: string
  65. anim*: PAnimationRecord
  66. physics*: TPhysicsRecord
  67. lifetime*, inheritVelocity*, baseVelocity*: float
  68. explosion*: TExplosionRecord
  69. trail*: TTrailRecord
  70. TTrailRecord* = object
  71. anim*: PAnimationRecord
  72. timer*: float ##how often it should be created
  73. TPhysicsRecord* = object
  74. mass*: float
  75. radius*: float
  76. moment*: float
  77. THandlingRecord = object
  78. thrust*, top_speed*: float
  79. reverse*, strafe*, rotation*: float
  80. TSoulRecord = object
  81. energy*: int
  82. health*: int
  83. TExplosionRecord* = object
  84. anim*: PAnimationRecord
  85. sound*: PSoundRecord
  86. PAnimationRecord* = ref TAnimationRecord
  87. TAnimationRecord* = object
  88. spriteSheet*: PSpriteSheet
  89. angle*: float
  90. delay*: float ##animation delay
  91. PSoundRecord* = ref TSoundRecord
  92. TSoundRecord* = object
  93. file*: string
  94. when defined(NoSFML):
  95. contents*: TChecksumFile
  96. else:
  97. soundBuf*: PSoundBuffer
  98. PSpriteSheet* = ref TSpriteSheet
  99. TSpriteSheet* = object
  100. file*: string
  101. framew*,frameh*: int
  102. rows*, cols*: int
  103. when defined(NoSFML):
  104. contents*: TChecksumFile
  105. when not defined(NoSFML):
  106. sprite*: PSprite
  107. tex*: PTexture
  108. TGameState* = enum
  109. Lobby, Transitioning, Field
  110. const
  111. MomentMult* = 0.62 ## global moment of inertia multiplier
  112. var
  113. cfg: PZoneSettings
  114. SpriteSheets* = initTable[string, PSpriteSheet](64)
  115. SoundCache * = initTable[string, PSoundRecord](64)
  116. nameToVehID*: Table[string, int]
  117. nameToItemID*: Table[string, int]
  118. nameToObjID*: Table[string, int]
  119. nameToBulletID*: Table[string, int]
  120. activeState = Lobby
  121. proc newSprite*(filename: string; errors: var seq[string]): PSpriteSheet
  122. proc load*(ss: PSpriteSheet): bool {.discardable.}
  123. proc newSound*(filename: string; errors: var seq[string]): PSoundRecord
  124. proc load*(s: PSoundRecord): bool {.discardable.}
  125. proc validateSettings*(settings: JsonNode; errors: var seq[string]): bool
  126. proc loadSettings*(rawJson: string, errors: var seq[string]): bool
  127. proc loadSettingsFromFile*(filename: string, errors: var seq[string]): bool
  128. proc fetchVeh*(name: string): PVehicleRecord
  129. proc fetchItm*(itm: string): PItemRecord
  130. proc fetchObj*(name: string): PObjectRecord
  131. proc fetchBullet(name: string): PBulletRecord
  132. proc importLevel(data: JsonNode; errors: var seq[string]): PLevelSettings
  133. proc importVeh(data: JsonNode; errors: var seq[string]): PVehicleRecord
  134. proc importObject(data: JsonNode; errors: var seq[string]): PObjectRecord
  135. proc importItem(data: JsonNode; errors: var seq[string]): PItemRecord
  136. proc importPhys(data: JsonNode): TPhysicsRecord
  137. proc importAnim(data: JsonNode; errors: var seq[string]): PAnimationRecord
  138. proc importHandling(data: JsonNode): THandlingRecord
  139. proc importBullet(data: JsonNode; errors: var seq[string]): PBulletRecord
  140. proc importSoul(data: JsonNode): TSoulRecord
  141. proc importExplosion(data: JsonNode; errors: var seq[string]): TExplosionRecord
  142. proc importSound*(data: JsonNode; errors: var seq[string]; fieldName: string = ""): PSoundRecord
  143. ## this is the only pipe between lobby and main.nim
  144. proc getActiveState*(): TGameState =
  145. result = activeState
  146. proc transition*() =
  147. assert activeState == Lobby, "Transition() called from a state other than lobby!"
  148. activeState = Transitioning
  149. proc doneWithSaidTransition*() =
  150. assert activeState == Transitioning, "Finished() called from a state other than transitioning!"
  151. activeState = Field
  152. proc checksumFile*(filename: string): TChecksumFile =
  153. let fullText = readFile(filename)
  154. result.unpackedSize = fullText.len
  155. result.sum = toMD5(fullText)
  156. result.compressed = compress(fullText)
  157. proc checksumStr*(str: string): TChecksumFile =
  158. result.unpackedSize = str.len
  159. result.sum = toMD5(str)
  160. result.compressed = compress(str)
  161. ##at this point none of these should ever be freed
  162. proc free*(obj: PZoneSettings) =
  163. echo "Free'd zone settings"
  164. proc free*(obj: PSpriteSheet) =
  165. echo "Free'd ", obj.file
  166. proc free*(obj: PSoundRecord) =
  167. echo "Free'd ", obj.file
  168. proc loadAllAssets*() =
  169. var
  170. loaded = 0
  171. failed = 0
  172. for name, ss in SpriteSheets.pairs():
  173. if load(ss):
  174. inc loaded
  175. else:
  176. inc failed
  177. echo loaded," sprites loaded. ", failed, " sprites failed."
  178. loaded = 0
  179. failed = 0
  180. for name, s in SoundCache.pairs():
  181. if load(s):
  182. inc loaded
  183. else:
  184. inc failed
  185. echo loaded, " sounds loaded. ", failed, " sounds failed."
  186. proc getLevelSettings*(): PLevelSettings =
  187. result = cfg.levelSettings
  188. iterator playableVehicles*(): PVehicleRecord =
  189. for v in cfg.vehicles.items():
  190. if v.playable:
  191. yield v
  192. template allAssets*(body: untyped) {.dirty.}=
  193. block:
  194. var assetType = FGraphics
  195. for file, asset in pairs(SpriteSheets):
  196. body
  197. assetType = FSound
  198. for file, asset in pairs(SoundCache):
  199. body
  200. template cacheImpl(procName, cacheName, resultType, body: untyped) {.dirty.} =
  201. proc procName*(filename: string; errors: var seq[string]): resulttype =
  202. if hasKey(cacheName, filename):
  203. return cacheName[filename]
  204. new(result, free)
  205. body
  206. cacheName[filename] = result
  207. template checkFile(path: untyped) {.dirty.} =
  208. if not fileExists(path):
  209. errors.add("File missing: " & path)
  210. cacheImpl newSprite, SpriteSheets, PSpriteSheet:
  211. result.file = filename
  212. if filename =~ re"\S+_(\d+)x(\d+)\.\S\S\S":
  213. result.framew = strutils.parseInt(matches[0])
  214. result.frameh = strutils.parseInt(matches[1])
  215. checkFile("data/gfx"/result.file)
  216. else:
  217. errors.add "Bad file: " & filename & " must be in format name_WxH.png"
  218. return
  219. cacheImpl newSound, SoundCache, PSoundRecord:
  220. result.file = filename
  221. checkFile("data/sfx"/result.file)
  222. proc expandPath*(assetType: TAssetType; fileName: string): string =
  223. result = "data/"
  224. case assetType
  225. of FGraphics: result.add "gfx/"
  226. of FSound: result.add "sfx/"
  227. else: discard
  228. result.add fileName
  229. proc expandPath*(fc: ScFileChallenge): string {.inline.} =
  230. result = expandPath(fc.assetType, fc.file)
  231. when defined(NoSFML):
  232. proc load*(ss: PSpriteSheet): bool =
  233. if not ss.contents.unpackedSize == 0: return
  234. ss.contents = checksumFile(expandPath(FGraphics, ss.file))
  235. result = true
  236. proc load*(s: PSoundRecord): bool =
  237. if not s.contents.unpackedSize == 0: return
  238. s.contents = checksumFile(expandPath(FSound, s.file))
  239. result = true
  240. else:
  241. proc load*(ss: PSpriteSheet): bool =
  242. if not ss.sprite.isNil:
  243. return
  244. var image = sfml.newImage("data/gfx/"/ss.file)
  245. if image == nil:
  246. echo "Image could not be loaded"
  247. return
  248. let size = image.getSize()
  249. ss.rows = int(size.y / ss.frameh) #y is h
  250. ss.cols = int(size.x / ss.framew) #x is w
  251. ss.tex = newTexture(image)
  252. image.destroy()
  253. ss.sprite = newSprite()
  254. ss.sprite.setTexture(ss.tex, true)
  255. ss.sprite.setTextureRect(intrect(0, 0, ss.framew.cint, ss.frameh.cint))
  256. ss.sprite.setOrigin(vec2f(ss.framew / 2, ss.frameh / 2))
  257. result = true
  258. proc load*(s: PSoundRecord): bool =
  259. s.soundBuf = newSoundBuffer("data/sfx"/s.file)
  260. if not s.soundBuf.isNil:
  261. result = true
  262. template addError(e: untyped) =
  263. errors.add(e)
  264. result = false
  265. proc validateSettings*(settings: JsonNode, errors: var seq[string]): bool =
  266. result = true
  267. if settings.kind != JObject:
  268. addError("Settings root must be an object")
  269. return
  270. if not settings.hasKey("vehicles"):
  271. addError("Vehicles section missing")
  272. if not settings.hasKey("objects"):
  273. errors.add("Objects section is missing")
  274. result = false
  275. if not settings.hasKey("level"):
  276. errors.add("Level settings section is missing")
  277. result = false
  278. else:
  279. let lvl = settings["level"]
  280. if lvl.kind != JObject or not lvl.hasKey("size"):
  281. errors.add("Invalid level settings")
  282. result = false
  283. elif not lvl.hasKey("size") or lvl["size"].kind != JArray or lvl["size"].len != 2:
  284. errors.add("Invalid/missing level size")
  285. result = false
  286. if not settings.hasKey("items"):
  287. errors.add("Items section missing")
  288. result = false
  289. else:
  290. let items = settings["items"]
  291. if items.kind != JArray or items.len == 0:
  292. errors.add "Invalid or empty item list"
  293. else:
  294. var id = 0
  295. for i in items.items:
  296. if i.kind != JArray: errors.add("Item #$1 is not an array" % $id)
  297. elif i.len != 3: errors.add("($1) Item record should have 3 fields"%($id))
  298. elif i[0].kind != JString or i[1].kind != JString or i[2].kind != JObject:
  299. errors.add("($1) Item should be in form [name, type, {item: data}]" % $id)
  300. result = false
  301. inc id
  302. proc loadSettingsFromFile*(filename: string, errors: var seq[string]): bool =
  303. if not fileExists(filename):
  304. errors.add("File does not exist: "&filename)
  305. else:
  306. result = loadSettings(readFile(filename), errors)
  307. proc loadSettings*(rawJson: string, errors: var seq[string]): bool =
  308. var settings: JsonNode
  309. try:
  310. settings = parseJson(rawJson)
  311. except JsonParsingError:
  312. errors.add("JSON parsing error: " & getCurrentExceptionMsg())
  313. return
  314. except:
  315. errors.add("Unknown exception: " & getCurrentExceptionMsg())
  316. return
  317. if not validateSettings(settings, errors):
  318. return
  319. if cfg != nil: #TODO try this
  320. echo("Overwriting zone settings")
  321. free(cfg)
  322. cfg = nil
  323. new(cfg, free)
  324. cfg.levelSettings = importLevel(settings, errors)
  325. cfg.vehicles = @[]
  326. cfg.items = @[]
  327. cfg.objects = @[]
  328. cfg.bullets = @[]
  329. nameToVehID = initTable[string, int](32)
  330. nameToItemID = initTable[string, int](32)
  331. nameToObjID = initTable[string, int](32)
  332. nameToBulletID = initTable[string, int](32)
  333. var
  334. vID = 0'i16
  335. bID = 0'i16
  336. for vehicle in settings["vehicles"].items:
  337. var veh = importVeh(vehicle, errors)
  338. veh.id = vID
  339. cfg.vehicles.add veh
  340. nameToVehID[veh.name] = veh.id
  341. inc vID
  342. vID = 0
  343. if settings.hasKey("bullets"):
  344. for blt in settings["bullets"].items:
  345. var bullet = importBullet(blt, errors)
  346. bullet.id = bID
  347. cfg.bullets.add bullet
  348. nameToBulletID[bullet.name] = bullet.id
  349. inc bID
  350. for item in settings["items"].items:
  351. var itm = importItem(item, errors)
  352. itm.id = vID
  353. cfg.items.add itm
  354. nameToItemID[itm.name] = itm.id
  355. inc vID
  356. if itm.kind == Projectile:
  357. if itm.bullet.isNil:
  358. errors.add("Projectile #$1 has no bullet!" % $vID)
  359. elif itm.bullet.id == -1:
  360. ## this item has an anonymous bullet, fix the ID and name
  361. itm.bullet.id = bID
  362. itm.bullet.name = itm.name
  363. cfg.bullets.add itm.bullet
  364. nameToBulletID[itm.bullet.name] = itm.bullet.id
  365. inc bID
  366. vID = 0
  367. for obj in settings["objects"].items:
  368. var o = importObject(obj, errors)
  369. o.id = vID
  370. cfg.objects.add o
  371. nameToObjID[o.name] = o.id
  372. inc vID
  373. result = (errors.len == 0)
  374. proc `$`*(obj: PSpriteSheet): string =
  375. return "<Sprite $1 ($2x$3) $4 rows $5 cols>" % [obj.file, $obj.framew, $obj.frameh, $obj.rows, $obj.cols]
  376. proc fetchVeh*(name: string): PVehicleRecord =
  377. return cfg.vehicles[nameToVehID[name]]
  378. proc fetchItm*(itm: string): PItemRecord =
  379. return cfg.items[nameToItemID[itm]]
  380. proc fetchObj*(name: string): PObjectRecord =
  381. return cfg.objects[nameToObjID[name]]
  382. proc fetchBullet(name: string): PBulletRecord =
  383. return cfg.bullets[nameToBulletID[name]]
  384. proc getField(node: JsonNode, field: string, target: var float) =
  385. if not node.hasKey(field):
  386. return
  387. if node[field].kind == JFloat:
  388. target = node[field].fnum
  389. elif node[field].kind == JInt:
  390. target = node[field].num.float
  391. proc getField(node: JsonNode, field: string, target: var int) =
  392. if not node.hasKey(field):
  393. return
  394. if node[field].kind == JInt:
  395. target = node[field].num.int
  396. elif node[field].kind == JFloat:
  397. target = node[field].fnum.int
  398. proc getField(node: JsonNode; field: string; target: var bool) =
  399. if not node.hasKey(field):
  400. return
  401. case node[field].kind
  402. of JBool:
  403. target = node[field].bval
  404. of JInt:
  405. target = (node[field].num != 0)
  406. of JFloat:
  407. target = (node[field].fnum != 0.0)
  408. else: discard
  409. template checkKey(node: untyped; key: string) =
  410. if not hasKey(node, key):
  411. return
  412. proc importTrail(data: JsonNode; errors: var seq[string]): TTrailRecord =
  413. checkKey(data, "trail")
  414. result.anim = importAnim(data["trail"], errors)
  415. result.timer = 1000.0
  416. getField(data["trail"], "timer", result.timer)
  417. result.timer /= 1000.0
  418. proc importLevel(data: JsonNode; errors: var seq[string]): PLevelSettings =
  419. new(result)
  420. result.size = vec2i(5000, 5000)
  421. result.starfield = @[]
  422. checkKey(data, "level")
  423. var level = data["level"]
  424. if level.hasKey("size") and level["size"].kind == JArray and level["size"].len == 2:
  425. result.size.x = level["size"][0].num.cint
  426. result.size.y = level["size"][1].num.cint
  427. if level.hasKey("starfield"):
  428. for star in level["starfield"].items:
  429. result.starfield.add(newSprite(star.str, errors))
  430. proc importPhys(data: JsonNode): TPhysicsRecord =
  431. result.radius = 20.0
  432. result.mass = 10.0
  433. if data.hasKey("physics") and data["physics"].kind == JObject:
  434. let phys = data["physics"]
  435. phys.getField("radius", result.radius)
  436. phys.getField("mass", result.mass)
  437. when not defined(NoChipmunk):
  438. result.moment = momentForCircle(result.mass, 0.0, result.radius, VectorZero) * MomentMult
  439. proc importHandling(data: JsonNode): THandlingRecord =
  440. result.thrust = 45.0
  441. result.topSpeed = 100.0 #unused
  442. result.reverse = 30.0
  443. result.strafe = 30.0
  444. result.rotation = 2200.0
  445. checkKey(data, "handling")
  446. if data["handling"].kind != JObject:
  447. return
  448. let hand = data["handling"]
  449. hand.getField("thrust", result.thrust)
  450. hand.getField("top_speed", result.topSpeed)
  451. hand.getField("reverse", result.reverse)
  452. hand.getField("strafe", result.strafe)
  453. hand.getField("rotation", result.rotation)
  454. proc importAnim(data: JsonNode, errors: var seq[string]): PAnimationRecord =
  455. new(result)
  456. result.angle = 0.0
  457. result.delay = 1000.0
  458. result.spriteSheet = nil
  459. if data.hasKey("anim"):
  460. let anim = data["anim"]
  461. if anim.kind == JObject:
  462. if anim.hasKey("file"):
  463. result.spriteSheet = newSprite(anim["file"].str, errors)
  464. anim.getField "angle", result.angle
  465. anim.getField "delay", result.delay
  466. elif data["anim"].kind == JString:
  467. result.spriteSheet = newSprite(anim.str, errors)
  468. result.angle = radians(result.angle) ## comes in as degrees
  469. result.delay /= 1000 ## delay comes in as milliseconds
  470. proc importSoul(data: JsonNode): TSoulRecord =
  471. result.energy = 10000
  472. result.health = 1
  473. checkKey(data, "soul")
  474. let soul = data["soul"]
  475. soul.getField("energy", result.energy)
  476. soul.getField("health", result.health)
  477. proc importExplosion(data: JsonNode; errors: var seq[string]): TExplosionRecord =
  478. checkKey(data, "explode")
  479. let expl = data["explode"]
  480. result.anim = importAnim(expl, errors)
  481. result.sound = importSound(expl, errors, "sound")
  482. proc importSound*(data: JsonNode; errors: var seq[string]; fieldName: string = ""): PSoundRecord =
  483. if data.kind == JObject:
  484. checkKey(data, fieldName)
  485. result = newSound(data[fieldName].str, errors)
  486. elif data.kind == JString:
  487. result = newSound(data.str, errors)
  488. proc importVeh(data: JsonNode; errors: var seq[string]): PVehicleRecord =
  489. new(result)
  490. result.playable = false
  491. if data.kind != JArray or data.len != 2 or
  492. (data.kind == JArray and
  493. (data[0].kind != JString or data[1].kind != JObject)):
  494. result.name = "(broken)"
  495. errors.add "Vehicle record is malformed"
  496. return
  497. var vehData = data[1]
  498. result.name = data[0].str
  499. result.anim = importAnim(vehdata, errors)
  500. result.physics = importPhys(vehdata)
  501. result.handling = importHandling(vehdata)
  502. vehdata.getField("playable", result.playable)
  503. if result.anim.spriteSheet.isNil and result.playable:
  504. result.playable = false
  505. proc importObject(data: JsonNode; errors: var seq[string]): PObjectRecord =
  506. new(result)
  507. if data.kind != JArray or data.len != 2:
  508. result.name = "(broken)"
  509. return
  510. result.name = data[0].str
  511. result.anim = importAnim(data[1], errors)
  512. result.physics = importPhys(data[1])
  513. proc importItem(data: JsonNode; errors: var seq[string]): PItemRecord =
  514. new(result)
  515. if data.kind != JArray or data.len != 3:
  516. result.name = "(broken)"
  517. errors.add "Item record is malformed"
  518. return
  519. result.name = data[0].str
  520. result.anim = importAnim(data[2], errors)
  521. result.physics = importPhys(data[2])
  522. result.cooldown = 100.0
  523. data[2].getField("cooldown", result.cooldown)
  524. result.cooldown /= 1000.0 ##cooldown is stored in ms
  525. result.useSound = importSound(data[2], errors, "useSound")
  526. case data[1].str.toLowerAscii
  527. of "projectile":
  528. result.kind = Projectile
  529. if data[2]["bullet"].kind == JString:
  530. result.bullet = fetchBullet(data[2]["bullet"].str)
  531. elif data[2]["bullet"].kind == JInt:
  532. result.bullet = cfg.bullets[data[2]["bullet"].num.int]
  533. elif data[2]["bullet"].kind == JObject:
  534. result.bullet = importBullet(data[2]["bullet"], errors)
  535. else:
  536. errors.add "UNKNOWN BULLET TYPE for item " & result.name
  537. of "ammo":
  538. result.kind = Ammo
  539. of "utility":
  540. discard
  541. else:
  542. errors.add "Invalid item type \""&data[1].str&"\" for item "&result.name
  543. proc importBullet(data: JsonNode; errors: var seq[string]): PBulletRecord =
  544. new(result)
  545. result.id = -1
  546. var bdata: JsonNode
  547. if data.kind == JArray:
  548. result.name = data[0].str
  549. bdata = data[1]
  550. elif data.kind == JObject:
  551. bdata = data
  552. else:
  553. errors.add "Malformed bullet record"
  554. return
  555. result.anim = importAnim(bdata, errors)
  556. result.physics = importPhys(bdata)
  557. result.lifetime = 2000.0
  558. result.inheritVelocity = 1000.0
  559. result.baseVelocity = 30.0
  560. getField(bdata, "lifetime", result.lifetime)
  561. getField(bdata, "inheritVelocity", result.inheritVelocity)
  562. getField(bdata, "baseVelocity", result.baseVelocity)
  563. result.lifetime /= 1000.0 ## lifetime is stored as milliseconds
  564. result.inheritVelocity /= 1000.0 ## inherit velocity 1000 = 1.0 (100%)
  565. result.explosion = importExplosion(bdata, errors)
  566. result.trail = importTrail(bdata, errors)