cfgplayerspawnhandler.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. class PlayerSpawnHandler
  2. {
  3. private static bool m_Initialized;
  4. static ref PlayerSpawnJsonData m_Data = new PlayerSpawnJsonData();
  5. static bool LoadData()
  6. {
  7. array<string> spawnGearPresetFiles = CfgGameplayHandler.GetPlayerSpawnGearPresetFiles();
  8. if (!spawnGearPresetFiles || (spawnGearPresetFiles && spawnGearPresetFiles.Count() == 0))
  9. return false;
  10. m_Data.presets = {};
  11. foreach (string spawnPresetFile : spawnGearPresetFiles)
  12. {
  13. PlayerSpawnPreset preset;
  14. string path = "$mission:" + spawnPresetFile;
  15. string errorMessage;
  16. if (!JsonFileLoader<PlayerSpawnPreset>.LoadFile(path, preset, errorMessage))
  17. {
  18. ErrorEx(errorMessage);
  19. return false;
  20. }
  21. if (preset != null)
  22. m_Data.presets.Insert(preset);
  23. }
  24. m_Initialized = m_Data.presets.Count() > 0;
  25. return true;
  26. }
  27. static bool IsInitialized()
  28. {
  29. return m_Initialized;
  30. }
  31. static PlayerSpawnPreset GetRandomCharacterPreset()
  32. {
  33. array<int> weightedPresetIndexes = new array<int>();
  34. int count = m_Data.presets.Count();
  35. PlayerSpawnPreset p;
  36. for (int i = 0; i < count; i++)
  37. {
  38. p = m_Data.presets[i];
  39. if (p.IsValid())
  40. {
  41. for (int j = 0; j < p.spawnWeight; j++)
  42. {
  43. weightedPresetIndexes.Insert(i);
  44. }
  45. }
  46. }
  47. return m_Data.presets.Get(weightedPresetIndexes.GetRandomElement());
  48. }
  49. static PlayerSpawnPreset GetCharacterPresetByName(string presetName)
  50. {
  51. foreach (PlayerSpawnPreset preset : m_Data.presets)
  52. {
  53. if (preset.IsValid() && preset.name == presetName)
  54. return preset;
  55. }
  56. return null;
  57. }
  58. //! equips character with the chosen preset
  59. static bool ProcessEquipmentData(PlayerBase player, PlayerSpawnPreset data)
  60. {
  61. if (data.IsValid())
  62. {
  63. ProcessSlotsEquipment(player, data);
  64. ProcessCargoEquipment(player, data);
  65. }
  66. return true;
  67. }
  68. //! iterates over each object and spawns alternatives
  69. private static void ProcessSlotsEquipment(PlayerBase player, PlayerSpawnPreset data)
  70. {
  71. if (!data.HasAttachmentSlotSetsDefined())
  72. {
  73. Debug.Log("No non-empty 'attachmentSlotItemSets' array found. Skipping slot spawns","n/a","n/a","ProcessSlotsEquipment");
  74. return;
  75. }
  76. foreach (PlayerSpawnPresetSlotData slotData : data.attachmentSlotItemSets)
  77. {
  78. SelectAndSpawnSlotEquipment(player,slotData);
  79. }
  80. }
  81. //! selects weighted slot equipment variant
  82. private static bool SelectAndSpawnSlotEquipment(PlayerBase player, PlayerSpawnPresetSlotData slotData)
  83. {
  84. int slotID;
  85. if (!slotData.TranslateAndValidateSlot(player,slotID))
  86. return false;
  87. if (!slotData.IsValid())
  88. return false;
  89. array<int> weightedDiscreteSetIndexes = new array<int>();
  90. int count = slotData.discreteItemSets.Count();
  91. PlayerSpawnPresetDiscreteItemSetSlotData dis;
  92. for (int i = 0; i < count; i++)
  93. {
  94. dis = slotData.discreteItemSets[i];
  95. if (dis.IsValid()) //only when the type exists and spawnWeight is set
  96. {
  97. for (int j = 0; j < dis.spawnWeight; j++)
  98. {
  99. weightedDiscreteSetIndexes.Insert(i);
  100. }
  101. }
  102. }
  103. dis = null;
  104. if (weightedDiscreteSetIndexes.Count() > 0)
  105. dis = slotData.discreteItemSets.Get(weightedDiscreteSetIndexes.GetRandomElement());
  106. return SpawnDiscreteSlotItemSet(player,dis,slotID);
  107. }
  108. //! chooses one object from the array
  109. private static void ProcessCargoEquipment(PlayerBase player, PlayerSpawnPreset data)
  110. {
  111. if (!data.HasDiscreteUnsortedItemSetsDefined())
  112. {
  113. Debug.Log("No non-empty 'discreteUnsortedItemSets' array found. Skipping cargo spawns","n/a","n/a","ProcessCargoEquipment");
  114. return;
  115. }
  116. SelectAndSpawnCargoSet(player,data);
  117. }
  118. private static bool SelectAndSpawnCargoSet(PlayerBase player, PlayerSpawnPreset data)
  119. {
  120. array<int> weightedDiscreteSetIndexes = new array<int>();
  121. int count = data.discreteUnsortedItemSets.Count();
  122. PlayerSpawnPresetDiscreteCargoSetData csd;
  123. for (int i = 0; i < count; i++)
  124. {
  125. csd = data.discreteUnsortedItemSets[i];
  126. if (csd.IsValid()) //only when the spawnWeight is set
  127. {
  128. for (int j = 0; j < csd.spawnWeight; j++)
  129. {
  130. weightedDiscreteSetIndexes.Insert(i);
  131. }
  132. }
  133. }
  134. csd = null;
  135. if (weightedDiscreteSetIndexes.Count() > 0)
  136. csd = data.discreteUnsortedItemSets.Get(weightedDiscreteSetIndexes.GetRandomElement());
  137. return SpawnDiscreteCargoItemSet(player,csd);
  138. }
  139. private static bool SpawnDiscreteCargoItemSet(PlayerBase player, PlayerSpawnPresetDiscreteCargoSetData csd)
  140. {
  141. SpawnComplexChildrenItems(player,csd);
  142. SpawnSimpleChildrenItems(player,csd);
  143. return true;
  144. }
  145. private static bool SpawnDiscreteSlotItemSet(PlayerBase player, PlayerSpawnPresetDiscreteItemSetSlotData dis, int slotID)
  146. {
  147. if (!dis)
  148. {
  149. Debug.Log("No PlayerSpawnPresetDiscreteItemSet found. Skipping spawn for slot: " + InventorySlots.GetSlotName(slotID),"n/a","n/a","SpawnDiscreteSlotItemSet");
  150. return false;
  151. }
  152. ItemBase item;
  153. if (slotID == InventorySlots.HANDS) //hands exception
  154. item = ItemBase.Cast(player.GetHumanInventory().CreateInHands(dis.itemType));
  155. else
  156. item = ItemBase.Cast(player.GetInventory().CreateAttachmentEx(dis.itemType,slotID));
  157. if (item)
  158. {
  159. HandleNewItem(item,dis);
  160. }
  161. else if (dis.itemType != string.Empty)
  162. {
  163. Debug.Log("FAILED spawning item type: " + dis.itemType + " into slot: " + InventorySlots.GetSlotName(slotID) + " of parent: " + player,"n/a","n/a","SpawnDiscreteSlotItemSet");
  164. return false;
  165. }
  166. return item != null;
  167. }
  168. //! could spawn other items recursively. Parent item is guaranteed here.
  169. private static bool SpawnComplexChildrenItems(EntityAI parent, notnull PlayerSpawnPresetItemSetBase data)
  170. {
  171. if (!data.complexChildrenTypes || data.complexChildrenTypes.Count() < 1) //no children defined, still valid!
  172. {
  173. return false;
  174. }
  175. foreach (PlayerSpawnPresetComplexChildrenType cct : data.complexChildrenTypes)
  176. {
  177. if (cct.itemType == string.Empty)
  178. {
  179. Debug.Log("Empty item type found in 'complexChildrenTypes' of parent : " + parent,"n/a","n/a","SpawnSimpleChildrenItems");
  180. continue;
  181. }
  182. ItemBase item;
  183. Class.CastTo(item,CreateChildItem(parent,cct.itemType));
  184. if (item)
  185. {
  186. HandleNewItem(item,cct);
  187. }
  188. else
  189. {
  190. Weapon_Base wep;
  191. if (!Class.CastTo(wep,parent) || !IsWeaponAndMagazineType(parent,cct.itemType) || !wep.HasInternalMagazine(-1))
  192. Debug.Log("FAILED spawning item: " + cct.itemType + " of parent: " + parent,"n/a","n/a","SpawnComplexChildrenItems");
  193. }
  194. }
  195. return true;
  196. }
  197. private static bool SpawnSimpleChildrenItems(EntityAI parent, PlayerSpawnPresetItemSetBase data)
  198. {
  199. if (!data || !data.simpleChildrenTypes || data.simpleChildrenTypes.Count() < 1) //no children defined, still valid!
  200. {
  201. return false;
  202. }
  203. int count = data.simpleChildrenTypes.Count();
  204. string itemType;
  205. for (int i = 0; i < count; i++)
  206. {
  207. itemType = data.simpleChildrenTypes[i];
  208. if (itemType == string.Empty)
  209. {
  210. Debug.Log("Empty item type found at idx: " + i.ToString() + " of 'simpleChildrenTypes' array. Skipping","n/a","n/a","SpawnSimpleChildrenItems");
  211. continue;
  212. }
  213. ItemBase item;
  214. Class.CastTo(item,CreateChildItem(parent,itemType));
  215. if (item)
  216. {
  217. if (!data.simpleChildrenUseDefaultAttributes)
  218. ApplyAttributes(item,data.attributes);
  219. }
  220. else
  221. {
  222. Weapon_Base wep;
  223. if (!Class.CastTo(wep,parent) || !IsWeaponAndMagazineType(parent,itemType) || !wep.HasInternalMagazine(-1))
  224. Debug.Log("FAILED spawning item type: " + itemType + " to parent: " + parent,"n/a","n/a","SpawnSimpleChildrenItems");
  225. }
  226. }
  227. return true;
  228. }
  229. private static void HandleNewItem(notnull ItemBase item, PlayerSpawnPresetItemSetBase data)
  230. {
  231. ApplyAttributes(item,data.attributes);
  232. PlayerBase player;
  233. if (Class.CastTo(player,item.GetHierarchyRootPlayer()) && data.GetQuickbarIdx() > -1)
  234. player.SetQuickBarEntityShortcut(item,data.GetQuickbarIdx());
  235. SpawnComplexChildrenItems(item,data);
  236. SpawnSimpleChildrenItems(item,data);
  237. }
  238. private static EntityAI CreateChildItem(EntityAI parent, string type)
  239. {
  240. PlayerBase player;
  241. ItemBase newItem;
  242. if (Class.CastTo(player,parent)) //special behavior
  243. {
  244. if (Class.CastTo(newItem,player.GetInventory().CreateInInventory(type)))
  245. return newItem;
  246. Debug.Log("FAILED spawning item: " + type + ", it fits in no cargo or attachment on any worn item","n/a","n/a","CreateChildItem");
  247. return null;
  248. }
  249. //weapon magazine exception
  250. if (GetGame().ConfigIsExisting(CFG_MAGAZINESPATH + " " + type) && parent.IsWeapon())
  251. {
  252. Weapon_Base wep
  253. if (Class.CastTo(wep,parent) && wep.SpawnAmmo(type) && !wep.HasInternalMagazine(-1)) //assuming weps with internal magazine don't attach external magazines
  254. {
  255. Magazine mag;
  256. int muzzleCount = wep.GetMuzzleCount();
  257. for (int i = 0; i < muzzleCount; i++)
  258. {
  259. if (Class.CastTo(mag,wep.GetMagazine(i)) && mag.GetType() == type)
  260. return mag;
  261. }
  262. }
  263. return null;
  264. }
  265. return parent.GetInventory().CreateInInventory(type);
  266. }
  267. private static void ApplyAttributes(ItemBase item, PlayerSpawnAttributesData attributes)
  268. {
  269. if (!attributes)
  270. return;
  271. float health01 = Math.RandomFloatInclusive(attributes.healthMin,attributes.healthMax);
  272. item.SetHealth01("","Health",health01);
  273. float quantity01 = Math.RandomFloatInclusive(attributes.quantityMin,attributes.quantityMax);
  274. if (item.IsMagazine())
  275. {
  276. Magazine mag = Magazine.Cast(item);
  277. /*if (attributes.magazineAmmoOrdered && attributes.magazineAmmoOrdered.Count() > 0)
  278. {
  279. mag.ServerSetAmmoCount(0);
  280. foreach (string bulletType : attributes.magazineAmmoOrdered)
  281. {
  282. mag.ServerStoreCartridge(health01,bulletType);
  283. }
  284. mag.SetSynchDirty();
  285. }
  286. else*/
  287. {
  288. int ammoQuantity = (int)Math.Lerp(0,mag.GetAmmoMax(),quantity01);
  289. mag.ServerSetAmmoCount(ammoQuantity);
  290. }
  291. }
  292. else //'varQuantityDestroyOnMin' quantity safeguard
  293. {
  294. float quantityAbsolute = Math.Lerp(item.GetQuantityMin(),item.GetQuantityMax(),quantity01);
  295. quantityAbsolute = Math.Round(quantityAbsolute); //avoids weird floats
  296. if (quantityAbsolute <= item.GetQuantityMin() && item.ConfigGetBool("varQuantityDestroyOnMin"))
  297. quantityAbsolute++;
  298. item.SetQuantity(quantityAbsolute);
  299. }
  300. }
  301. //! Used for exceptions in the system
  302. private static bool IsWeaponAndMagazineType(EntityAI parent, string type)
  303. {
  304. return (GetGame().ConfigIsExisting(CFG_MAGAZINESPATH + " " + type) && parent.IsWeapon());
  305. }
  306. }