1
0

SharedBiomeSystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Numerics;
  3. using Content.Shared.Maps;
  4. using Content.Shared.Parallax.Biomes.Layers;
  5. using Robust.Shared.Map;
  6. using Robust.Shared.Map.Components;
  7. using Robust.Shared.Noise;
  8. using Robust.Shared.Prototypes;
  9. using Robust.Shared.Serialization.Manager;
  10. using Robust.Shared.Utility;
  11. namespace Content.Shared.Parallax.Biomes;
  12. public abstract class SharedBiomeSystem : EntitySystem
  13. {
  14. [Dependency] protected readonly IPrototypeManager ProtoManager = default!;
  15. [Dependency] private readonly ISerializationManager _serManager = default!;
  16. [Dependency] protected readonly ITileDefinitionManager TileDefManager = default!;
  17. [Dependency] private readonly TileSystem _tile = default!;
  18. protected const byte ChunkSize = 8;
  19. private T Pick<T>(List<T> collection, float value)
  20. {
  21. // Listen I don't need this exact and I'm too lazy to finetune just for random ent picking.
  22. value %= 1f;
  23. value = Math.Clamp(value, 0f, 1f);
  24. if (collection.Count == 1)
  25. return collection[0];
  26. var randValue = value * collection.Count;
  27. foreach (var item in collection)
  28. {
  29. randValue -= 1f;
  30. if (randValue <= 0f)
  31. {
  32. return item;
  33. }
  34. }
  35. throw new ArgumentOutOfRangeException();
  36. }
  37. private int Pick(int count, float value)
  38. {
  39. value %= 1f;
  40. value = Math.Clamp(value, 0f, 1f);
  41. if (count == 1)
  42. return 0;
  43. value *= count;
  44. for (var i = 0; i < count; i++)
  45. {
  46. value -= 1f;
  47. if (value <= 0f)
  48. {
  49. return i;
  50. }
  51. }
  52. throw new ArgumentOutOfRangeException();
  53. }
  54. public bool TryGetBiomeTile(EntityUid uid, MapGridComponent grid, Vector2i indices, [NotNullWhen(true)] out Tile? tile)
  55. {
  56. if (grid.TryGetTileRef(indices, out var tileRef) && !tileRef.Tile.IsEmpty)
  57. {
  58. tile = tileRef.Tile;
  59. return true;
  60. }
  61. if (!TryComp<BiomeComponent>(uid, out var biome))
  62. {
  63. tile = null;
  64. return false;
  65. }
  66. return TryGetBiomeTile(indices, biome.Layers, biome.Seed, grid, out tile);
  67. }
  68. /// <summary>
  69. /// Tries to get the tile, real or otherwise, for the specified indices.
  70. /// </summary>
  71. public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
  72. {
  73. if (grid?.TryGetTileRef(indices, out var tileRef) == true && !tileRef.Tile.IsEmpty)
  74. {
  75. tile = tileRef.Tile;
  76. return true;
  77. }
  78. return TryGetTile(indices, layers, seed, grid, out tile);
  79. }
  80. /// <summary>
  81. /// Gets the underlying biome tile, ignoring any existing tile that may be there.
  82. /// </summary>
  83. public bool TryGetTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
  84. {
  85. for (var i = layers.Count - 1; i >= 0; i--)
  86. {
  87. var layer = layers[i];
  88. var noiseCopy = GetNoise(layer.Noise, seed);
  89. var invert = layer.Invert;
  90. var value = noiseCopy.GetNoise(indices.X, indices.Y);
  91. value = invert ? value * -1 : value;
  92. if (value < layer.Threshold)
  93. continue;
  94. // Check if the tile is from meta layer, otherwise fall back to default layers.
  95. if (layer is BiomeMetaLayer meta)
  96. {
  97. if (TryGetBiomeTile(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, seed, grid, out tile))
  98. {
  99. return true;
  100. }
  101. continue;
  102. }
  103. if (layer is not BiomeTileLayer tileLayer)
  104. continue;
  105. if (TryGetTile(indices, noiseCopy, tileLayer.Invert, tileLayer.Threshold, ProtoManager.Index(tileLayer.Tile), tileLayer.Variants, out tile))
  106. {
  107. return true;
  108. }
  109. }
  110. tile = null;
  111. return false;
  112. }
  113. /// <summary>
  114. /// Gets the underlying biome tile, ignoring any existing tile that may be there.
  115. /// </summary>
  116. private bool TryGetTile(Vector2i indices, FastNoiseLite noise, bool invert, float threshold, ContentTileDefinition tileDef, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
  117. {
  118. var found = noise.GetNoise(indices.X, indices.Y);
  119. found = invert ? found * -1 : found;
  120. if (found < threshold)
  121. {
  122. tile = null;
  123. return false;
  124. }
  125. byte variant = 0;
  126. var variantCount = variants?.Count ?? tileDef.Variants;
  127. // Pick a variant tile if they're available as well
  128. if (variantCount > 1)
  129. {
  130. var variantValue = (noise.GetNoise(indices.X * 8, indices.Y * 8, variantCount) + 1f) * 100;
  131. variant = _tile.PickVariant(tileDef, (int) variantValue);
  132. }
  133. tile = new Tile(tileDef.TileId, variant);
  134. return true;
  135. }
  136. /// <summary>
  137. /// Tries to get the relevant entity for this tile.
  138. /// </summary>
  139. public bool TryGetEntity(Vector2i indices, BiomeComponent component, MapGridComponent grid,
  140. [NotNullWhen(true)] out string? entity)
  141. {
  142. if (!TryGetBiomeTile(indices, component.Layers, component.Seed, grid, out var tile))
  143. {
  144. entity = null;
  145. return false;
  146. }
  147. return TryGetEntity(indices, component.Layers, tile.Value, component.Seed, grid, out entity);
  148. }
  149. public bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, Tile tileRef, int seed, MapGridComponent grid,
  150. [NotNullWhen(true)] out string? entity)
  151. {
  152. var tileId = TileDefManager[tileRef.TypeId].ID;
  153. for (var i = layers.Count - 1; i >= 0; i--)
  154. {
  155. var layer = layers[i];
  156. switch (layer)
  157. {
  158. case BiomeDummyLayer:
  159. continue;
  160. case IBiomeWorldLayer worldLayer:
  161. if (!worldLayer.AllowedTiles.Contains(tileId))
  162. continue;
  163. break;
  164. case BiomeMetaLayer:
  165. break;
  166. default:
  167. continue;
  168. }
  169. var noiseCopy = GetNoise(layer.Noise, seed);
  170. var invert = layer.Invert;
  171. var value = noiseCopy.GetNoise(indices.X, indices.Y);
  172. value = invert ? value * -1 : value;
  173. if (value < layer.Threshold)
  174. continue;
  175. if (layer is BiomeMetaLayer meta)
  176. {
  177. if (TryGetEntity(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, tileRef, seed, grid, out entity))
  178. {
  179. return true;
  180. }
  181. continue;
  182. }
  183. // Decals might block entity so need to check if there's one in front of us.
  184. if (layer is not BiomeEntityLayer biomeLayer)
  185. {
  186. entity = null;
  187. return false;
  188. }
  189. var noiseValue = noiseCopy.GetNoise(indices.X, indices.Y, i);
  190. entity = Pick(biomeLayer.Entities, (noiseValue + 1f) / 2f);
  191. return true;
  192. }
  193. entity = null;
  194. return false;
  195. }
  196. /// <summary>
  197. /// Tries to get the relevant decals for this tile.
  198. /// </summary>
  199. public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent grid,
  200. [NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
  201. {
  202. if (!TryGetBiomeTile(indices, layers, seed, grid, out var tileRef))
  203. {
  204. decals = null;
  205. return false;
  206. }
  207. var tileId = TileDefManager[tileRef.Value.TypeId].ID;
  208. for (var i = layers.Count - 1; i >= 0; i--)
  209. {
  210. var layer = layers[i];
  211. // Entities might block decal so need to check if there's one in front of us.
  212. switch (layer)
  213. {
  214. case BiomeDummyLayer:
  215. continue;
  216. case IBiomeWorldLayer worldLayer:
  217. if (!worldLayer.AllowedTiles.Contains(tileId))
  218. continue;
  219. break;
  220. case BiomeMetaLayer:
  221. break;
  222. default:
  223. continue;
  224. }
  225. var invert = layer.Invert;
  226. var noiseCopy = GetNoise(layer.Noise, seed);
  227. var value = noiseCopy.GetNoise(indices.X, indices.Y);
  228. value = invert ? value * -1 : value;
  229. if (value < layer.Threshold)
  230. continue;
  231. if (layer is BiomeMetaLayer meta)
  232. {
  233. if (TryGetDecals(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, seed, grid, out decals))
  234. {
  235. return true;
  236. }
  237. continue;
  238. }
  239. // Check if the other layer should even render, if not then keep going.
  240. if (layer is not BiomeDecalLayer decalLayer)
  241. {
  242. decals = null;
  243. return false;
  244. }
  245. decals = new List<(string ID, Vector2 Position)>();
  246. for (var x = 0; x < decalLayer.Divisions; x++)
  247. {
  248. for (var y = 0; y < decalLayer.Divisions; y++)
  249. {
  250. var index = new Vector2(indices.X + x * 1f / decalLayer.Divisions, indices.Y + y * 1f / decalLayer.Divisions);
  251. var decalValue = noiseCopy.GetNoise(index.X, index.Y);
  252. decalValue = invert ? decalValue * -1 : decalValue;
  253. if (decalValue < decalLayer.Threshold)
  254. continue;
  255. decals.Add((Pick(decalLayer.Decals, (noiseCopy.GetNoise(indices.X, indices.Y, x + y * decalLayer.Divisions) + 1f) / 2f), index));
  256. }
  257. }
  258. // Check other layers
  259. if (decals.Count == 0)
  260. continue;
  261. return true;
  262. }
  263. decals = null;
  264. return false;
  265. }
  266. private FastNoiseLite GetNoise(FastNoiseLite seedNoise, int seed)
  267. {
  268. var noiseCopy = new FastNoiseLite();
  269. _serManager.CopyTo(seedNoise, ref noiseCopy, notNullableOverride: true);
  270. noiseCopy.SetSeed(noiseCopy.GetSeed() + seed);
  271. // Ensure re-calculate is run.
  272. noiseCopy.SetFractalOctaves(noiseCopy.GetFractalOctaves());
  273. return noiseCopy;
  274. }
  275. }