Advertisement
Guest User

Untitled

a guest
Oct 13th, 2019
340
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.79 KB | None | 0 0
  1. // Project: Daggerfall Tools For Unity
  2. // Copyright: Copyright (C) 2009-2019 Daggerfall Workshop
  3. // Web Site: http://www.dfworkshop.net
  4. // License: MIT License (http://www.opensource.org/licenses/mit-license.php)
  5. // Source Code: https://github.com/Interkarma/daggerfall-unity
  6. // Original Author: Gavin Clayton (interkarma@dfworkshop.net)
  7. // Contributors:
  8. //
  9. // Notes:
  10. //
  11.  
  12. using UnityEngine;
  13. using System.Collections.Generic;
  14. using DaggerfallConnect.Arena2;
  15. using DaggerfallConnect.Utility;
  16. using DaggerfallWorkshop.Utility;
  17. using DaggerfallWorkshop.Game.Entity;
  18.  
  19. namespace DaggerfallWorkshop.Game.Utility
  20. {
  21. /// <summary>
  22. /// Manages a pool of civilian mobiles (wandering NPCs) for the local town environment.
  23. /// Attached to the same GameObject as DaggerfallLocation and CityNavigation by environment layout process in StreamingWorld.
  24. /// </summary>
  25. [RequireComponent(typeof(DaggerfallLocation))]
  26. [RequireComponent(typeof(CityNavigation))]
  27. public class KamerPopulationManager : MonoBehaviour
  28. {
  29. #region Fields
  30.  
  31. const float ticksPerSecond = 10; // How often population manager will tick per second
  32.  
  33. const string mobileNPCName = "MobileNPC"; // Name displayed in scene view
  34. const int maxPlayerDistanceOutsideRect = 2500; // Max world units beyond location rect where no mobiles are spawned
  35. const int populationIndexPer16Blocks = 10; // This many NPCs will be spawned around player per 16 RMB blocks in location
  36. const int navGridSpawnRadius = 96; // Radius of spawn distance around player or target point
  37. const float recycleDistance = 150f; // Distance in world units after which NPCs are recycled
  38. const float allowVisiblePopRange = 120f; // Distance in world units after which visible popin/popout is allowed
  39.  
  40. bool playerInLocationRange = false;
  41. int maxPopulation = 0;
  42. float updateTimer = 0;
  43.  
  44. FactionFile.FactionRaces populationRace;
  45.  
  46. PlayerGPS playerGPS;
  47. DaggerfallLocation dfLocation;
  48. CityNavigation cityNavigation;
  49.  
  50. List<PoolItem> populationPool = new List<PoolItem>();
  51.  
  52. #endregion
  53.  
  54. #region Structs & Enums
  55.  
  56. public struct PoolItem
  57. {
  58. public bool active; // NPC is currently active/inactive
  59. public bool scheduleEnable; // NPC is active and waiting to be made visible
  60. public bool scheduleRecycle; // NPC is active and waiting to be hidden for recycling
  61. public float distanceToPlayer; // Distance to player
  62. public MobilePersonNPC npc; // NPC motor
  63. }
  64.  
  65. #endregion
  66.  
  67. #region Properties
  68.  
  69. /// <summary>
  70. /// Gets max population calculated for this location.
  71. /// </summary>
  72. public int MaxPopulation
  73. {
  74. get { return maxPopulation; }
  75. }
  76.  
  77. public List<PoolItem> PopulationPool
  78. {
  79. get { return populationPool; }
  80. }
  81.  
  82. #endregion
  83.  
  84. #region Unity
  85.  
  86. private void Start()
  87. {
  88. // Cache references
  89. playerGPS = GameManager.Instance.PlayerGPS;
  90. dfLocation = GetComponent<DaggerfallLocation>();
  91. cityNavigation = GetComponent<CityNavigation>();
  92.  
  93. // Get dominant race in locations climate zone
  94. populationRace = playerGPS.ClimateSettings.People;
  95.  
  96. // Calculate maximum population
  97. int totalBlocks = dfLocation.Summary.BlockWidth * dfLocation.Summary.BlockHeight;
  98. int populationBlocks = Mathf.Clamp(totalBlocks / 16, 1, 4);
  99. maxPopulation = populationBlocks * populationIndexPer16Blocks;
  100. }
  101.  
  102. private void Update()
  103. {
  104. // Increment update timer
  105. updateTimer += Time.deltaTime;
  106. if (updateTimer < (1f / ticksPerSecond))
  107. return;
  108. else
  109. updateTimer = 0;
  110.  
  111. // Check if player inside max world range for population to exist
  112. playerInLocationRange = false;
  113. RectOffset locationRect = dfLocation.LocationRect;
  114. if (playerGPS.WorldX >= locationRect.left - maxPlayerDistanceOutsideRect &&
  115. playerGPS.WorldX <= locationRect.right + maxPlayerDistanceOutsideRect &&
  116. playerGPS.WorldZ >= locationRect.top - maxPlayerDistanceOutsideRect &&
  117. playerGPS.WorldZ <= locationRect.bottom + maxPlayerDistanceOutsideRect)
  118. {
  119. playerInLocationRange = true;
  120. }
  121.  
  122. // Update population
  123. SpawnAvailableMobile();
  124. UpdateMobiles();
  125. }
  126.  
  127. #endregion
  128.  
  129. #region Private Methods
  130.  
  131. /// <summary>
  132. /// Spawn a new pool item within range of player.
  133. /// </summary>
  134. void SpawnAvailableMobile()
  135. {
  136. // Player must be in range of location
  137. if (!playerInLocationRange)
  138. return;
  139.  
  140. // Get a free mobile from pool
  141. int item = GetNextFreePoolItem();
  142. if (item == -1)
  143. return;
  144.  
  145. // Get closest point on navgrid to player position in world
  146. DFPosition playerWorldPos = new DFPosition(playerGPS.WorldX, playerGPS.WorldZ);
  147. DFPosition playerGridPos = cityNavigation.WorldToNavGridPosition(playerWorldPos);
  148.  
  149. // Spawn mobile at a random position and schedule to be live
  150. DFPosition spawnPosition;
  151. if (cityNavigation.GetRandomSpawnPosition(playerGridPos, out spawnPosition, navGridSpawnRadius))
  152. {
  153. PoolItem poolItem = populationPool[item];
  154.  
  155. // Setup spawn position
  156. DFPosition worldPosition = cityNavigation.NavGridToWorldPosition(spawnPosition);
  157. Vector3 scenePosition = cityNavigation.WorldToScenePosition(worldPosition);
  158. poolItem.npc.Motor.transform.position = scenePosition;
  159. GameObjectHelper.AlignBillboardToGround(poolItem.npc.Motor.gameObject, new Vector2(0, 2f));
  160.  
  161. // Schedule for enabling
  162. poolItem.active = true;
  163. poolItem.scheduleEnable = true;
  164.  
  165. populationPool[item] = poolItem;
  166. }
  167. }
  168.  
  169. /// <summary>
  170. /// Promote pending mobiles to live status and recycle out of range mobiles.
  171. /// </summary>
  172. void UpdateMobiles()
  173. {
  174. // Racial override can suppress population, e.g. transformed lycanthrope
  175. MagicAndEffects.MagicEffects.RacialOverrideEffect racialOverride = GameManager.Instance.PlayerEffectManager.GetRacialOverrideEffect();
  176. bool suppressPopulationSpawns = racialOverride != null && racialOverride.SuppressPopulationSpawns;
  177.  
  178. bool isDaytime = DaggerfallUnity.Instance.WorldTime.Now.IsDay;
  179. for (int i = 0; i < populationPool.Count; i++)
  180. {
  181. PoolItem poolItem = populationPool[i];
  182.  
  183. // Get distance to player
  184. poolItem.distanceToPlayer = Vector3.Distance(playerGPS.transform.position, poolItem.npc.Motor.transform.position);
  185.  
  186. // Show pending mobiles when available
  187. if (poolItem.active &&
  188. poolItem.scheduleEnable &&
  189. AllowMobileActivationChange(ref poolItem) &&
  190. isDaytime &&
  191. !suppressPopulationSpawns)
  192. {
  193. poolItem.npc.Motor.gameObject.SetActive(true);
  194. poolItem.scheduleEnable = false;
  195. poolItem.npc.RandomiseNPC(GetEntityRace());
  196. poolItem.npc.Motor.InitMotor();
  197.  
  198. // Adjust billboard position for actual size
  199. Vector2 size = poolItem.npc.Billboard.GetBillboardSize();
  200. if (Mathf.Abs(size.y - 2f) > 0.1f)
  201. poolItem.npc.Billboard.transform.Translate(0, (size.y - 2f) * 0.52f, 0);
  202. }
  203.  
  204. // Mark for recycling
  205. if (poolItem.npc.Motor.SeekCount > 4 ||
  206. poolItem.distanceToPlayer > recycleDistance ||
  207. !isDaytime)
  208. {
  209. poolItem.scheduleRecycle = true;
  210. }
  211.  
  212. // Recycle pending mobiles when available
  213. if (poolItem.active && poolItem.scheduleRecycle && AllowMobileActivationChange(ref poolItem))
  214. {
  215. poolItem.npc.Motor.gameObject.SetActive(false);
  216. poolItem.active = false;
  217. poolItem.scheduleEnable = false;
  218. poolItem.scheduleRecycle = false;
  219. if (poolItem.npc.Billboard)
  220. poolItem.npc.Billboard.transform.localPosition = Vector3.zero;
  221. }
  222.  
  223. populationPool[i] = poolItem;
  224.  
  225. // Do not render active mobile until it has made at least 1 full tile move
  226. // This hides skating effect while unit aligning to navigation grid
  227. if (poolItem.active && poolItem.npc.Billboard)
  228. {
  229. MeshRenderer billboardRenderer = poolItem.npc.Billboard.GetComponent<MeshRenderer>();
  230. if (billboardRenderer)
  231. billboardRenderer.enabled = (poolItem.npc.Motor.MoveCount > 0) ? true : false;
  232. }
  233. }
  234. }
  235.  
  236. // Gets next free pool item
  237. // Will attempt to create new item if none could be found - up to max population
  238. // Returns -1 if no free item could be found or created
  239. int GetNextFreePoolItem()
  240. {
  241. // Look for an available inactive pool item
  242. for (int i = 0; i < populationPool.Count; i++)
  243. {
  244. if (!populationPool[i].active)
  245. return i;
  246. }
  247.  
  248. // Create a new item if population not at maximum
  249. if (populationPool.Count < maxPopulation)
  250. return CreateNewPoolItem();
  251.  
  252. return -1;
  253. }
  254.  
  255. // Creates a new pool item with NPC prefab - returns -1 if could not be created
  256. int CreateNewPoolItem()
  257. {
  258. // Must have an NPC prefab set
  259. if (!DaggerfallUnity.Instance.Option_MobileNPCPrefab)
  260. return -1;
  261.  
  262. // Instantiate NPC prefab
  263. GameObject go = GameObjectHelper.InstantiatePrefab(DaggerfallUnity.Instance.Option_MobileNPCPrefab.gameObject, mobileNPCName, dfLocation.transform, Vector3.zero);
  264. go.SetActive(false);
  265.  
  266. // Get MobilePersonNPC reference
  267. MobilePersonNPC npc = go.GetComponent<MobilePersonNPC>();
  268.  
  269. // Get motor and set reference to navgrid
  270. MobilePersonMotor motor = go.GetComponent<MobilePersonMotor>();
  271. motor.cityNavigation = cityNavigation;
  272.  
  273. // Create the pool item and assign new GameObject
  274. // This pool item starts inactive and can be used later
  275. PoolItem poolItem = new PoolItem();
  276. poolItem.npc = npc;
  277. poolItem.npc.Motor = motor;
  278.  
  279. // Add to pool
  280. populationPool.Add(poolItem);
  281.  
  282. return populationPool.Count - 1;
  283. }
  284.  
  285. bool AllowMobileActivationChange(ref PoolItem poolItem)
  286. {
  287. const float fieldOfView = 180f;
  288.  
  289. // Allow visible popin/popout beyond control range
  290. if (poolItem.distanceToPlayer > allowVisiblePopRange)
  291. return true;
  292.  
  293. // Check if outside player's main field of view
  294. Vector3 directionToMobile = poolItem.npc.Motor.transform.position - playerGPS.transform.position;
  295. float angle = Vector3.Angle(directionToMobile, playerGPS.transform.forward);
  296. if (angle > fieldOfView * 0.5f)
  297. {
  298. return true;
  299. }
  300.  
  301. return false;
  302. }
  303.  
  304. Races GetEntityRace()
  305. {
  306. // Convert factionfile race to entity race
  307. // DFTFU is mostly isolated from game classes and does not know entity races
  308. // Need to convert this into something the billboard can use
  309. // Only Redguard, Nord, Breton have mobile NPC assets
  310. switch (UnityEngine.Random.Range(1, 4))
  311. {
  312. case 1:
  313. return Races.Breton;
  314. case 2:
  315. return Races.Nord;
  316. case 3:
  317. return Races.Redguard;
  318. default:
  319. return Races.Breton;
  320. }
  321. }
  322.  
  323. #endregion
  324. }
  325. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement