effectarea.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. // Mostly used for better readability
  2. enum eZoneType
  3. {
  4. STATIC = 1,
  5. DYNAMIC = 2
  6. }
  7. enum EEffectAreaType
  8. {
  9. UNDEFINED = 1,
  10. HOT_SPRING = 2,
  11. VOLCANIC = 4
  12. }
  13. // Class used for parameter transfer, all of the params are shadows of EffectArea member variables
  14. class EffectAreaParams
  15. {
  16. string m_ParamName = "Default setup";
  17. string m_ParamTriggerType = "ContaminatedTrigger";
  18. float m_ParamRadius = 100;
  19. float m_ParamPosHeight = 25;
  20. float m_ParamNegHeight = 10;
  21. int m_ParamInnerRings = 1;
  22. int m_ParamInnerSpace = 35;
  23. bool m_ParamOuterToggle = true;
  24. int m_ParamOuterSpace = 20;
  25. int m_ParamOuterOffset = -5;
  26. int m_ParamVertLayers = 0;
  27. int m_ParamVerticalOffset = 10;
  28. int m_ParamEffectInterval = 0;
  29. int m_ParamEffectDuration = 0;
  30. int m_ParamEffectModifier = 0;
  31. /*
  32. int m_ParamPartId = ParticleList.CONTAMINATED_AREA_GAS_BIGASS;
  33. int m_ParamAroundPartId = ParticleList.CONTAMINATED_AREA_GAS_AROUND;
  34. int m_ParamTinyPartId = ParticleList.CONTAMINATED_AREA_GAS_TINY;
  35. */
  36. int m_ParamPartId = 0;
  37. int m_ParamAroundPartId = 0;
  38. int m_ParamTinyPartId = 0;
  39. string m_ParamPpeRequesterType = "PPERequester_ContaminatedAreaTint";
  40. }
  41. // Base class for contaminated and other "Effect" areas
  42. class EffectArea : House
  43. {
  44. // Area Data
  45. string m_Name = "Default setup"; // The user defined name of the area
  46. int m_Type = eZoneType.STATIC; // If the zone is static or dynamic
  47. vector m_Position; // World position of Area
  48. int m_EffectInterval; // If non persisent effect: determines intervals between effect activation
  49. int m_EffectDuration; // If non persisent effect: determines duration of effect
  50. bool m_EffectModifier; // Flag for modification of internal behavior of the effect
  51. // Trigger Data
  52. float m_Radius = 100; // Radius of the Contaminated Area
  53. float m_PositiveHeight = 25; // Distance between center and maximum height
  54. float m_NegativeHeight = 10; // Distance between center and minimum height
  55. // PCG parameters
  56. // Inner Particle data
  57. int m_InnerRings = 1; // The amount of inner rings one wants
  58. int m_InnerSpacing = 35; // Distance between two particles placed on inner rings of the area
  59. // Outer particle data
  60. bool m_OuterRingToggle = true; // Allow disabling outer ring if undesired
  61. int m_OuterRingOffset = -5; // Distance between the outermost ring of particles and area radius
  62. int m_OuterSpacing = 20; // Distance between two particles placed on the outer most ring of the area
  63. // Verticality handling
  64. int m_VerticalLayers = 0; // Used to add multiple layers vertically and set vertical spacing ( 0 don't do anything )
  65. int m_VerticalOffset = 10; // Used to determine vertical offset between two vertical layers
  66. // Particles and visual effects
  67. int m_ParticleID = ParticleList.CONTAMINATED_AREA_GAS_BIGASS;
  68. int m_AroundParticleID = ParticleList.CONTAMINATED_AREA_GAS_AROUND;
  69. int m_TinyParticleID = ParticleList.CONTAMINATED_AREA_GAS_TINY;
  70. string m_PPERequesterType;
  71. int m_PPERequesterIdx = -1;
  72. int m_EffectsPriority; // When multiple areas overlap, only the area with the highest priority will play its effects
  73. // Other values and storage
  74. string m_TriggerType = "ContaminatedTrigger"; // The trigger class used by this zone
  75. EffectTrigger m_Trigger; // The trigger used to determine if player is inside toxic area
  76. ref array<Particle> m_ToxicClouds; // All static toxic clouds in ContaminatedArea
  77. // ----------------------------------------------
  78. // INITIAL SETUP
  79. // ----------------------------------------------
  80. void EffectArea()
  81. {
  82. RegisterNetSyncVariableFloat("m_Radius", 0, 0, 2);
  83. RegisterNetSyncVariableFloat("m_PositiveHeight", 0, 0, 2);
  84. RegisterNetSyncVariableFloat("m_NegativeHeight", 0, 0, 2);
  85. RegisterNetSyncVariableInt("m_InnerRings");
  86. RegisterNetSyncVariableInt("m_InnerSpacing");
  87. RegisterNetSyncVariableInt("m_OuterRingOffset");
  88. RegisterNetSyncVariableInt("m_OuterSpacing");
  89. RegisterNetSyncVariableInt("m_VerticalLayers");
  90. RegisterNetSyncVariableInt("m_VerticalOffset");
  91. RegisterNetSyncVariableInt("m_ParticleID");
  92. /*
  93. RegisterNetSyncVariableInt("m_AroundParticleID");
  94. RegisterNetSyncVariableInt("m_TinyParticleID");
  95. RegisterNetSyncVariableInt("m_PPERequesterIdx");
  96. */
  97. RegisterNetSyncVariableBool("m_OuterRingToggle");
  98. }
  99. void ~EffectArea()
  100. {
  101. }
  102. void SetupZoneData( EffectAreaParams params )
  103. {
  104. // A lot of branching, allowing to use default values on specified params
  105. if ( params.m_ParamName != "" )
  106. m_Name = params.m_ParamName;
  107. if ( params.m_ParamTriggerType != "" )
  108. m_TriggerType = params.m_ParamTriggerType;
  109. if ( params.m_ParamRadius > 0 )
  110. m_Radius = params.m_ParamRadius;
  111. if ( params.m_ParamPosHeight > -1 )
  112. m_PositiveHeight = params.m_ParamPosHeight;
  113. if ( params.m_ParamNegHeight > -1 )
  114. m_NegativeHeight = params.m_ParamNegHeight;
  115. m_InnerRings = params.m_ParamInnerRings;
  116. if ( params.m_ParamInnerSpace > -1 )
  117. m_InnerSpacing = params.m_ParamInnerSpace;
  118. m_OuterRingToggle = params.m_ParamOuterToggle;
  119. if ( params.m_ParamOuterSpace > -1 )
  120. m_OuterSpacing = params.m_ParamOuterSpace;
  121. m_OuterRingOffset = params.m_ParamOuterOffset;
  122. if ( params.m_ParamVertLayers > 0 )
  123. m_VerticalLayers = params.m_ParamVertLayers;
  124. if ( params.m_ParamVerticalOffset > 0 )
  125. m_VerticalOffset = params.m_ParamVerticalOffset;
  126. m_EffectInterval = params.m_ParamEffectInterval;
  127. m_EffectDuration = params.m_ParamEffectDuration;
  128. m_EffectModifier = params.m_ParamEffectModifier;
  129. m_ParticleID = params.m_ParamPartId;
  130. m_AroundParticleID = params.m_ParamAroundPartId;
  131. m_TinyParticleID = params.m_ParamTinyPartId;
  132. if ( params.m_ParamPpeRequesterType != "" )
  133. {
  134. m_PPERequesterType = params.m_ParamPpeRequesterType;
  135. m_PPERequesterIdx = GetRequesterIndex(m_PPERequesterType);
  136. }
  137. // We get the PPE index for future usage and synchronization
  138. // DEVELOPER NOTE :
  139. // If you cannot register a new requester, add your own indexation and lookup methods to get an index and synchronize it
  140. // EXAMPLE : m_PPERequesterIdx = MyLookupMethod()
  141. // We sync our data
  142. SetSynchDirty();
  143. // Now that everything is ready, we finalize setup
  144. InitZone();
  145. }
  146. void Tick() {};
  147. // Through this we will evaluate the resize of particles
  148. override void OnCEUpdate()
  149. {
  150. super.OnCEUpdate();
  151. Tick();
  152. }
  153. void InitZone()
  154. {
  155. //Debug.Log("------------------------------------------");
  156. //Debug.Log( "We have created the zone : " + m_Name );
  157. m_Position = GetWorldPosition();
  158. if ( !GetGame().IsDedicatedServer() )
  159. {
  160. InitZoneClient();
  161. }
  162. if ( GetGame().IsServer() )
  163. {
  164. InitZoneServer();
  165. }
  166. //Debug.Log("------------------------------------------");
  167. }
  168. // The following methods are to be overriden to execute specifc logic
  169. // Each method is executed where it says it will so no need to check for server or client ;)
  170. void InitZoneServer() {};
  171. void InitZoneClient() {};
  172. // ----------------------------------------------
  173. // INTERACTION SETUP
  174. // ----------------------------------------------
  175. override bool CanPutInCargo( EntityAI parent )
  176. {
  177. return false;
  178. }
  179. override bool CanPutIntoHands( EntityAI parent )
  180. {
  181. return false;
  182. }
  183. override bool DisableVicinityIcon()
  184. {
  185. return true;
  186. }
  187. override bool CanBeTargetedByAI( EntityAI ai )
  188. {
  189. return false;
  190. }
  191. // ----------------------------------------------
  192. // PARTICLE GENERATION
  193. // ----------------------------------------------
  194. // Used to position all particles procedurally
  195. void PlaceParticles( vector pos, float radius, int nbRings, int innerSpacing, bool outerToggle, int outerSpacing, int outerOffset, int partId )
  196. {
  197. #ifdef NO_GUI
  198. return; // do not place any particles if there is no GUI
  199. #endif
  200. if (partId == 0)
  201. {
  202. Error("[WARNING] :: [EffectArea PlaceParticles] :: no particle defined, skipping area particle generation" );
  203. return;
  204. }
  205. // Determine if we snap first layer to ground
  206. bool snapFirstLayer = true;
  207. if ( m_Type == eZoneType.STATIC && pos[1] != GetGame().SurfaceRoadY( pos[0], pos[2] ) )
  208. snapFirstLayer = false;
  209. // BEGINNING OF SAFETY NET
  210. // We want to prevent divisions by 0
  211. if ( radius == 0 )
  212. {
  213. // In specific case of radius, we log an error and return as it makes no sense
  214. Error("[WARNING] :: [EffectArea PlaceParticles] :: Radius of contaminated zone is set to 0, this should not happen");
  215. return;
  216. }
  217. if ( outerToggle && radius == outerOffset )
  218. {
  219. Error("[WARNING] :: [EffectArea PlaceParticles] :: Your outerOffset is EQUAL to your Radius, this will result in division by 0");
  220. return;
  221. }
  222. // Inner spacing of 0 would cause infinite loops as no increment would happen
  223. if ( innerSpacing == 0 )
  224. innerSpacing = 1;
  225. // END OF SAFETY NET
  226. int partCounter = 0; // Used for debugging, allows one to know how many emitters are spawned in zone
  227. int numberOfEmitters = 1; // We always have the central emitter
  228. //Debug.Log("We have : " + nbRings + " rings");
  229. //Debug.Log("We have : " + m_VerticalLayers + " layers");
  230. float angle = 0; // Used in for loop to know where we are in terms of angle spacing ( RADIANS )
  231. ParticlePropertiesArray props = new ParticlePropertiesArray();
  232. // We also populate vertically, layer 0 will be snapped to ground, subsequent layers will see particles floating and relevant m_VerticalOffset
  233. for ( int k = 0; k <= m_VerticalLayers; k++ )
  234. {
  235. vector partPos = pos;
  236. // We prevent division by 0
  237. // We don't want to tamper with ground layer vertical positioning
  238. if ( k != 0 )
  239. {
  240. partPos[1] = partPos[1] + ( m_VerticalOffset * k );
  241. }
  242. // We will want to start by placing a particle at center of area
  243. props.Insert(ParticleProperties(partPos, ParticlePropertiesFlags.PLAY_ON_CREATION, null, vector.Zero, this));
  244. partCounter++;
  245. // For each concentric ring, we place a particle emitter at a set offset
  246. for ( int i = 1; i <= nbRings + outerToggle; i++ )
  247. {
  248. //Debug.Log("We are on iteration I : " + i );
  249. // We prepare the variables to use later in calculation
  250. float angleIncrement; // The value added to the offset angle to place following particle
  251. float ab; // Length of a side of triangle used to calculate particle positionning
  252. vector temp = vector.Zero; // Vector we rotate to position next spawn point
  253. // The particle density is not the same on the final ring which will only happen if toggled
  254. // Toggle uses bool parameter treated as int, thus i > nbRings test ( allows to limit branching )
  255. if ( i > nbRings )
  256. {
  257. ab = radius - outerOffset; // We want to leave some space to better see area demarcation
  258. // We calculate the rotation angle depending on particle spacing and distance from center
  259. angleIncrement = Math.Acos( 1 - ( ( outerSpacing * outerSpacing ) / ( 2 * Math.SqrInt(ab) ) ) );
  260. temp[2] = temp[2] + ab;
  261. //Debug.Log("Radius of last circle " + i + " is : " + ab);
  262. }
  263. else
  264. {
  265. ab = ( radius / ( nbRings + 1 ) ) * i; // We add the offset from one ring to another
  266. // We calculate the rotation angle depending on particle spacing and distance from center
  267. angleIncrement = Math.Acos( 1 - ( ( innerSpacing * innerSpacing ) / ( 2 * Math.SqrInt(ab) ) ) );
  268. temp[2] = temp[2] + ab;
  269. //Debug.Log("Radius of inner circle " + i + " is : " + ab);
  270. }
  271. for ( int j = 0; j <= ( Math.PI2 / angleIncrement ); j++ )
  272. {
  273. // Determine position of particle emitter
  274. // Use offset of current ring for vector length
  275. // Use accumulated angle for vector direction
  276. float sinAngle = Math.Sin( angle );
  277. float cosAngle = Math.Cos( angle );
  278. partPos = vector.RotateAroundZero( temp, vector.Up, cosAngle, sinAngle );
  279. partPos += pos;
  280. // We snap first layer to ground if specified
  281. if ( k == 0 && snapFirstLayer == true )
  282. partPos[1] = GetGame().SurfaceY( partPos[0], partPos[2] );
  283. else if ( k == 0 && snapFirstLayer == false )
  284. partPos[1] = partPos[1] - m_NegativeHeight;
  285. // We check the particle is indeed in the trigger to make it consistent
  286. if ( partPos[1] <= pos[1] + m_PositiveHeight && partPos[1] >= pos[1] - m_NegativeHeight )
  287. {
  288. // Place emitter at vector end ( coord )
  289. props.Insert(ParticleProperties(partPos, ParticlePropertiesFlags.PLAY_ON_CREATION, null, GetGame().GetSurfaceOrientation( partPos[0], partPos[2] ), this));
  290. ++partCounter;
  291. }
  292. // Increase accumulated angle
  293. angle += angleIncrement;
  294. }
  295. angle = 0; // We reset our accumulated angle for the next ring
  296. }
  297. }
  298. m_ToxicClouds.Reserve(partCounter);
  299. ParticleManager gPM = ParticleManager.GetInstance();
  300. array<ParticleSource> createdParticles = gPM.CreateParticlesByIdArr(partId, props, partCounter);
  301. if (createdParticles.Count() != partCounter)
  302. {
  303. if (gPM.IsFinishedAllocating())
  304. {
  305. ErrorEx(string.Format("Not enough particles in pool for EffectArea: %1", m_Name));
  306. OnParticleAllocation(gPM, createdParticles);
  307. }
  308. else
  309. {
  310. gPM.GetEvents().Event_OnAllocation.Insert(OnParticleAllocation);
  311. }
  312. }
  313. else
  314. {
  315. OnParticleAllocation(gPM, createdParticles);
  316. }
  317. //Debug.Log("Emitter count : " + partCounter );
  318. }
  319. void OnParticleAllocation(ParticleManager pm, array<ParticleSource> particles)
  320. {
  321. foreach (ParticleSource p : particles)
  322. {
  323. if (p.GetOwner() == this) // Safety, since it can be unrelated particles when done through event
  324. m_ToxicClouds.Insert(p);
  325. }
  326. }
  327. int GetRequesterIndex(string type)
  328. {
  329. typename t = type.ToType();
  330. if (!t)
  331. return - 1;
  332. PPERequesterBase req = PPERequesterBank.GetRequester(t);
  333. if (req)
  334. return req.GetRequesterIDX();
  335. return -1;
  336. }
  337. // ----------------------------------------------
  338. // TRIGGER SETUP
  339. // ----------------------------------------------
  340. void CreateTrigger( vector pos, int radius )
  341. {
  342. // The trigger pos is based on lwer end, but we want to stretch downwards
  343. pos[1] = pos[1] - m_NegativeHeight;
  344. // Create new trigger of specified type
  345. if ( Class.CastTo( m_Trigger, GetGame().CreateObjectEx( m_TriggerType, pos, ECE_NONE ) ) )
  346. {
  347. // We finalize trigger dimension setup
  348. m_Trigger.SetCollisionCylinder( radius, ( m_NegativeHeight + m_PositiveHeight ) );
  349. // If the trigger is lower in hierarchy and can see it's local effects customized, we pass the new parameters
  350. if ( m_Trigger.IsInherited( EffectTrigger ) )
  351. {
  352. //Debug.Log("We override area local effects");
  353. EffectTrigger.Cast( m_Trigger ).SetLocalEffects( m_AroundParticleID, m_TinyParticleID, m_PPERequesterIdx );
  354. }
  355. m_Trigger.Init(this, m_EffectsPriority);
  356. //Debug.Log("We created the trigger at : " + m_Trigger.GetWorldPosition() );
  357. }
  358. }
  359. // ----------------------------------------------
  360. // AREA DELETION
  361. // ----------------------------------------------
  362. override void EEDelete( EntityAI parent )
  363. {
  364. if ( m_Trigger )
  365. {
  366. GetGame().ObjectDelete( m_Trigger );
  367. }
  368. // We stop playing particles on this client when the base object is deleted ( out of range for example )
  369. if ( (GetGame().IsClient() || !GetGame().IsMultiplayer()) && m_ToxicClouds )
  370. {
  371. foreach ( Particle p : m_ToxicClouds )
  372. {
  373. p.Stop();
  374. }
  375. }
  376. super.EEDelete( parent );
  377. }
  378. void OnPlayerEnterServer(PlayerBase player, EffectTrigger trigger)
  379. {
  380. player.IncreaseEffectAreaCount();
  381. }
  382. void OnPlayerExitServer(PlayerBase player, EffectTrigger trigger)
  383. {
  384. player.DecreaseEffectAreaCount();
  385. }
  386. }