1
0

WeatherNomadsSystem.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Content.Shared.Weather;
  4. using Robust.Shared.Map;
  5. using Robust.Shared.Prototypes;
  6. using Robust.Shared.Timing;
  7. using Robust.Shared.GameObjects;
  8. using Content.Server.Atmos.Components;
  9. using Content.Server.Atmos.EntitySystems;
  10. using Content.Shared.Atmos;
  11. using Content.Shared.Maps;
  12. using Robust.Shared.Map.Components;
  13. using Content.Server.Chat.Systems;
  14. using Content.Shared.Light.EntitySystems;
  15. using Content.Shared.Light.Components;
  16. using System;
  17. namespace Content.Server.Weather;
  18. /// <summary>
  19. /// System responsible for managing dynamic weather changes and temperature adjustments for exposed tiles in a grid.
  20. /// </summary>
  21. public sealed class WeatherNomadsSystem : EntitySystem
  22. {
  23. // Dependencies injected via IoC
  24. [Dependency] private readonly IGameTiming _timing = default!;
  25. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  26. [Dependency] private readonly SharedWeatherSystem _weatherSystem = default!;
  27. [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
  28. [Dependency] private readonly IMapManager _mapManager = default!;
  29. [Dependency] private readonly SharedRoofSystem _roofSystem = default!;
  30. [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
  31. [Dependency] private readonly SharedMapSystem _mapSystem = default!;
  32. [Dependency] private readonly ChatSystem _chat = default!;
  33. /// <summary>
  34. /// Structure representing properties of a weather type.
  35. /// </summary>
  36. private class WeatherType
  37. {
  38. public string? PrototypeId { get; set; } // ID of the weather prototype, null for "Clear"
  39. public int Weight { get; set; } // Weight for weather transition order (unused now, kept for compatibility)
  40. public float MinTemperature { get; set; } // Minimum temperature in Kelvin
  41. public float MaxTemperature { get; set; } // Maximum temperature in Kelvin
  42. }
  43. /// <summary>
  44. /// Dictionary defining available weather types and their properties.
  45. /// </summary>
  46. private readonly Dictionary<string, WeatherType> _weatherTypes = new()
  47. {
  48. { "Clear", new WeatherType { PrototypeId = "", Weight = 0, MinTemperature = 293.15f, MaxTemperature = 293.15f } },
  49. { "Rain", new WeatherType { PrototypeId = "Rain", Weight = 1, MinTemperature = 278.15f, MaxTemperature = 288.15f } },
  50. { "Storm", new WeatherType { PrototypeId = "Storm", Weight = 3, MinTemperature = 273.15f, MaxTemperature = 278.15f } },
  51. { "SnowfallLight", new WeatherType { PrototypeId = "SnowfallLight", Weight = 4, MinTemperature = 268.15f, MaxTemperature = 273.15f } },
  52. { "SnowfallMedium", new WeatherType { PrototypeId = "SnowfallMedium", Weight = 5, MinTemperature = 258.15f, MaxTemperature = 268.15f } },
  53. { "SnowfallHeavy", new WeatherType { PrototypeId = "SnowfallHeavy", Weight = 6, MinTemperature = 243.15f, MaxTemperature = 258.15f } },
  54. { "Hail", new WeatherType { PrototypeId = "Hail", Weight = 7, MinTemperature = 273.15f, MaxTemperature = 278.15f } },
  55. { "Sandstorm", new WeatherType { PrototypeId = "Sandstorm", Weight = 9, MinTemperature = 293.15f, MaxTemperature = 313.15f } },
  56. { "SandstormHeavy", new WeatherType { PrototypeId = "SandstormHeavy", Weight = 10, MinTemperature = 293.15f, MaxTemperature = 313.15f } },
  57. };
  58. /// <summary>
  59. /// Dictionary mapping (Biome, Season, Precipitation) to specific weather types.
  60. /// </summary>
  61. private static readonly Dictionary<(Biome, string, Precipitation), string> _weatherTransitionMap = new()
  62. {
  63. // Summer
  64. { (Biome.Tundra, "Summer", Precipitation.Dry), "Clear" },
  65. { (Biome.Tundra, "Summer", Precipitation.LightWet), "SnowfallLight" },
  66. { (Biome.Tundra, "Summer", Precipitation.HeavyWet), "SnowfallMedium" },
  67. { (Biome.Tundra, "Summer", Precipitation.Storm), "SnowfallHeavy" },
  68. { (Biome.Taiga, "Summer", Precipitation.Dry), "Clear" },
  69. { (Biome.Taiga, "Summer", Precipitation.LightWet), "Rain" },
  70. { (Biome.Taiga, "Summer", Precipitation.HeavyWet), "Rain" },
  71. { (Biome.Taiga, "Summer", Precipitation.Storm), "Hail" },
  72. { (Biome.Temperate, "Summer", Precipitation.Dry), "Clear" },
  73. { (Biome.Temperate, "Summer", Precipitation.LightWet), "Rain" },
  74. { (Biome.Temperate, "Summer", Precipitation.HeavyWet), "Rain" },
  75. { (Biome.Temperate, "Summer", Precipitation.Storm), "Storm" },
  76. { (Biome.Sea, "Summer", Precipitation.Dry), "Clear" },
  77. { (Biome.Sea, "Summer", Precipitation.LightWet), "Rain" },
  78. { (Biome.Sea, "Summer", Precipitation.HeavyWet), "Rain" },
  79. { (Biome.Sea, "Summer", Precipitation.Storm), "Storm" },
  80. { (Biome.SemiArid, "Summer", Precipitation.Dry), "Clear" },
  81. { (Biome.SemiArid, "Summer", Precipitation.LightWet), "Clear" },
  82. { (Biome.SemiArid, "Summer", Precipitation.HeavyWet), "Rain" },
  83. { (Biome.SemiArid, "Summer", Precipitation.Storm), "Rain" },
  84. { (Biome.Desert, "Summer", Precipitation.Dry), "Clear" },
  85. { (Biome.Desert, "Summer", Precipitation.LightWet), "Clear" },
  86. { (Biome.Desert, "Summer", Precipitation.HeavyWet), "Sandstorm" },
  87. { (Biome.Desert, "Summer", Precipitation.Storm), "SandstormHeavy" },
  88. { (Biome.Savanna, "Summer", Precipitation.Dry), "Clear" },
  89. { (Biome.Savanna, "Summer", Precipitation.LightWet), "Clear" },
  90. { (Biome.Savanna, "Summer", Precipitation.HeavyWet), "Rain" },
  91. { (Biome.Savanna, "Summer", Precipitation.Storm), "Storm" },
  92. { (Biome.Jungle, "Summer", Precipitation.Dry), "Clear" },
  93. { (Biome.Jungle, "Summer", Precipitation.LightWet), "Rain" },
  94. { (Biome.Jungle, "Summer", Precipitation.HeavyWet), "Storm" },
  95. { (Biome.Jungle, "Summer", Precipitation.Storm), "Storm" },
  96. // Spring
  97. { (Biome.Tundra, "Spring", Precipitation.Dry), "Clear" },
  98. { (Biome.Tundra, "Spring", Precipitation.LightWet), "SnowfallLight" },
  99. { (Biome.Tundra, "Spring", Precipitation.HeavyWet), "SnowfallMedium" },
  100. { (Biome.Tundra, "Spring", Precipitation.Storm), "SnowfallHeavy" },
  101. { (Biome.Taiga, "Spring", Precipitation.Dry), "Clear" },
  102. { (Biome.Taiga, "Spring", Precipitation.LightWet), "Rain" },
  103. { (Biome.Taiga, "Spring", Precipitation.HeavyWet), "SnowfallLight" },
  104. { (Biome.Taiga, "Spring", Precipitation.Storm), "SnowfallHeavy" },
  105. { (Biome.Temperate, "Spring", Precipitation.Dry), "Clear" },
  106. { (Biome.Temperate, "Spring", Precipitation.LightWet), "Rain" },
  107. { (Biome.Temperate, "Spring", Precipitation.HeavyWet), "Storm" },
  108. { (Biome.Temperate, "Spring", Precipitation.Storm), "SnowfallMedium" },
  109. { (Biome.Sea, "Spring", Precipitation.Dry), "Clear" },
  110. { (Biome.Sea, "Spring", Precipitation.LightWet), "Rain" },
  111. { (Biome.Sea, "Spring", Precipitation.HeavyWet), "Rain" },
  112. { (Biome.Sea, "Spring", Precipitation.Storm), "Storm" },
  113. { (Biome.SemiArid, "Spring", Precipitation.Dry), "Clear" },
  114. { (Biome.SemiArid, "Spring", Precipitation.LightWet), "Clear" },
  115. { (Biome.SemiArid, "Spring", Precipitation.HeavyWet), "Rain" },
  116. { (Biome.SemiArid, "Spring", Precipitation.Storm), "Rain" },
  117. { (Biome.Desert, "Spring", Precipitation.Dry), "Clear" },
  118. { (Biome.Desert, "Spring", Precipitation.LightWet), "Clear" },
  119. { (Biome.Desert, "Spring", Precipitation.HeavyWet), "Rain" },
  120. { (Biome.Desert, "Spring", Precipitation.Storm), "Sandstorm" },
  121. { (Biome.Savanna, "Spring", Precipitation.Dry), "Clear" },
  122. { (Biome.Savanna, "Spring", Precipitation.LightWet), "Clear" },
  123. { (Biome.Savanna, "Spring", Precipitation.HeavyWet), "Rain" },
  124. { (Biome.Savanna, "Spring", Precipitation.Storm), "Storm" },
  125. { (Biome.Jungle, "Spring", Precipitation.Dry), "Clear" },
  126. { (Biome.Jungle, "Spring", Precipitation.LightWet), "Rain" },
  127. { (Biome.Jungle, "Spring", Precipitation.HeavyWet), "Rain" },
  128. { (Biome.Jungle, "Spring", Precipitation.Storm), "Storm" },
  129. // Autumn
  130. { (Biome.Tundra, "Autumn", Precipitation.Dry), "Clear" },
  131. { (Biome.Tundra, "Autumn", Precipitation.LightWet), "SnowfallLight" },
  132. { (Biome.Tundra, "Autumn", Precipitation.HeavyWet), "SnowfallMedium" },
  133. { (Biome.Tundra, "Autumn", Precipitation.Storm), "SnowfallHeavy" },
  134. { (Biome.Taiga, "Autumn", Precipitation.Dry), "Clear" },
  135. { (Biome.Taiga, "Autumn", Precipitation.LightWet), "Rain" },
  136. { (Biome.Taiga, "Autumn", Precipitation.HeavyWet), "SnowfallLight" },
  137. { (Biome.Taiga, "Autumn", Precipitation.Storm), "SnowfallHeavy" },
  138. { (Biome.Temperate, "Autumn", Precipitation.Dry), "Clear" },
  139. { (Biome.Temperate, "Autumn", Precipitation.LightWet), "Rain" },
  140. { (Biome.Temperate, "Autumn", Precipitation.HeavyWet), "Storm" },
  141. { (Biome.Temperate, "Autumn", Precipitation.Storm), "SnowfallMedium" },
  142. { (Biome.Sea, "Autumn", Precipitation.Dry), "Clear" },
  143. { (Biome.Sea, "Autumn", Precipitation.LightWet), "Rain" },
  144. { (Biome.Sea, "Autumn", Precipitation.HeavyWet), "Rain" },
  145. { (Biome.Sea, "Autumn", Precipitation.Storm), "Storm" },
  146. { (Biome.SemiArid, "Autumn", Precipitation.Dry), "Clear" },
  147. { (Biome.SemiArid, "Autumn", Precipitation.LightWet), "Clear" },
  148. { (Biome.SemiArid, "Autumn", Precipitation.HeavyWet), "Rain" },
  149. { (Biome.SemiArid, "Autumn", Precipitation.Storm), "Rain" },
  150. { (Biome.Desert, "Autumn", Precipitation.Dry), "Clear" },
  151. { (Biome.Desert, "Autumn", Precipitation.LightWet), "Clear" },
  152. { (Biome.Desert, "Autumn", Precipitation.HeavyWet), "Rain" },
  153. { (Biome.Desert, "Autumn", Precipitation.Storm), "Sandstorm" },
  154. { (Biome.Savanna, "Autumn", Precipitation.Dry), "Clear" },
  155. { (Biome.Savanna, "Autumn", Precipitation.LightWet), "Clear" },
  156. { (Biome.Savanna, "Autumn", Precipitation.HeavyWet), "Rain" },
  157. { (Biome.Savanna, "Autumn", Precipitation.Storm), "Storm" },
  158. { (Biome.Jungle, "Autumn", Precipitation.Dry), "Clear" },
  159. { (Biome.Jungle, "Autumn", Precipitation.LightWet), "Rain" },
  160. { (Biome.Jungle, "Autumn", Precipitation.HeavyWet), "Rain" },
  161. { (Biome.Jungle, "Autumn", Precipitation.Storm), "Storm" },
  162. // Winter
  163. { (Biome.Tundra, "Winter", Precipitation.Dry), "Clear" },
  164. { (Biome.Tundra, "Winter", Precipitation.LightWet), "SnowfallMedium" },
  165. { (Biome.Tundra, "Winter", Precipitation.HeavyWet), "SnowfallHeavy" },
  166. { (Biome.Tundra, "Winter", Precipitation.Storm), "SnowfallHeavy" },
  167. { (Biome.Taiga, "Winter", Precipitation.Dry), "Clear" },
  168. { (Biome.Taiga, "Winter", Precipitation.LightWet), "SnowfallLight" },
  169. { (Biome.Taiga, "Winter", Precipitation.HeavyWet), "SnowfallHeavy" },
  170. { (Biome.Taiga, "Winter", Precipitation.Storm), "SnowfallHeavy" },
  171. { (Biome.Temperate, "Winter", Precipitation.Dry), "Clear" },
  172. { (Biome.Temperate, "Winter", Precipitation.LightWet), "SnowfallLight" },
  173. { (Biome.Temperate, "Winter", Precipitation.HeavyWet), "SnowfallMedium" },
  174. { (Biome.Temperate, "Winter", Precipitation.Storm), "SnowfallHeavy" },
  175. { (Biome.Sea, "Winter", Precipitation.Dry), "Clear" },
  176. { (Biome.Sea, "Winter", Precipitation.LightWet), "Rain" },
  177. { (Biome.Sea, "Winter", Precipitation.HeavyWet), "Storm" },
  178. { (Biome.Sea, "Winter", Precipitation.Storm), "Storm" },
  179. { (Biome.SemiArid, "Winter", Precipitation.Dry), "Clear" },
  180. { (Biome.SemiArid, "Winter", Precipitation.LightWet), "Rain" },
  181. { (Biome.SemiArid, "Winter", Precipitation.HeavyWet), "Rain" },
  182. { (Biome.SemiArid, "Winter", Precipitation.Storm), "Storm" },
  183. { (Biome.Desert, "Winter", Precipitation.Dry), "Clear" },
  184. { (Biome.Desert, "Winter", Precipitation.LightWet), "Clear" },
  185. { (Biome.Desert, "Winter", Precipitation.HeavyWet), "Rain" },
  186. { (Biome.Desert, "Winter", Precipitation.Storm), "Rain" },
  187. { (Biome.Savanna, "Winter", Precipitation.Dry), "Clear" },
  188. { (Biome.Savanna, "Winter", Precipitation.LightWet), "Rain" },
  189. { (Biome.Savanna, "Winter", Precipitation.HeavyWet), "Storm" },
  190. { (Biome.Savanna, "Winter", Precipitation.Storm), "Storm" },
  191. { (Biome.Jungle, "Winter", Precipitation.Dry), "Clear" },
  192. { (Biome.Jungle, "Winter", Precipitation.LightWet), "Rain" },
  193. { (Biome.Jungle, "Winter", Precipitation.HeavyWet), "Storm" },
  194. { (Biome.Jungle, "Winter", Precipitation.Storm), "Storm" },
  195. };
  196. /// <summary>
  197. /// Dictionary that maps each season to its transformation rules for tiles and entities.
  198. /// Each entry contains a tuple with two dictionaries:
  199. /// - Tile transformation dictionary: Maps original tile names to their transformed counterparts.
  200. /// - Entity transformation dictionary: Maps original entity prototype IDs to their transformed counterparts.
  201. /// Seasons without transformations have empty dictionaries.
  202. /// </summary>
  203. private readonly Dictionary<string, (Dictionary<string, string> TileTransformations, Dictionary<string, string> EntityTransformations)> _seasonBasedTransformationRules = new()
  204. {
  205. ["Winter"] = (
  206. TileTransformations: new Dictionary<string, string>
  207. {
  208. { "FloorPlanetGrass", "FloorSnowGrass" },
  209. { "FloorDirt", "FloorSnow" },
  210. { "FloorSand", "FloorSnowSand" },
  211. },
  212. EntityTransformations: new Dictionary<string, string>
  213. {
  214. { "TreeTemperate", "TreeTemperateSnow" } // Temperate trees get a snowy variant
  215. }
  216. ),
  217. ["Summer"] = (
  218. TileTransformations: new Dictionary<string, string>(),
  219. EntityTransformations: new Dictionary<string, string>()
  220. ),
  221. ["Spring"] = (
  222. TileTransformations: new Dictionary<string, string>
  223. {
  224. { "FloorSnowGrass", "FloorPlanetGrass" },
  225. { "FloorSnow", "FloorDirt" },
  226. { "FloorSnowSand", "FloorSand" },
  227. },
  228. EntityTransformations: new Dictionary<string, string>
  229. {
  230. { "TreeTemperateSnow", "TreeTemperate" }
  231. }
  232. ),
  233. ["Autumn"] = (
  234. TileTransformations: new Dictionary<string, string>(),
  235. EntityTransformations: new Dictionary<string, string>()
  236. )
  237. };
  238. /// <summary>
  239. /// Initializes the system and subscribes to relevant events.
  240. /// </summary>
  241. public override void Initialize()
  242. {
  243. base.Initialize();
  244. SubscribeLocalEvent<WeatherNomadsComponent, MapInitEvent>(OnMapInit);
  245. }
  246. /// <summary>
  247. /// Handles the initialization of weather for a map when it is first created.
  248. /// </summary>
  249. private void OnMapInit(EntityUid uid, WeatherNomadsComponent component, MapInitEvent args)
  250. {
  251. component.CurrentPrecipitation = Precipitation.Dry;
  252. component.CurrentWeather = "Clear";
  253. component.NextSwitchTime = _timing.CurTime + TimeSpan.FromMinutes(GetRandomPrecipitationDuration(component));
  254. component.NextSeasonChange = _timing.CurTime + TimeSpan.FromMinutes(GetRandomSeasonDuration(component));
  255. Dirty(uid, component);
  256. UpdateTileWeathers(uid, component);
  257. _chat.DispatchGlobalAnnouncement($"Current season: {component.CurrentSeason}", "World", false, null, null);
  258. }
  259. /// <summary>
  260. /// Updates the weather system periodically, switching precipitation and season states as needed.
  261. /// </summary>
  262. public override void Update(float frameTime)
  263. {
  264. base.Update(frameTime);
  265. var query = EntityQueryEnumerator<WeatherNomadsComponent>();
  266. while (query.MoveNext(out var uid, out var nomads))
  267. {
  268. // Handle season changes
  269. if (_timing.CurTime >= nomads.NextSeasonChange)
  270. {
  271. var oldSeason = nomads.CurrentSeason;
  272. nomads.CurrentSeason = GetNextSeason(nomads.CurrentSeason);
  273. nomads.NextSeasonChange = _timing.CurTime + TimeSpan.FromMinutes(GetRandomSeasonDuration(nomads));
  274. Dirty(uid, nomads);
  275. _chat.DispatchGlobalAnnouncement($"Changed season to {nomads.CurrentSeason}", null, false, null, null);
  276. Log.Debug($"Season changed from {oldSeason} to {nomads.CurrentSeason} for entity {uid}, triggering UpdateTileWeathers");
  277. UpdateTileWeathers(uid, nomads);
  278. }
  279. // Handle precipitation changes
  280. if (_timing.CurTime < nomads.NextSwitchTime)
  281. {
  282. continue;
  283. }
  284. var oldPrecipitation = nomads.CurrentPrecipitation;
  285. nomads.CurrentPrecipitation = GetNextPrecipitation(nomads.CurrentPrecipitation);
  286. nomads.NextSwitchTime = _timing.CurTime + TimeSpan.FromMinutes(GetRandomPrecipitationDuration(nomads));
  287. Dirty(uid, nomads);
  288. Log.Debug($"Precipitation changed from {oldPrecipitation} to {nomads.CurrentPrecipitation} for entity {uid}, triggering UpdateTileWeathers");
  289. UpdateTileWeathers(uid, nomads);
  290. }
  291. }
  292. /// <summary>
  293. /// Updates weather effects for each tile based on biome, season, and global precipitation.
  294. /// Also applies transformations to tiles and entities based on the current season if transformation rules are defined.
  295. /// </summary>
  296. private void UpdateTileWeathers(EntityUid uid, WeatherNomadsComponent nomads)
  297. {
  298. Log.Debug($"Starting UpdateTileWeathers for entity {uid}, season: {nomads.CurrentSeason}");
  299. var mapId = Transform(uid).MapID;
  300. var gridUid = GetGridUidForMap(mapId);
  301. if (gridUid == null)
  302. {
  303. Log.Warning($"No grid found for map {mapId}");
  304. return;
  305. }
  306. Log.Debug($"Grid found for map {mapId}: {gridUid}");
  307. if (!TryComp<MapGridComponent>(gridUid.Value, out var grid))
  308. {
  309. Log.Warning($"No MapGridComponent found for grid {gridUid}");
  310. return;
  311. }
  312. if (!TryComp<GridAtmosphereComponent>(gridUid.Value, out var gridAtmosphere))
  313. {
  314. Log.Warning($"No GridAtmosphereComponent found for grid {gridUid}");
  315. return;
  316. }
  317. RoofComponent? roofComp = TryComp<RoofComponent>(gridUid.Value, out var rc) ? rc : null;
  318. // Retrieve transformation rules for the current season, defaulting to empty dictionaries if not found
  319. if (!_seasonBasedTransformationRules.TryGetValue(nomads.CurrentSeason, out var transformationRules))
  320. {
  321. Log.Warning($"No transformation rules defined for season {nomads.CurrentSeason}");
  322. transformationRules = (TileTransformations: new Dictionary<string, string>(), EntityTransformations: new Dictionary<string, string>());
  323. }
  324. Log.Debug($"Transformation rules retrieved for season {nomads.CurrentSeason}: {transformationRules.TileTransformations.Count} tile rules, {transformationRules.EntityTransformations.Count} entity rules");
  325. var tileTransformationDictionary = transformationRules.TileTransformations;
  326. var entityTransformationDictionary = transformationRules.EntityTransformations;
  327. // Apply tile transformations only if there are rules defined
  328. if (tileTransformationDictionary.Count > 0)
  329. {
  330. Log.Debug($"Processing {gridAtmosphere.Tiles.Count} tiles for transformation in season {nomads.CurrentSeason}");
  331. foreach (var tile in gridAtmosphere.Tiles.Values)
  332. {
  333. var tileRef = grid.GetTileRef(tile.GridIndices);
  334. if (tileRef.Tile.IsEmpty)
  335. {
  336. continue; // Skip empty tiles
  337. }
  338. var tileDef = (ContentTileDefinition)_tileDefManager[tileRef.Tile.TypeId];
  339. if (tileTransformationDictionary.TryGetValue(tileDef.ID, out var transformedTileName))
  340. {
  341. if (tileDef.ID == transformedTileName)
  342. {
  343. continue;
  344. }
  345. var newTileDefinition = _tileDefManager[transformedTileName];
  346. var newTile = new Tile(newTileDefinition.TileId);
  347. grid.SetTile(tileRef.GridIndices, newTile);
  348. Log.Debug($"Transformed tile at {tileRef.GridIndices} from {tileDef.ID} to {transformedTileName} for season {nomads.CurrentSeason}");
  349. }
  350. else
  351. {
  352. Log.Debug($"No transformation rule found for tile {tileDef.ID} at {tileRef.GridIndices}");
  353. }
  354. // Get biome from tile definition
  355. if (!Enum.TryParse<Biome>(tileDef.Biome, true, out var biome))
  356. {
  357. biome = Biome.Temperate; // Fallback to Temperate if biome string is invalid
  358. Log.Warning($"Invalid biome '{tileDef.Biome}' for tile at {tileRef.GridIndices}, defaulting to Temperate");
  359. }
  360. if (_weatherTransitionMap.TryGetValue((biome, nomads.CurrentSeason, nomads.CurrentPrecipitation), out var weatherType))
  361. {
  362. ApplyWeatherToTile(uid, nomads, gridUid.Value, tileRef, weatherType, grid, gridAtmosphere, roofComp);
  363. }
  364. else
  365. {
  366. Log.Warning($"No weather mapping found for Biome: {biome}, Season: {nomads.CurrentSeason}, Precipitation: {nomads.CurrentPrecipitation}");
  367. }
  368. }
  369. }
  370. else
  371. {
  372. Log.Debug($"No tile transformations defined for season {nomads.CurrentSeason}, processing {gridAtmosphere.Tiles.Count} tiles for weather effects only");
  373. // If no tile transformations, just apply weather effects
  374. foreach (var tile in gridAtmosphere.Tiles.Values)
  375. {
  376. var tileRef = grid.GetTileRef(tile.GridIndices);
  377. if (tileRef.Tile.IsEmpty)
  378. {
  379. Log.Debug($"Skipping empty tile at {tileRef.GridIndices}");
  380. continue; // Skip empty tiles
  381. }
  382. // Get biome from tile definition
  383. var tileDef = (ContentTileDefinition)_tileDefManager[tileRef.Tile.TypeId];
  384. if (!Enum.TryParse<Biome>(tileDef.Biome, true, out var biome))
  385. {
  386. biome = Biome.Temperate; // Fallback to Temperate if biome string is invalid
  387. Log.Warning($"Invalid biome '{tileDef.Biome}' for tile at {tileRef.GridIndices}, defaulting to Temperate");
  388. }
  389. if (_weatherTransitionMap.TryGetValue((biome, nomads.CurrentSeason, nomads.CurrentPrecipitation), out var weatherType))
  390. {
  391. ApplyWeatherToTile(uid, nomads, gridUid.Value, tileRef, weatherType, grid, gridAtmosphere, roofComp);
  392. }
  393. else
  394. {
  395. Log.Warning($"No weather mapping found for Biome: {biome}, Season: {nomads.CurrentSeason}, Precipitation: {nomads.CurrentPrecipitation}");
  396. }
  397. }
  398. }
  399. // Apply entity transformations only if there are rules defined
  400. if (entityTransformationDictionary.Count > 0)
  401. {
  402. Log.Debug($"Calling TransformEntitiesOnGrid for {entityTransformationDictionary.Count} entity transformation rules in season {nomads.CurrentSeason}");
  403. TransformEntitiesOnGrid(gridUid.Value, nomads, entityTransformationDictionary);
  404. }
  405. else
  406. {
  407. Log.Debug($"No entity transformations defined for season {nomads.CurrentSeason}, skipping TransformEntitiesOnGrid");
  408. }
  409. }
  410. /// <summary>
  411. /// Applies weather effects and temperature to a specific tile.
  412. /// </summary>
  413. private void ApplyWeatherToTile(EntityUid weatherUid, WeatherNomadsComponent nomads, EntityUid gridUid, TileRef tileRef, string weatherType, MapGridComponent grid,
  414. GridAtmosphereComponent gridAtmosphere, RoofComponent? roofComp)
  415. {
  416. if (!CanWeatherAffect(gridUid, grid, tileRef, roofComp))
  417. return;
  418. var tile = gridAtmosphere.Tiles[tileRef.GridIndices];
  419. if (tile.Air == null)
  420. return;
  421. if (!_weatherTypes.TryGetValue(weatherType, out var weatherData))
  422. {
  423. Log.Warning($"Weather type {weatherType} not found in _weatherTypes");
  424. return;
  425. }
  426. // Update CurrentWeather if it has changed
  427. if (nomads.CurrentWeather != weatherType)
  428. {
  429. nomads.CurrentWeather = weatherType;
  430. Dirty(weatherUid, nomads);
  431. }
  432. // Apply weather visuals globally
  433. var mapId = Transform(gridUid).MapID;
  434. if (!string.IsNullOrEmpty(weatherData.PrototypeId) &&
  435. _prototypeManager.TryIndex<WeatherPrototype>(weatherData.PrototypeId, out var proto))
  436. {
  437. _weatherSystem.SetWeather(mapId, proto, null);
  438. }
  439. else
  440. {
  441. _weatherSystem.SetWeather(mapId, null, null);
  442. }
  443. // Adjust temperature
  444. var temperature = (float)(weatherData.MinTemperature +
  445. (weatherData.MaxTemperature - weatherData.MinTemperature) * Random.Shared.NextDouble());
  446. var air = tile.Air;
  447. if (air.Immutable)
  448. {
  449. var newAir = new GasMixture();
  450. newAir.CopyFrom(air);
  451. air = newAir;
  452. }
  453. air.Temperature = temperature;
  454. }
  455. /// <summary>
  456. /// Transforms entities on the grid based on the transformation rules defined for the current season.
  457. /// </summary>
  458. private void TransformEntitiesOnGrid(EntityUid gridUid, WeatherNomadsComponent nomads, Dictionary<string, string> entityTransformationDictionary)
  459. {
  460. if (TryComp<MapGridComponent>(gridUid, out var gridComp))
  461. {
  462. Log.Debug($"TransformEntitiesOnGrid: MapGridComponent found for grid {gridUid}");
  463. var anchoredEntities = EntityQuery<TransformComponent>()
  464. .Where(t => t.GridUid == gridUid && t.Anchored)
  465. .Select(t => t.Owner)
  466. .ToList();
  467. Log.Debug($"Found {anchoredEntities.Count} anchored entities on grid {gridUid}");
  468. foreach (var entity in anchoredEntities)
  469. {
  470. if (!TryComp<MetaDataComponent>(entity, out var metaData))
  471. {
  472. Log.Debug($"Entity {entity} has no MetaDataComponent");
  473. continue;
  474. }
  475. if (metaData.EntityPrototype == null)
  476. {
  477. Log.Debug($"Entity {entity} has no EntityPrototype");
  478. continue;
  479. }
  480. if (!entityTransformationDictionary.TryGetValue(metaData.EntityPrototype.ID, out var transformedEntityPrototypeId))
  481. {
  482. Log.Debug($"No transformation rule for entity {entity} with prototype ID {metaData.EntityPrototype.ID}");
  483. continue;
  484. }
  485. if (metaData.EntityPrototype.ID == transformedEntityPrototypeId)
  486. {
  487. continue;
  488. }
  489. var transformComponent = Transform(entity);
  490. var entityCoordinates = transformComponent.Coordinates;
  491. QueueDel(entity);
  492. var newEntity = Spawn(transformedEntityPrototypeId, entityCoordinates);
  493. Log.Debug($"Transformed entity {entity} to {newEntity} at {entityCoordinates} for season {nomads.CurrentSeason}");
  494. }
  495. }
  496. else
  497. {
  498. Log.Warning($"Could not get MapGridComponent for grid {gridUid}");
  499. }
  500. }
  501. /// <summary>
  502. /// Gets the next precipitation state in the cycle.
  503. /// </summary>
  504. private Precipitation GetNextPrecipitation(Precipitation current)
  505. {
  506. return current switch
  507. {
  508. Precipitation.Dry => Precipitation.LightWet,
  509. Precipitation.LightWet => Precipitation.HeavyWet,
  510. Precipitation.HeavyWet => Precipitation.Storm,
  511. Precipitation.Storm => Precipitation.Dry,
  512. _ => Precipitation.Dry // Default to Dry if something goes wrong
  513. };
  514. }
  515. /// <summary>
  516. /// Generates a random duration for a season based on component settings.
  517. /// </summary>
  518. private double GetRandomSeasonDuration(WeatherNomadsComponent component)
  519. {
  520. var duration = Random.Shared.Next(component.MinSeasonMinutes, component.MaxSeasonMinutes + 1);
  521. return duration;
  522. }
  523. /// <summary>
  524. /// Generates a random duration for a precipitation change based on component settings.
  525. /// </summary>
  526. private double GetRandomPrecipitationDuration(WeatherNomadsComponent component)
  527. {
  528. var duration = Random.Shared.Next(component.MinPrecipitationDurationMinutes, component.MaxPrecipitationDurationMinutes + 1);
  529. return duration;
  530. }
  531. /// <summary>
  532. /// Determines if weather can affect a specific tile, based on roof coverage, tile type, and blocking entities.
  533. /// </summary>
  534. private bool CanWeatherAffect(EntityUid gridUid, MapGridComponent grid, TileRef tileRef, RoofComponent? roofComp)
  535. {
  536. if (tileRef.Tile.IsEmpty)
  537. return true;
  538. if (roofComp != null && _roofSystem.IsRooved((gridUid, grid, roofComp), tileRef.GridIndices))
  539. return false;
  540. var tileDef = (ContentTileDefinition)_tileDefManager[tileRef.Tile.TypeId];
  541. if (!tileDef.Weather)
  542. return false;
  543. var anchoredEntities = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, tileRef.GridIndices);
  544. while (anchoredEntities.MoveNext(out var ent))
  545. {
  546. if (HasComp<BlockWeatherComponent>(ent.Value))
  547. return false;
  548. }
  549. return true;
  550. }
  551. /// <summary>
  552. /// Retrieves the EntityUid of the grid associated with a given map ID.
  553. /// Assumes one grid per map for simplicity.
  554. /// </summary>
  555. private EntityUid? GetGridUidForMap(MapId mapId)
  556. {
  557. var grids = _mapManager.GetAllMapGrids(mapId);
  558. return grids.Any() ? grids.First().Owner : null;
  559. }
  560. /// <summary>
  561. /// Gets the next season in the cycle.
  562. /// </summary>
  563. private string GetNextSeason(string current)
  564. {
  565. return current switch
  566. {
  567. "Spring" => "Summer",
  568. "Summer" => "Autumn",
  569. "Autumn" => "Winter",
  570. "Winter" => "Spring",
  571. _ => "Spring" // Default to Spring if something goes wrong
  572. };
  573. }
  574. }