sg_assets.nim 19 KB

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