SpawnSalvageMissionJob.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. using System.Collections;
  2. using System.Linq;
  3. using System.Numerics;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Content.Server.Atmos;
  7. using Content.Server.Atmos.Components;
  8. using Content.Server.Atmos.EntitySystems;
  9. using Robust.Shared.CPUJob.JobQueues;
  10. using Content.Server.Ghost.Roles.Components;
  11. using Content.Server.Parallax;
  12. using Content.Server.Procedural;
  13. using Content.Server.Salvage.Expeditions;
  14. using Content.Server.Salvage.Expeditions.Structure;
  15. using Content.Shared.Atmos;
  16. using Content.Shared.Construction.EntitySystems;
  17. using Content.Shared.Dataset;
  18. using Content.Shared.Gravity;
  19. using Content.Shared.Parallax.Biomes;
  20. using Content.Shared.Physics;
  21. using Content.Shared.Procedural;
  22. using Content.Shared.Procedural.Loot;
  23. using Content.Shared.Random;
  24. using Content.Shared.Salvage;
  25. using Content.Shared.Salvage.Expeditions;
  26. using Content.Shared.Salvage.Expeditions.Modifiers;
  27. using Content.Shared.Shuttles.Components;
  28. using Content.Shared.Storage;
  29. using Robust.Shared.Collections;
  30. using Robust.Shared.Map;
  31. using Robust.Shared.Map.Components;
  32. using Robust.Shared.Prototypes;
  33. using Robust.Shared.Random;
  34. using Robust.Shared.Timing;
  35. using Robust.Shared.Utility;
  36. using Content.Server.Shuttles.Components;
  37. namespace Content.Server.Salvage;
  38. public sealed class SpawnSalvageMissionJob : Job<bool>
  39. {
  40. private readonly IEntityManager _entManager;
  41. private readonly IGameTiming _timing;
  42. private readonly IMapManager _mapManager;
  43. private readonly IPrototypeManager _prototypeManager;
  44. private readonly AnchorableSystem _anchorable;
  45. private readonly BiomeSystem _biome;
  46. private readonly DungeonSystem _dungeon;
  47. private readonly MetaDataSystem _metaData;
  48. private readonly SharedTransformSystem _xforms;
  49. private readonly SharedMapSystem _map;
  50. public readonly EntityUid Station;
  51. public readonly EntityUid? CoordinatesDisk;
  52. private readonly SalvageMissionParams _missionParams;
  53. private readonly ISawmill _sawmill;
  54. public SpawnSalvageMissionJob(
  55. double maxTime,
  56. IEntityManager entManager,
  57. IGameTiming timing,
  58. ILogManager logManager,
  59. IMapManager mapManager,
  60. IPrototypeManager protoManager,
  61. AnchorableSystem anchorable,
  62. BiomeSystem biome,
  63. DungeonSystem dungeon,
  64. MetaDataSystem metaData,
  65. SharedTransformSystem xform,
  66. SharedMapSystem map,
  67. EntityUid station,
  68. EntityUid? coordinatesDisk,
  69. SalvageMissionParams missionParams,
  70. CancellationToken cancellation = default) : base(maxTime, cancellation)
  71. {
  72. _entManager = entManager;
  73. _timing = timing;
  74. _mapManager = mapManager;
  75. _prototypeManager = protoManager;
  76. _anchorable = anchorable;
  77. _biome = biome;
  78. _dungeon = dungeon;
  79. _metaData = metaData;
  80. _xforms = xform;
  81. _map = map;
  82. Station = station;
  83. CoordinatesDisk = coordinatesDisk;
  84. _missionParams = missionParams;
  85. _sawmill = logManager.GetSawmill("salvage_job");
  86. #if !DEBUG
  87. _sawmill.Level = LogLevel.Info;
  88. #endif
  89. }
  90. protected override async Task<bool> Process()
  91. {
  92. _sawmill.Debug("salvage", $"Spawning salvage mission with seed {_missionParams.Seed}");
  93. var mapUid = _map.CreateMap(out var mapId, runMapInit: false);
  94. MetaDataComponent? metadata = null;
  95. var grid = _entManager.EnsureComponent<MapGridComponent>(mapUid);
  96. var random = new Random(_missionParams.Seed);
  97. var destComp = _entManager.AddComponent<FTLDestinationComponent>(mapUid);
  98. destComp.BeaconsOnly = true;
  99. destComp.RequireCoordinateDisk = true;
  100. destComp.Enabled = true;
  101. _metaData.SetEntityName(
  102. mapUid,
  103. _entManager.System<SharedSalvageSystem>().GetFTLName(_prototypeManager.Index<LocalizedDatasetPrototype>("NamesBorer"), _missionParams.Seed));
  104. _entManager.AddComponent<FTLBeaconComponent>(mapUid);
  105. // Saving the mission mapUid to a CD is made optional, in case one is somehow made in a process without a CD entity
  106. if (CoordinatesDisk.HasValue)
  107. {
  108. var cd = _entManager.EnsureComponent<ShuttleDestinationCoordinatesComponent>(CoordinatesDisk.Value);
  109. cd.Destination = mapUid;
  110. _entManager.Dirty(CoordinatesDisk.Value, cd);
  111. }
  112. // Setup mission configs
  113. // As we go through the config the rating will deplete so we'll go for most important to least important.
  114. var difficultyId = "Moderate";
  115. var difficultyProto = _prototypeManager.Index<SalvageDifficultyPrototype>(difficultyId);
  116. var mission = _entManager.System<SharedSalvageSystem>()
  117. .GetMission(difficultyProto, _missionParams.Seed);
  118. var missionBiome = _prototypeManager.Index<SalvageBiomeModPrototype>(mission.Biome);
  119. if (missionBiome.BiomePrototype != null)
  120. {
  121. var biome = _entManager.AddComponent<BiomeComponent>(mapUid);
  122. var biomeSystem = _entManager.System<BiomeSystem>();
  123. biomeSystem.SetTemplate(mapUid, biome, _prototypeManager.Index<BiomeTemplatePrototype>(missionBiome.BiomePrototype));
  124. biomeSystem.SetSeed(mapUid, biome, mission.Seed);
  125. _entManager.Dirty(mapUid, biome);
  126. // Gravity
  127. var gravity = _entManager.EnsureComponent<GravityComponent>(mapUid);
  128. gravity.Enabled = true;
  129. _entManager.Dirty(mapUid, gravity, metadata);
  130. // Atmos
  131. var air = _prototypeManager.Index<SalvageAirMod>(mission.Air);
  132. // copy into a new array since the yml deserialization discards the fixed length
  133. var moles = new float[Atmospherics.AdjustedNumberOfGases];
  134. air.Gases.CopyTo(moles, 0);
  135. var atmos = _entManager.EnsureComponent<MapAtmosphereComponent>(mapUid);
  136. _entManager.System<AtmosphereSystem>().SetMapSpace(mapUid, air.Space, atmos);
  137. _entManager.System<AtmosphereSystem>().SetMapGasMixture(mapUid, new GasMixture(moles, mission.Temperature), atmos);
  138. if (mission.Color != null)
  139. {
  140. var lighting = _entManager.EnsureComponent<MapLightComponent>(mapUid);
  141. lighting.AmbientLightColor = mission.Color.Value;
  142. _entManager.Dirty(mapUid, lighting);
  143. }
  144. }
  145. _mapManager.DoMapInitialize(mapId);
  146. _mapManager.SetMapPaused(mapId, true);
  147. // Setup expedition
  148. var expedition = _entManager.AddComponent<SalvageExpeditionComponent>(mapUid);
  149. expedition.Station = Station;
  150. expedition.EndTime = _timing.CurTime + mission.Duration;
  151. expedition.MissionParams = _missionParams;
  152. var landingPadRadius = 24;
  153. var minDungeonOffset = landingPadRadius + 4;
  154. // We'll use the dungeon rotation as the spawn angle
  155. var dungeonRotation = _dungeon.GetDungeonRotation(_missionParams.Seed);
  156. var maxDungeonOffset = minDungeonOffset + 12;
  157. var dungeonOffsetDistance = minDungeonOffset + (maxDungeonOffset - minDungeonOffset) * random.NextFloat();
  158. var dungeonOffset = new Vector2(0f, dungeonOffsetDistance);
  159. dungeonOffset = dungeonRotation.RotateVec(dungeonOffset);
  160. var dungeonMod = _prototypeManager.Index<SalvageDungeonModPrototype>(mission.Dungeon);
  161. var dungeonConfig = _prototypeManager.Index(dungeonMod.Proto);
  162. var dungeons = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i) dungeonOffset,
  163. _missionParams.Seed));
  164. var dungeon = dungeons.First();
  165. // Aborty
  166. if (dungeon.Rooms.Count == 0)
  167. {
  168. return false;
  169. }
  170. expedition.DungeonLocation = dungeonOffset;
  171. List<Vector2i> reservedTiles = new();
  172. foreach (var tile in _map.GetTilesIntersecting(mapUid, grid, new Circle(Vector2.Zero, landingPadRadius), false))
  173. {
  174. if (!_biome.TryGetBiomeTile(mapUid, grid, tile.GridIndices, out _))
  175. continue;
  176. reservedTiles.Add(tile.GridIndices);
  177. }
  178. var budgetEntries = new List<IBudgetEntry>();
  179. /*
  180. * GUARANTEED LOOT
  181. */
  182. // We'll always add this loot if possible
  183. // mainly used for ore layers.
  184. foreach (var lootProto in _prototypeManager.EnumeratePrototypes<SalvageLootPrototype>())
  185. {
  186. if (!lootProto.Guaranteed)
  187. continue;
  188. await SpawnDungeonLoot(lootProto, mapUid);
  189. }
  190. // Handle boss loot (when relevant).
  191. // Handle mob loot.
  192. // Handle remaining loot
  193. /*
  194. * MOB SPAWNS
  195. */
  196. var mobBudget = difficultyProto.MobBudget;
  197. var faction = _prototypeManager.Index<SalvageFactionPrototype>(mission.Faction);
  198. var randomSystem = _entManager.System<RandomSystem>();
  199. foreach (var entry in faction.MobGroups)
  200. {
  201. budgetEntries.Add(entry);
  202. }
  203. var probSum = budgetEntries.Sum(x => x.Prob);
  204. while (mobBudget > 0f)
  205. {
  206. var entry = randomSystem.GetBudgetEntry(ref mobBudget, ref probSum, budgetEntries, random);
  207. if (entry == null)
  208. break;
  209. await SpawnRandomEntry(grid, entry, dungeon, random);
  210. }
  211. var allLoot = _prototypeManager.Index<SalvageLootPrototype>(SharedSalvageSystem.ExpeditionsLootProto);
  212. var lootBudget = difficultyProto.LootBudget;
  213. foreach (var rule in allLoot.LootRules)
  214. {
  215. switch (rule)
  216. {
  217. case RandomSpawnsLoot randomLoot:
  218. budgetEntries.Clear();
  219. foreach (var entry in randomLoot.Entries)
  220. {
  221. budgetEntries.Add(entry);
  222. }
  223. probSum = budgetEntries.Sum(x => x.Prob);
  224. while (lootBudget > 0f)
  225. {
  226. var entry = randomSystem.GetBudgetEntry(ref lootBudget, ref probSum, budgetEntries, random);
  227. if (entry == null)
  228. break;
  229. _sawmill.Debug($"Spawning dungeon loot {entry.Proto}");
  230. await SpawnRandomEntry(grid, entry, dungeon, random);
  231. }
  232. break;
  233. default:
  234. throw new NotImplementedException();
  235. }
  236. }
  237. return true;
  238. }
  239. private async Task SpawnRandomEntry(MapGridComponent grid, IBudgetEntry entry, Dungeon dungeon, Random random)
  240. {
  241. await SuspendIfOutOfTime();
  242. var availableRooms = new ValueList<DungeonRoom>(dungeon.Rooms);
  243. var availableTiles = new List<Vector2i>();
  244. while (availableRooms.Count > 0)
  245. {
  246. availableTiles.Clear();
  247. var roomIndex = random.Next(availableRooms.Count);
  248. var room = availableRooms.RemoveSwap(roomIndex);
  249. availableTiles.AddRange(room.Tiles);
  250. while (availableTiles.Count > 0)
  251. {
  252. var tile = availableTiles.RemoveSwap(random.Next(availableTiles.Count));
  253. if (!_anchorable.TileFree(grid, tile, (int) CollisionGroup.MachineLayer,
  254. (int) CollisionGroup.MachineLayer))
  255. {
  256. continue;
  257. }
  258. var uid = _entManager.SpawnAtPosition(entry.Proto, grid.GridTileToLocal(tile));
  259. _entManager.RemoveComponent<GhostRoleComponent>(uid);
  260. _entManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
  261. return;
  262. }
  263. }
  264. // oh noooooooooooo
  265. }
  266. private async Task SpawnDungeonLoot(SalvageLootPrototype loot, EntityUid gridUid)
  267. {
  268. for (var i = 0; i < loot.LootRules.Count; i++)
  269. {
  270. var rule = loot.LootRules[i];
  271. switch (rule)
  272. {
  273. case BiomeMarkerLoot biomeLoot:
  274. {
  275. if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
  276. {
  277. _biome.AddMarkerLayer(gridUid, biome, biomeLoot.Prototype);
  278. }
  279. }
  280. break;
  281. case BiomeTemplateLoot biomeLoot:
  282. {
  283. if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
  284. {
  285. _biome.AddTemplate(gridUid, biome, "Loot", _prototypeManager.Index<BiomeTemplatePrototype>(biomeLoot.Prototype), i);
  286. }
  287. }
  288. break;
  289. }
  290. }
  291. }
  292. }