fivefury 0.2.7__tar.gz → 0.2.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. {fivefury-0.2.7 → fivefury-0.2.8}/PKG-INFO +27 -1
  2. fivefury-0.2.7/fivefury.egg-info/PKG-INFO → fivefury-0.2.8/README.md +25 -12
  3. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/__init__.py +32 -0
  4. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cache/core.py +41 -3
  5. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cache/io.py +3 -0
  6. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cache/scan.py +1 -1
  7. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/gamefile.py +2 -0
  8. fivefury-0.2.8/fivefury/water/__init__.py +30 -0
  9. fivefury-0.2.8/fivefury/water/geometry.py +99 -0
  10. fivefury-0.2.8/fivefury/water/io.py +195 -0
  11. fivefury-0.2.8/fivefury/water/model.py +573 -0
  12. fivefury-0.2.7/README.md → fivefury-0.2.8/fivefury.egg-info/PKG-INFO +38 -0
  13. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury.egg-info/SOURCES.txt +5 -1
  14. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury.egg-info/requires.txt +1 -0
  15. {fivefury-0.2.7 → fivefury-0.2.8}/native/rpf_archive.cpp +3 -0
  16. {fivefury-0.2.7 → fivefury-0.2.8}/pyproject.toml +5 -2
  17. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_rpf_streaming_folder.py +63 -1
  18. fivefury-0.2.8/tests/test_water.py +317 -0
  19. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ybn.py +4 -3
  20. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ydr.py +9 -13
  21. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ydr_builder.py +4 -0
  22. fivefury-0.2.7/tests/test_ymap_occluders.py +0 -37
  23. {fivefury-0.2.7 → fivefury-0.2.8}/LICENSE +0 -0
  24. {fivefury-0.2.7 → fivefury-0.2.8}/MANIFEST.in +0 -0
  25. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/_native.py +0 -0
  26. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/assets/__init__.py +0 -0
  27. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/assets/base.py +0 -0
  28. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/assets/ydd.py +0 -0
  29. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/assets/ydr.py +0 -0
  30. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/assets/yft.py +0 -0
  31. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/assets/ypt.py +0 -0
  32. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/awc/__init__.py +0 -0
  33. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/awc/audio.py +0 -0
  34. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/awc/constants.py +0 -0
  35. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/awc/conversion.py +0 -0
  36. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/awc/crypto.py +0 -0
  37. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/awc/io.py +0 -0
  38. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/awc/structures.py +0 -0
  39. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/binary.py +0 -0
  40. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/bounds/__init__.py +0 -0
  41. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/bounds/geometry.py +0 -0
  42. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/bounds/materials.py +0 -0
  43. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/bounds/model.py +0 -0
  44. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/bounds/reader.py +0 -0
  45. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/bounds/writer.py +0 -0
  46. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/buckets.py +0 -0
  47. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cache/__init__.py +0 -0
  48. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cache/assets.py +0 -0
  49. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cache/kinds.py +0 -0
  50. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cache/paths.py +0 -0
  51. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cache/views.py +0 -0
  52. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cdr/__init__.py +0 -0
  53. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cdr/model.py +0 -0
  54. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cdr/reader.py +0 -0
  55. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cdr/resource.py +0 -0
  56. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cdr/shaders.py +0 -0
  57. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cdr/vertices.py +0 -0
  58. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/colors.py +0 -0
  59. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/common.py +0 -0
  60. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/crypto/__init__.py +0 -0
  61. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/crypto/backends.py +0 -0
  62. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/crypto/keys.py +0 -0
  63. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/__init__.py +0 -0
  64. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/analysis.py +0 -0
  65. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/dsl.py +0 -0
  66. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/events.py +0 -0
  67. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/flags.py +0 -0
  68. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/lights.py +0 -0
  69. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/model.py +0 -0
  70. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/names.py +0 -0
  71. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/payloads.py +0 -0
  72. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/pso.py +0 -0
  73. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/scene/__init__.py +0 -0
  74. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/scene/base.py +0 -0
  75. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/scene/bindings.py +0 -0
  76. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/scene/core.py +0 -0
  77. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/scene/event_api.py +0 -0
  78. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/scene/io.py +0 -0
  79. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/scene/shared.py +0 -0
  80. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/scene/subtitles.py +0 -0
  81. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/scene/timeline.py +0 -0
  82. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/scene/validation.py +0 -0
  83. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/schema.py +0 -0
  84. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/write.py +0 -0
  85. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/cut/xml.py +0 -0
  86. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/data/lut.dat +0 -0
  87. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/data/magic.dat +0 -0
  88. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/data/ng.dat +0 -0
  89. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/dlc/__init__.py +0 -0
  90. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/dlc/enums.py +0 -0
  91. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/dlc/highlevel.py +0 -0
  92. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/dlc/model.py +0 -0
  93. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/dlc/xml.py +0 -0
  94. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/drawable/Shaders.xml +0 -0
  95. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/drawable/__init__.py +0 -0
  96. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/drawable/lod.py +0 -0
  97. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/drawable/model.py +0 -0
  98. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/drawable/shaders.py +0 -0
  99. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/game_target.py +0 -0
  100. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/gtxd.py +0 -0
  101. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/gxt2.py +0 -0
  102. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/hashing.py +0 -0
  103. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/meta/__init__.py +0 -0
  104. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/meta/backed.py +0 -0
  105. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/meta/builder.py +0 -0
  106. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/meta/defs.py +0 -0
  107. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/meta/read.py +0 -0
  108. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/meta/resource.py +0 -0
  109. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/meta/utils.py +0 -0
  110. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/metahash.py +0 -0
  111. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/pso/__init__.py +0 -0
  112. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/pso/codec.py +0 -0
  113. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/pso/model.py +0 -0
  114. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/pso/reader.py +0 -0
  115. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/pso/schema.py +0 -0
  116. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/pso/writer.py +0 -0
  117. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rbf.py +0 -0
  118. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rel/__init__.py +0 -0
  119. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rel/enums.py +0 -0
  120. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rel/io.py +0 -0
  121. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rel/model.py +0 -0
  122. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/resolver.py +0 -0
  123. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/resource.py +0 -0
  124. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/resource_textures.py +0 -0
  125. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rpf/__init__.py +0 -0
  126. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rpf/convert.py +0 -0
  127. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rpf/entries.py +0 -0
  128. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rpf/modes.py +0 -0
  129. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rpf/ps3.py +0 -0
  130. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rpf/streaming.py +0 -0
  131. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/rpf/utils.py +0 -0
  132. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/texture.py +0 -0
  133. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/vector.py +0 -0
  134. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/vehiclemeta/__init__.py +0 -0
  135. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/vehiclemeta/carcols.py +0 -0
  136. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/vehiclemeta/common.py +0 -0
  137. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/vehiclemeta/enums.py +0 -0
  138. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/vehiclemeta/handling.py +0 -0
  139. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/vehiclemeta/resource.py +0 -0
  140. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/vehiclemeta/variations.py +0 -0
  141. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/vehiclemeta/vehicles.py +0 -0
  142. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/xml.py +0 -0
  143. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ybn/__init__.py +0 -0
  144. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ybn/mlo.py +0 -0
  145. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ycd/__init__.py +0 -0
  146. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ycd/cutscene.py +0 -0
  147. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ycd/model.py +0 -0
  148. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ycd/reader.py +0 -0
  149. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ycd/sequence_channels.py +0 -0
  150. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ycd/sequence_codec.py +0 -0
  151. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ycd/sequence_tracks.py +0 -0
  152. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ycd/sequences.py +0 -0
  153. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ycd/write.py +0 -0
  154. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydd/__init__.py +0 -0
  155. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydd/model.py +0 -0
  156. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydd/reader.py +0 -0
  157. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydd/rigging.py +0 -0
  158. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydd/writer.py +0 -0
  159. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/ShadersGen9Conversion.xml +0 -0
  160. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/__init__.py +0 -0
  161. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/assimp.py +0 -0
  162. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/build_types.py +0 -0
  163. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/builder.py +0 -0
  164. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/collision.py +0 -0
  165. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/defs.py +0 -0
  166. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/gen9.py +0 -0
  167. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/gen9_shader_enums.py +0 -0
  168. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/gen9_shader_info.py +0 -0
  169. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/materials.py +0 -0
  170. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/model.py +0 -0
  171. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/prepare.py +0 -0
  172. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/read_joints.py +0 -0
  173. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/read_lights.py +0 -0
  174. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/read_materials.py +0 -0
  175. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/read_skeleton.py +0 -0
  176. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/reader.py +0 -0
  177. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/rigging.py +0 -0
  178. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/shader_enums.py +0 -0
  179. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/shader_info.py +0 -0
  180. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/shaders.py +0 -0
  181. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/write_buffers.py +0 -0
  182. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/write_drawable.py +0 -0
  183. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/write_geometry.py +0 -0
  184. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/write_joints.py +0 -0
  185. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/write_lights.py +0 -0
  186. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/write_materials.py +0 -0
  187. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/write_models.py +0 -0
  188. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ydr/write_skeleton.py +0 -0
  189. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yed/__init__.py +0 -0
  190. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yed/audit.py +0 -0
  191. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yed/constants.py +0 -0
  192. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yed/enums.py +0 -0
  193. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yed/instructions.py +0 -0
  194. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yed/model.py +0 -0
  195. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yed/ped.py +0 -0
  196. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yed/reader.py +0 -0
  197. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yed/writer.py +0 -0
  198. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/__init__.py +0 -0
  199. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/cloth.py +0 -0
  200. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/cloth_reader.py +0 -0
  201. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/cloth_writer.py +0 -0
  202. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/constants.py +0 -0
  203. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/corpus.py +0 -0
  204. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/drawable_reader.py +0 -0
  205. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/drawables.py +0 -0
  206. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/events.py +0 -0
  207. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/events_reader.py +0 -0
  208. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/events_writer.py +0 -0
  209. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/fields_reader.py +0 -0
  210. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/fragment.py +0 -0
  211. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/fragment_drawable.py +0 -0
  212. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/glass.py +0 -0
  213. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/glass_reader.py +0 -0
  214. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/glass_writer.py +0 -0
  215. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/io_helpers.py +0 -0
  216. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/matrices.py +0 -0
  217. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/matrices_reader.py +0 -0
  218. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/matrices_writer.py +0 -0
  219. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/model.py +0 -0
  220. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/physics.py +0 -0
  221. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/physics_authoring.py +0 -0
  222. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/physics_reader.py +0 -0
  223. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/physics_writer.py +0 -0
  224. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/pointers.py +0 -0
  225. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/reader.py +0 -0
  226. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/stats.py +0 -0
  227. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/validation.py +0 -0
  228. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/yft/writer.py +0 -0
  229. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/__init__.py +0 -0
  230. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/base.py +0 -0
  231. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/blocks.py +0 -0
  232. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/cargens.py +0 -0
  233. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/defs.py +0 -0
  234. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/entities.py +0 -0
  235. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/enums.py +0 -0
  236. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/extension_defs.py +0 -0
  237. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/extensions.py +0 -0
  238. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/grass.py +0 -0
  239. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/io.py +0 -0
  240. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/lights.py +0 -0
  241. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/mlo_validation.py +0 -0
  242. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/model.py +0 -0
  243. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/occluders.py +0 -0
  244. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/packing.py +0 -0
  245. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/surfaces.py +0 -0
  246. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/timecycle.py +0 -0
  247. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymap/utils.py +0 -0
  248. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymf/__init__.py +0 -0
  249. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymf/builder.py +0 -0
  250. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymf/enums.py +0 -0
  251. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymf/model.py +0 -0
  252. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymf/pso.py +0 -0
  253. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymf/resource.py +0 -0
  254. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymf/schema.py +0 -0
  255. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymf/utils.py +0 -0
  256. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymt/__init__.py +0 -0
  257. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymt/ped_metadata.py +0 -0
  258. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymt/ped_variation.py +0 -0
  259. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymt/scenario.py +0 -0
  260. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ymt/streaming.py +0 -0
  261. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynd/__init__.py +0 -0
  262. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynd/heightmaps.py +0 -0
  263. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynd/model.py +0 -0
  264. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynd/network.py +0 -0
  265. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynd/reader.py +0 -0
  266. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynd/regions.py +0 -0
  267. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynd/writer.py +0 -0
  268. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynv/__init__.py +0 -0
  269. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynv/assimp.py +0 -0
  270. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynv/model.py +0 -0
  271. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynv/reader.py +0 -0
  272. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ynv/writer.py +0 -0
  273. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytd/__init__.py +0 -0
  274. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytd/defs.py +0 -0
  275. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytd/model.py +0 -0
  276. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/__init__.py +0 -0
  277. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/archetypes.py +0 -0
  278. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/asset_types.py +0 -0
  279. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/base_archetype.py +0 -0
  280. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/defs.py +0 -0
  281. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/extension_defs.py +0 -0
  282. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/extensions.py +0 -0
  283. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/flags.py +0 -0
  284. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/helpers.py +0 -0
  285. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/lod.py +0 -0
  286. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/mlo.py +0 -0
  287. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/mlo_validation.py +0 -0
  288. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/model.py +0 -0
  289. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury/ytyp/timed_archetype.py +0 -0
  290. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury.egg-info/dependency_links.txt +0 -0
  291. {fivefury-0.2.7 → fivefury-0.2.8}/fivefury.egg-info/top_level.txt +0 -0
  292. {fivefury-0.2.7 → fivefury-0.2.8}/native/bounds_algorithms.cpp +0 -0
  293. {fivefury-0.2.7 → fivefury-0.2.8}/native/bounds_algorithms.h +0 -0
  294. {fivefury-0.2.7 → fivefury-0.2.8}/native/bounds_python.cpp +0 -0
  295. {fivefury-0.2.7 → fivefury-0.2.8}/native/bounds_python.h +0 -0
  296. {fivefury-0.2.7 → fivefury-0.2.8}/native/bounds_types.h +0 -0
  297. {fivefury-0.2.7 → fivefury-0.2.8}/native/crypto_magic.cpp +0 -0
  298. {fivefury-0.2.7 → fivefury-0.2.8}/native/crypto_magic.h +0 -0
  299. {fivefury-0.2.7 → fivefury-0.2.8}/native/py_bindings.cpp +0 -0
  300. {fivefury-0.2.7 → fivefury-0.2.8}/native/py_bindings.h +0 -0
  301. {fivefury-0.2.7 → fivefury-0.2.8}/native/py_bounds.cpp +0 -0
  302. {fivefury-0.2.7 → fivefury-0.2.8}/native/py_crypto.cpp +0 -0
  303. {fivefury-0.2.7 → fivefury-0.2.8}/native/py_index.cpp +0 -0
  304. {fivefury-0.2.7 → fivefury-0.2.8}/native/py_module.cpp +0 -0
  305. {fivefury-0.2.7 → fivefury-0.2.8}/native/py_resource.cpp +0 -0
  306. {fivefury-0.2.7 → fivefury-0.2.8}/native/py_rpf.cpp +0 -0
  307. {fivefury-0.2.7 → fivefury-0.2.8}/native/py_vertex.cpp +0 -0
  308. {fivefury-0.2.7 → fivefury-0.2.8}/native/resource_layout.cpp +0 -0
  309. {fivefury-0.2.7 → fivefury-0.2.8}/native/resource_layout.h +0 -0
  310. {fivefury-0.2.7 → fivefury-0.2.8}/native/rpf_archive.h +0 -0
  311. {fivefury-0.2.7 → fivefury-0.2.8}/native/rpf_crypto.cpp +0 -0
  312. {fivefury-0.2.7 → fivefury-0.2.8}/native/rpf_index.cpp +0 -0
  313. {fivefury-0.2.7 → fivefury-0.2.8}/native/rpf_index.h +0 -0
  314. {fivefury-0.2.7 → fivefury-0.2.8}/native/rpf_read.cpp +0 -0
  315. {fivefury-0.2.7 → fivefury-0.2.8}/native/rpf_scan.cpp +0 -0
  316. {fivefury-0.2.7 → fivefury-0.2.8}/native/rpf_scan.h +0 -0
  317. {fivefury-0.2.7 → fivefury-0.2.8}/setup.cfg +0 -0
  318. {fivefury-0.2.7 → fivefury-0.2.8}/setup.py +0 -0
  319. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_cdr.py +0 -0
  320. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_cut.py +0 -0
  321. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_cut_dsl.py +0 -0
  322. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_cut_no_template.py +0 -0
  323. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_dlc.py +0 -0
  324. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_drawable.py +0 -0
  325. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_gtxd.py +0 -0
  326. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_hashing.py +0 -0
  327. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_meta_rpf_contracts.py +0 -0
  328. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_mlo.py +0 -0
  329. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_rpf_ps3.py +0 -0
  330. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_vehiclemeta.py +0 -0
  331. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ycd.py +0 -0
  332. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ycd_cutscene_builder.py +0 -0
  333. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ydd.py +0 -0
  334. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ydr_shaders.py +0 -0
  335. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_yft.py +0 -0
  336. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ymf.py +0 -0
  337. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ynd.py +0 -0
  338. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ynv.py +0 -0
  339. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ynv_assimp.py +0 -0
  340. {fivefury-0.2.7 → fivefury-0.2.8}/tests/test_ytyp_flags.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fivefury
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Python library for GTA V asset workflows including YMAP, YTYP, RPF, YTD and GameFileCache utilities.
5
5
  Author: Codex
6
6
  License-Expression: CC0-1.0
@@ -8,6 +8,7 @@ Requires-Python: >=3.11
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: miniaudio>=1.71
11
+ Requires-Dist: numpy>=1.26
11
12
  Dynamic: license-file
12
13
 
13
14
  # FiveFury
@@ -41,6 +42,7 @@ The Assimp-backed import helpers (`assimp_to_ydr`, `obj_to_ydr`, `fbx_to_ydr`, `
41
42
  | `YTD` | Full | Texture dictionaries: read/write, extraction, embedded-asset helpers |
42
43
  | `YND` | Full | Path nodes, links, area partitioning, junction heightmaps |
43
44
  | `YNV` | Full | Navmeshes: sectors, polys, portals, validation, basic OBJ partitioning |
45
+ | `water.xml` | Full | Water surfaces, triangle corners, calming regions, wave amplitude and direction |
44
46
  | `CUT` | Full | Cutscenes, plus the readable `.cuts` script format for round-trip authoring |
45
47
  | `GXT2` | Full | Hashed text tables with binary read/write and text import/export |
46
48
  | `AWC` | Full | Audio containers: PCM/WAV extraction, authoring from WAV/MP3/OGG/FLAC |
@@ -114,6 +116,30 @@ save_ybn(bound, "floor_collision.ybn")
114
116
 
115
117
  Generated geometry is chunked as needed and gets BVH and octant data. The same bounds model backs standalone `YBN` files, embedded `YDR` collisions, and `YFT` physics — a drawable's render mesh can also be converted directly with `ydr.ensure_bound_from_render_geometry()`.
116
118
 
119
+ ### Author water
120
+
121
+ ```python
122
+ from fivefury import WaterData, WaterQuad, WaterWaveQuad
123
+
124
+ water = WaterData()
125
+ water.add(
126
+ WaterQuad.rectangle(
127
+ center=(100.0, 200.0, 12.5),
128
+ size=(80.0, 40.0),
129
+ alpha=26,
130
+ limited_depth=True,
131
+ )
132
+ )
133
+ water.add(
134
+ WaterWaveQuad.from_angle(
135
+ bounds=(60, 180, 140, 220),
136
+ amplitude=1.2,
137
+ degrees=90.0,
138
+ )
139
+ )
140
+ water.translate(x=500, y=-200, z=20.0).save("water.xml")
141
+ ```
142
+
117
143
  ### Convert audio to AWC
118
144
 
119
145
  ```python
@@ -1,15 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: fivefury
3
- Version: 0.2.7
4
- Summary: Python library for GTA V asset workflows including YMAP, YTYP, RPF, YTD and GameFileCache utilities.
5
- Author: Codex
6
- License-Expression: CC0-1.0
7
- Requires-Python: >=3.11
8
- Description-Content-Type: text/markdown
9
- License-File: LICENSE
10
- Requires-Dist: miniaudio>=1.71
11
- Dynamic: license-file
12
-
13
1
  # FiveFury
14
2
 
15
3
  FiveFury is a Python library for reading, writing, and packaging GTA V asset files: drawables, collisions, map metadata, animations, navigation data, texture dictionaries, text tables, audio containers, cutscenes, DLC metadata, and RPF archives.
@@ -41,6 +29,7 @@ The Assimp-backed import helpers (`assimp_to_ydr`, `obj_to_ydr`, `fbx_to_ydr`, `
41
29
  | `YTD` | Full | Texture dictionaries: read/write, extraction, embedded-asset helpers |
42
30
  | `YND` | Full | Path nodes, links, area partitioning, junction heightmaps |
43
31
  | `YNV` | Full | Navmeshes: sectors, polys, portals, validation, basic OBJ partitioning |
32
+ | `water.xml` | Full | Water surfaces, triangle corners, calming regions, wave amplitude and direction |
44
33
  | `CUT` | Full | Cutscenes, plus the readable `.cuts` script format for round-trip authoring |
45
34
  | `GXT2` | Full | Hashed text tables with binary read/write and text import/export |
46
35
  | `AWC` | Full | Audio containers: PCM/WAV extraction, authoring from WAV/MP3/OGG/FLAC |
@@ -114,6 +103,30 @@ save_ybn(bound, "floor_collision.ybn")
114
103
 
115
104
  Generated geometry is chunked as needed and gets BVH and octant data. The same bounds model backs standalone `YBN` files, embedded `YDR` collisions, and `YFT` physics — a drawable's render mesh can also be converted directly with `ydr.ensure_bound_from_render_geometry()`.
116
105
 
106
+ ### Author water
107
+
108
+ ```python
109
+ from fivefury import WaterData, WaterQuad, WaterWaveQuad
110
+
111
+ water = WaterData()
112
+ water.add(
113
+ WaterQuad.rectangle(
114
+ center=(100.0, 200.0, 12.5),
115
+ size=(80.0, 40.0),
116
+ alpha=26,
117
+ limited_depth=True,
118
+ )
119
+ )
120
+ water.add(
121
+ WaterWaveQuad.from_angle(
122
+ bounds=(60, 180, 140, 220),
123
+ amplitude=1.2,
124
+ degrees=90.0,
125
+ )
126
+ )
127
+ water.translate(x=500, y=-200, z=20.0).save("water.xml")
128
+ ```
129
+
117
130
  ### Convert audio to AWC
118
131
 
119
132
  ```python
@@ -459,6 +459,23 @@ from .rpf import (
459
459
  zip_to_rpf,
460
460
  )
461
461
  from .texture import BCFormat
462
+ from .water import (
463
+ WaterAlpha,
464
+ WaterBounds,
465
+ WaterCalmingQuad,
466
+ WaterComponent,
467
+ WaterCornerAlphas,
468
+ WaterData,
469
+ WaterQuad,
470
+ WaterQuadType,
471
+ WaterValidationError,
472
+ WaterWaveQuad,
473
+ build_water_xml,
474
+ coerce_water_data,
475
+ create_water,
476
+ read_water,
477
+ save_water,
478
+ )
462
479
  from .ydr import (
463
480
  AssimpMaterial,
464
481
  AssimpScene,
@@ -1310,6 +1327,16 @@ __all__ = [
1310
1327
  "VehicleVariation",
1311
1328
  "VehicleWheelType",
1312
1329
  "VehicleWindow",
1330
+ "WaterAlpha",
1331
+ "WaterBounds",
1332
+ "WaterCalmingQuad",
1333
+ "WaterComponent",
1334
+ "WaterCornerAlphas",
1335
+ "WaterData",
1336
+ "WaterQuad",
1337
+ "WaterQuadType",
1338
+ "WaterValidationError",
1339
+ "WaterWaveQuad",
1313
1340
  "WalkDontWalkExtension",
1314
1341
  "WindDisturbanceExtension",
1315
1342
  "Ycd",
@@ -1568,6 +1595,7 @@ __all__ = [
1568
1595
  "mark_cutscene_prop_archetypes",
1569
1596
  "assimp_to_ydr",
1570
1597
  "build_rsc7",
1598
+ "build_water_xml",
1571
1599
  "build_ynv_bytes",
1572
1600
  "build_ynv_system_layout",
1573
1601
  "build_bound_from_render_geometry",
@@ -1589,11 +1617,13 @@ __all__ = [
1589
1617
  "clear_hash_resolver",
1590
1618
  "clear_game_crypto",
1591
1619
  "create_rpf",
1620
+ "create_water",
1592
1621
  "create_ymf_for_ymaps",
1593
1622
  "create_ydd",
1594
1623
  "create_ydr",
1595
1624
  "convert_audio_to_awc",
1596
1625
  "coerce_ped_component",
1626
+ "coerce_water_data",
1597
1627
  "ensure_game_crypto",
1598
1628
  "get_game_crypto",
1599
1629
  "get_hash_resolver",
@@ -1670,6 +1700,7 @@ __all__ = [
1670
1700
  "read_ynv",
1671
1701
  "read_ymap",
1672
1702
  "read_vehicle_meta",
1703
+ "read_water",
1673
1704
  "read_ymf",
1674
1705
  "read_ymf_xml",
1675
1706
  "read_ymt",
@@ -1699,6 +1730,7 @@ __all__ = [
1699
1730
  "save_ymt",
1700
1731
  "save_ytd",
1701
1732
  "save_ytyp",
1733
+ "save_water",
1702
1734
  "set_ped_drawable_cloth",
1703
1735
  "time_flags",
1704
1736
  "save_cut",
@@ -4,7 +4,7 @@ from collections import OrderedDict
4
4
  from collections.abc import Mapping, Sequence
5
5
  from dataclasses import dataclass, field
6
6
  from pathlib import Path
7
- from typing import Any, Iterator
7
+ from typing import Any, Iterator, Self
8
8
 
9
9
  from .assets import GameFileCacheAssetMixin, TextureRef
10
10
  from .io import GameFileCacheIOMixin
@@ -100,6 +100,12 @@ class GameFileCache(GameFileCacheScanMixin, GameFileCacheAssetMixin, GameFileCac
100
100
  def __iter__(self) -> Iterator[AssetRecord]:
101
101
  return self.iter_assets()
102
102
 
103
+ def __enter__(self) -> Self:
104
+ return self
105
+
106
+ def __exit__(self, *exc_info: object) -> None:
107
+ self.close()
108
+
103
109
  @property
104
110
  def scan_complete(self) -> bool:
105
111
  return self.last_scan is not None
@@ -139,7 +145,30 @@ class GameFileCache(GameFileCacheScanMixin, GameFileCacheAssetMixin, GameFileCac
139
145
  self._texture_parent_view = None
140
146
  self._kind_counts_view = None
141
147
 
148
+ def _close_runtime_archives(self) -> None:
149
+ seen: set[int] = set()
150
+ candidates = [
151
+ *self.archives,
152
+ *self._archive_lookup.values(),
153
+ *self._live_archives.values(),
154
+ ]
155
+ for game_file in self.files.values():
156
+ if game_file.archive is not None:
157
+ candidates.append(game_file.archive)
158
+ if isinstance(game_file.parsed, RpfArchive):
159
+ candidates.append(game_file.parsed)
160
+ for archive in candidates:
161
+ identity = id(archive)
162
+ if identity in seen:
163
+ continue
164
+ seen.add(identity)
165
+ archive.close()
166
+
167
+ def close(self) -> None:
168
+ self.clear_runtime_cache(loaded_files=True)
169
+
142
170
  def clear(self) -> None:
171
+ self._close_runtime_archives()
143
172
  self.archives.clear()
144
173
  self.files.clear()
145
174
  self.entries.clear()
@@ -155,6 +184,7 @@ class GameFileCache(GameFileCacheScanMixin, GameFileCacheAssetMixin, GameFileCac
155
184
  self._invalidate_views()
156
185
 
157
186
  def clear_runtime_cache(self, *, loaded_files: bool = False) -> None:
187
+ self._close_runtime_archives()
158
188
  self.archives.clear()
159
189
  self.entries.clear()
160
190
  self._archive_lookup.clear()
@@ -300,12 +330,18 @@ class GameFileCache(GameFileCacheScanMixin, GameFileCacheAssetMixin, GameFileCac
300
330
  normalized = _normalize_key(key)
301
331
  limit = max(0, int(self.max_open_archives))
302
332
  if limit <= 0:
333
+ for cached in self._archive_lookup.values():
334
+ cached.close()
303
335
  self._archive_lookup.clear()
304
336
  return
305
- self._archive_lookup.pop(normalized, None)
337
+ previous = self._archive_lookup.pop(normalized, None)
338
+ if previous is not None and previous is not archive:
339
+ previous.close()
306
340
  self._archive_lookup[normalized] = archive
307
341
  while len(self._archive_lookup) > limit:
308
- self._archive_lookup.popitem(last=False)
342
+ _, evicted = self._archive_lookup.popitem(last=False)
343
+ if all(cached is not evicted for cached in self._archive_lookup.values()):
344
+ evicted.close()
309
345
 
310
346
  def _register_asset(
311
347
  self,
@@ -518,6 +554,8 @@ _KIND_DICT_TYPES: dict[str, GameFileType] = {
518
554
  "GtxdDict": GameFileType.GTXD,
519
555
  "AwcDict": GameFileType.AWC,
520
556
  "YedDict": GameFileType.YED,
557
+ "WaterDict": GameFileType.WATER,
558
+ "WaterMapDict": GameFileType.WATER,
521
559
  }
522
560
 
523
561
 
@@ -22,6 +22,7 @@ from ..rpf import (
22
22
  _normalize_key,
23
23
  )
24
24
  from ..vehiclemeta import read_vehicle_meta
25
+ from ..water import read_water
25
26
  from ..ybn import read_ybn
26
27
  from ..ycd import read_ycd
27
28
  from ..ydd import read_ydd
@@ -65,6 +66,8 @@ def _decode_dynamic(data: bytes, *, module_name: str, attribute: str, kind: Game
65
66
  def _decode_payload(path: str, data: bytes, *, raw: bytes | None = None) -> tuple[Any, GameFileType]:
66
67
  ext = Path(path).suffix.lower()
67
68
  name = Path(path).name.lower()
69
+ if name == "water.xml":
70
+ return _decode_or_fallback(GameFileType.WATER, data, data, read_water)
68
71
  if name == "gtxd.meta":
69
72
  return _decode_or_fallback(GameFileType.GTXD, data, data, read_gtxd)
70
73
  vehicle_meta_type = guess_game_file_type(path)
@@ -15,7 +15,7 @@ from ..gamefile import GameFileType, guess_game_file_type
15
15
  from ..hashing import _get_lut
16
16
  from ..rpf import RpfArchive, RpfFileEntry, _normalize_key
17
17
 
18
- _SCAN_INDEX_VERSION = 8
18
+ _SCAN_INDEX_VERSION = 9
19
19
  _SCAN_GC_INTERVAL = 8
20
20
 
21
21
  _FLAG_LOOSE = 1
@@ -40,6 +40,7 @@ class GameFileType(IntEnum):
40
40
  YED = 25
41
41
  YFD = 27
42
42
  HEIGHTMAP = 28
43
+ WATER = 29
43
44
  WATERMAP = 29
44
45
  MRF = 30
45
46
  DISTANT_LIGHTS = 31
@@ -88,6 +89,7 @@ def guess_game_file_type(path: str | Path, default: GameFileType = GameFileType.
88
89
  "carvariations.meta": GameFileType.CAR_VARIATIONS,
89
90
  "vehiclelayouts.meta": GameFileType.VEHICLE_LAYOUTS,
90
91
  "peds.meta": GameFileType.PEDS,
92
+ "water.xml": GameFileType.WATER,
91
93
  }
92
94
  named_type = named_meta_types.get(parsed.name.lower())
93
95
  if named_type is not None:
@@ -0,0 +1,30 @@
1
+ from .geometry import WaterAlpha, WaterBounds, WaterCornerAlphas
2
+ from .io import build_water_xml, create_water, read_water, save_water
3
+ from .model import (
4
+ WaterCalmingQuad,
5
+ WaterComponent,
6
+ WaterData,
7
+ WaterQuad,
8
+ WaterQuadType,
9
+ WaterValidationError,
10
+ WaterWaveQuad,
11
+ coerce_water_data,
12
+ )
13
+
14
+ __all__ = [
15
+ "WaterAlpha",
16
+ "WaterBounds",
17
+ "WaterCalmingQuad",
18
+ "WaterComponent",
19
+ "WaterCornerAlphas",
20
+ "WaterData",
21
+ "WaterQuad",
22
+ "WaterQuadType",
23
+ "WaterValidationError",
24
+ "WaterWaveQuad",
25
+ "build_water_xml",
26
+ "coerce_water_data",
27
+ "create_water",
28
+ "read_water",
29
+ "save_water",
30
+ ]
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import math
5
+
6
+ WaterBounds = tuple[int, int, int, int]
7
+
8
+
9
+ @dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
10
+ class WaterCornerAlphas:
11
+ southwest: int = 26
12
+ southeast: int = 26
13
+ northeast: int = 26
14
+ northwest: int = 26
15
+
16
+ def values(self) -> tuple[int, int, int, int]:
17
+ return (
18
+ int(self.southwest),
19
+ int(self.southeast),
20
+ int(self.northeast),
21
+ int(self.northwest),
22
+ )
23
+
24
+
25
+ WaterAlpha = int | WaterCornerAlphas
26
+
27
+
28
+ def _coerce_alphas(value: WaterAlpha) -> tuple[int, int, int, int]:
29
+ if isinstance(value, int):
30
+ return value, value, value, value
31
+ if isinstance(value, WaterCornerAlphas):
32
+ return value.values()
33
+ raise TypeError("alpha must be an integer or WaterCornerAlphas")
34
+
35
+
36
+ def _grid_integer(value: float, *, label: str) -> int:
37
+ number = float(value)
38
+ rounded = round(number)
39
+ if not math.isfinite(number) or not math.isclose(
40
+ number,
41
+ rounded,
42
+ abs_tol=1e-9,
43
+ ):
44
+ raise ValueError(f"{label} must be an integer water coordinate")
45
+ return rounded
46
+
47
+
48
+ def _centered_bounds(
49
+ center: tuple[float, float],
50
+ size: tuple[float, float],
51
+ ) -> WaterBounds:
52
+ center_x, center_y = (float(component) for component in center)
53
+ width, height = (float(component) for component in size)
54
+ if not all(math.isfinite(value) for value in (center_x, center_y, width, height)):
55
+ raise ValueError("center and size must be finite")
56
+ if width <= 0.0 or height <= 0.0:
57
+ raise ValueError("size components must be greater than zero")
58
+ raw_bounds = (
59
+ center_x - width * 0.5,
60
+ center_y - height * 0.5,
61
+ center_x + width * 0.5,
62
+ center_y + height * 0.5,
63
+ )
64
+ try:
65
+ return tuple(
66
+ _grid_integer(value, label="center and size result") for value in raw_bounds
67
+ )
68
+ except ValueError as exc:
69
+ raise ValueError("center and size must produce integer water bounds") from exc
70
+
71
+
72
+ def _coerce_bounds(bounds: WaterBounds) -> WaterBounds:
73
+ if len(bounds) != 4:
74
+ raise ValueError("bounds must contain min_x, min_y, max_x, max_y")
75
+ return tuple(_grid_integer(value, label="bounds component") for value in bounds)
76
+
77
+
78
+ def _contains_xy(
79
+ points: tuple[tuple[float, float, float], ...],
80
+ x: float,
81
+ y: float,
82
+ ) -> bool:
83
+ if not points:
84
+ return False
85
+ signs: list[float] = []
86
+ for index, (x1, y1, _) in enumerate(points):
87
+ x2, y2, _ = points[(index + 1) % len(points)]
88
+ signs.append((x2 - x1) * (y - y1) - (y2 - y1) * (x - x1))
89
+ tolerance = 1e-9
90
+ return all(sign >= -tolerance for sign in signs) or all(
91
+ sign <= tolerance for sign in signs
92
+ )
93
+
94
+
95
+ __all__ = [
96
+ "WaterAlpha",
97
+ "WaterBounds",
98
+ "WaterCornerAlphas",
99
+ ]
@@ -0,0 +1,195 @@
1
+ from __future__ import annotations
2
+
3
+ import xml.etree.ElementTree as ET
4
+ from pathlib import Path
5
+
6
+ from ..xml import (
7
+ add_value,
8
+ child_by_name,
9
+ child_value,
10
+ children_by_name,
11
+ parse_bool,
12
+ parse_xml_root,
13
+ xml_bytes,
14
+ )
15
+ from .model import (
16
+ WaterCalmingQuad,
17
+ WaterComponent,
18
+ WaterData,
19
+ WaterQuad,
20
+ WaterWaveQuad,
21
+ coerce_water_data,
22
+ )
23
+
24
+
25
+ def _int_value(element: ET.Element, name: str, default: int = 0) -> int:
26
+ text = child_value(element, name, str(default))
27
+ try:
28
+ return int(text, 0)
29
+ except ValueError:
30
+ return default
31
+
32
+
33
+ def _float_value(element: ET.Element, name: str, default: float = 0.0) -> float:
34
+ text = child_value(element, name, str(default))
35
+ try:
36
+ return float(text)
37
+ except ValueError:
38
+ return default
39
+
40
+
41
+ def _float_text(value: float) -> str:
42
+ text = format(float(value), ".9g")
43
+ return text if "." in text or "e" in text.lower() else f"{text}.0"
44
+
45
+
46
+ def _item_elements(root: ET.Element, section_name: str) -> list[ET.Element]:
47
+ section = child_by_name(root, section_name)
48
+ if section is None:
49
+ return []
50
+ return [item for item in children_by_name(section, "Item") if len(item)]
51
+
52
+
53
+ def _read_water_quad(item: ET.Element) -> WaterQuad:
54
+ return WaterQuad(
55
+ min_x=_int_value(item, "minX"),
56
+ min_y=_int_value(item, "minY"),
57
+ max_x=_int_value(item, "maxX"),
58
+ max_y=_int_value(item, "maxY"),
59
+ type=_int_value(item, "Type"),
60
+ is_invisible=parse_bool(child_value(item, "IsInvisible")),
61
+ has_limited_depth=parse_bool(child_value(item, "HasLimitedDepth")),
62
+ z=_float_value(item, "z"),
63
+ alpha_sw=_int_value(item, "a1", 26),
64
+ alpha_se=_int_value(item, "a2", 26),
65
+ alpha_ne=_int_value(item, "a3", 26),
66
+ alpha_nw=_int_value(item, "a4", 26),
67
+ no_stencil=parse_bool(child_value(item, "NoStencil")),
68
+ )
69
+
70
+
71
+ def _read_calming_quad(item: ET.Element) -> WaterCalmingQuad:
72
+ return WaterCalmingQuad(
73
+ min_x=_int_value(item, "minX"),
74
+ min_y=_int_value(item, "minY"),
75
+ max_x=_int_value(item, "maxX"),
76
+ max_y=_int_value(item, "maxY"),
77
+ dampening=_float_value(item, "fDampening"),
78
+ )
79
+
80
+
81
+ def _read_wave_quad(item: ET.Element) -> WaterWaveQuad:
82
+ return WaterWaveQuad(
83
+ min_x=_int_value(item, "minX"),
84
+ min_y=_int_value(item, "minY"),
85
+ max_x=_int_value(item, "maxX"),
86
+ max_y=_int_value(item, "maxY"),
87
+ amplitude=_float_value(item, "Amplitude"),
88
+ direction_x=_float_value(item, "XDirection"),
89
+ direction_y=_float_value(item, "YDirection"),
90
+ )
91
+
92
+
93
+ def read_water(source: bytes | str | Path) -> WaterData:
94
+ root = parse_xml_root(source)
95
+ if root.tag.lower() != "waterdata":
96
+ raise ValueError(f"Expected WaterData XML root, got {root.tag!r}")
97
+ return WaterData(
98
+ water_quads=[
99
+ _read_water_quad(item) for item in _item_elements(root, "WaterQuads")
100
+ ],
101
+ calming_quads=[
102
+ _read_calming_quad(item) for item in _item_elements(root, "CalmingQuads")
103
+ ],
104
+ wave_quads=[
105
+ _read_wave_quad(item) for item in _item_elements(root, "WaveQuads")
106
+ ],
107
+ )
108
+
109
+
110
+ def _write_bounds(item: ET.Element, quad: WaterComponent) -> None:
111
+ add_value(item, "minX", quad.min_x)
112
+ add_value(item, "maxX", quad.max_x)
113
+ add_value(item, "minY", quad.min_y)
114
+ add_value(item, "maxY", quad.max_y)
115
+
116
+
117
+ def _water_quad_element(quad: WaterQuad) -> ET.Element:
118
+ item = ET.Element("Item")
119
+ _write_bounds(item, quad)
120
+ add_value(item, "Type", int(quad.type))
121
+ add_value(item, "IsInvisible", quad.is_invisible)
122
+ add_value(item, "HasLimitedDepth", quad.has_limited_depth)
123
+ add_value(item, "z", _float_text(quad.z))
124
+ add_value(item, "a1", quad.alpha_sw)
125
+ add_value(item, "a2", quad.alpha_se)
126
+ add_value(item, "a3", quad.alpha_ne)
127
+ add_value(item, "a4", quad.alpha_nw)
128
+ add_value(item, "NoStencil", quad.no_stencil)
129
+ return item
130
+
131
+
132
+ def _calming_quad_element(quad: WaterCalmingQuad) -> ET.Element:
133
+ item = ET.Element("Item")
134
+ _write_bounds(item, quad)
135
+ add_value(item, "fDampening", _float_text(quad.dampening))
136
+ return item
137
+
138
+
139
+ def _wave_quad_element(quad: WaterWaveQuad) -> ET.Element:
140
+ item = ET.Element("Item")
141
+ _write_bounds(item, quad)
142
+ add_value(item, "Amplitude", _float_text(quad.amplitude))
143
+ add_value(item, "XDirection", _float_text(quad.direction_x))
144
+ add_value(item, "YDirection", _float_text(quad.direction_y))
145
+ return item
146
+
147
+
148
+ def _append_section(
149
+ root: ET.Element,
150
+ name: str,
151
+ items: list[WaterComponent],
152
+ factory,
153
+ ) -> None:
154
+ if not items:
155
+ return
156
+ section = ET.SubElement(root, name)
157
+ for item in items:
158
+ section.append(factory(item))
159
+
160
+
161
+ def build_water_xml(source: WaterData, *, validate: bool = True) -> bytes:
162
+ water = source.build()
163
+ if validate:
164
+ water.ensure_valid()
165
+ root = ET.Element("WaterData")
166
+ _append_section(root, "WaterQuads", water.water_quads, _water_quad_element)
167
+ _append_section(
168
+ root,
169
+ "CalmingQuads",
170
+ water.calming_quads,
171
+ _calming_quad_element,
172
+ )
173
+ _append_section(root, "WaveQuads", water.wave_quads, _wave_quad_element)
174
+ return xml_bytes(root)
175
+
176
+
177
+ def create_water(*items: WaterComponent) -> WaterData:
178
+ return coerce_water_data(list(items))
179
+
180
+
181
+ def save_water(
182
+ source: WaterData,
183
+ path: str | Path,
184
+ *,
185
+ validate: bool = True,
186
+ ) -> Path:
187
+ return source.save(path, validate=validate)
188
+
189
+
190
+ __all__ = [
191
+ "build_water_xml",
192
+ "create_water",
193
+ "read_water",
194
+ "save_water",
195
+ ]