123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586 |
- // #define EFFECT_AREA_VISUAL_DEBUG
- // Mostly used for better readability
- enum eZoneType
- {
- STATIC = 1,
- DYNAMIC = 2
- }
- enum EEffectAreaType
- {
- UNDEFINED = 1,
- HOT_SPRING = 2,
- VOLCANIC = 4
- }
- // Class used for parameter transfer, all of the params are shadows of EffectArea member variables
- class EffectAreaParams
- {
- string m_ParamName = "Default setup";
- string m_ParamTriggerType = "ContaminatedTrigger";
- float m_ParamRadius = 100;
- float m_ParamPosHeight = 25;
- float m_ParamNegHeight = 10;
- int m_ParamInnerRings = 1;
- int m_ParamInnerSpace = 35;
- bool m_ParamOuterToggle = true;
- int m_ParamOuterSpace = 20;
- int m_ParamOuterOffset = -5;
- int m_ParamVertLayers = 0;
- int m_ParamVerticalOffset = 10;
- int m_ParamEffectInterval = 0;
- int m_ParamEffectDuration = 0;
- int m_ParamEffectModifier = 0;
- /*
- int m_ParamPartId = ParticleList.CONTAMINATED_AREA_GAS_BIGASS;
- int m_ParamAroundPartId = ParticleList.CONTAMINATED_AREA_GAS_AROUND;
- int m_ParamTinyPartId = ParticleList.CONTAMINATED_AREA_GAS_TINY;
- */
- int m_ParamPartId = 0;
- int m_ParamAroundPartId = 0;
- int m_ParamTinyPartId = 0;
-
- string m_ParamPpeRequesterType = "PPERequester_ContaminatedAreaTint";
- }
- // Base class for contaminated and other "Effect" areas
- class EffectArea : House
- {
- // Area Data
- string m_Name = "Default setup"; // The user defined name of the area
- int m_Type = eZoneType.STATIC; // If the zone is static or dynamic
- vector m_Position; // World position of area snapped to ground on creation (see: EffectAreaLoader)
- vector m_PositionTrigger; // World position adjusted according to trigger pivot (pivot is cylinder center)
- int m_EffectInterval; // If non persisent effect: determines intervals between effect activation
- int m_EffectDuration; // If non persisent effect: determines duration of effect
- bool m_EffectModifier; // Flag for modification of internal behavior of the effect
-
- // Trigger Data
- float m_Radius = 100; // Radius of the Contaminated Area
- float m_PositiveHeight = 25; // Distance between center and maximum height
- float m_NegativeHeight = 10; // Distance between center and minimum height
-
- // PCG parameters
- // Inner Particle data
- int m_InnerRings = 1; // The amount of inner rings one wants
- int m_InnerSpacing = 35; // Distance between two particles placed on inner rings of the area
- // Outer particle data
- bool m_OuterRingToggle = true; // Allow disabling outer ring if undesired
- int m_OuterRingOffset = -5; // Distance between the outermost ring of particles and area radius
- int m_OuterSpacing = 20; // Distance between two particles placed on the outer most ring of the area
- // Verticality handling
- int m_VerticalLayers = 0; // Used to add multiple layers vertically and set vertical spacing ( 0 don't do anything )
- int m_VerticalOffset = 10; // Used to determine vertical offset between two vertical layers
-
- // Particles and visual effects
- int m_ParticleID = ParticleList.CONTAMINATED_AREA_GAS_BIGASS;
- int m_AroundParticleID = ParticleList.CONTAMINATED_AREA_GAS_AROUND;
- int m_TinyParticleID = ParticleList.CONTAMINATED_AREA_GAS_TINY;
- string m_PPERequesterType;
- int m_PPERequesterIdx = -1;
- int m_EffectsPriority; // When multiple areas overlap, only the area with the highest priority will play its effects
-
- const int PARTICLES_MAX = 1000; // Better safe than sorry
-
- // Other values and storage
- string m_TriggerType = "ContaminatedTrigger"; // The trigger class used by this zone
- EffectTrigger m_Trigger; // The trigger used to determine if player is inside toxic area
- ref array<Particle> m_ToxicClouds; // All static toxic clouds in ContaminatedArea
- // ----------------------------------------------
- // INITIAL SETUP
- // ----------------------------------------------
-
- void EffectArea()
- {
- RegisterNetSyncVariableFloat("m_Radius", 0, 0, 2);
- RegisterNetSyncVariableFloat("m_PositiveHeight", 0, 0, 2);
- RegisterNetSyncVariableFloat("m_NegativeHeight", 0, 0, 2);
-
- RegisterNetSyncVariableInt("m_InnerRings");
- RegisterNetSyncVariableInt("m_InnerSpacing");
- RegisterNetSyncVariableInt("m_OuterRingOffset");
- RegisterNetSyncVariableInt("m_OuterSpacing");
- RegisterNetSyncVariableInt("m_VerticalLayers");
- RegisterNetSyncVariableInt("m_VerticalOffset");
-
- RegisterNetSyncVariableInt("m_ParticleID");
- /*
- RegisterNetSyncVariableInt("m_AroundParticleID");
- RegisterNetSyncVariableInt("m_TinyParticleID");
- RegisterNetSyncVariableInt("m_PPERequesterIdx");
- */
- RegisterNetSyncVariableBool("m_OuterRingToggle");
- }
-
- void ~EffectArea()
- {
-
- }
- void SetupZoneData( EffectAreaParams params )
- {
- // A lot of branching, allowing to use default values on specified params
- if ( params.m_ParamName != "" )
- m_Name = params.m_ParamName;
- if ( params.m_ParamTriggerType != "" )
- m_TriggerType = params.m_ParamTriggerType;
-
- if ( params.m_ParamRadius > 0 )
- m_Radius = params.m_ParamRadius;
- if ( params.m_ParamPosHeight > -1 )
- m_PositiveHeight = params.m_ParamPosHeight;
- if ( params.m_ParamNegHeight > -1 )
- m_NegativeHeight = params.m_ParamNegHeight;
-
- m_InnerRings = params.m_ParamInnerRings;
- if ( params.m_ParamInnerSpace > -1 )
- m_InnerSpacing = params.m_ParamInnerSpace;
-
- m_OuterRingToggle = params.m_ParamOuterToggle;
- if ( params.m_ParamOuterSpace > -1 )
- m_OuterSpacing = params.m_ParamOuterSpace;
- m_OuterRingOffset = params.m_ParamOuterOffset;
-
- if ( params.m_ParamVertLayers > 0 )
- m_VerticalLayers = params.m_ParamVertLayers;
- if ( params.m_ParamVerticalOffset > 0 )
- m_VerticalOffset = params.m_ParamVerticalOffset;
-
- m_EffectInterval = params.m_ParamEffectInterval;
- m_EffectDuration = params.m_ParamEffectDuration;
- m_EffectModifier = params.m_ParamEffectModifier;
-
- m_ParticleID = params.m_ParamPartId;
- m_AroundParticleID = params.m_ParamAroundPartId;
- m_TinyParticleID = params.m_ParamTinyPartId;
-
- if ( params.m_ParamPpeRequesterType != "" )
- {
- m_PPERequesterType = params.m_ParamPpeRequesterType;
- m_PPERequesterIdx = GetRequesterIndex(m_PPERequesterType);
- }
- // We get the PPE index for future usage and synchronization
-
- // DEVELOPER NOTE :
- // If you cannot register a new requester, add your own indexation and lookup methods to get an index and synchronize it
- // EXAMPLE : m_PPERequesterIdx = MyLookupMethod()
-
- #ifdef ENABLE_LOGGING
- Debug.Log(">>>> SetupZoneData: Finished: " + m_Name);
- #endif
-
- // We sync our data
- SetSynchDirty();
- }
- void Tick()
- {
- #ifdef DIAG_DEVELOPER
- #ifdef EFFECT_AREA_VISUAL_DEBUG
- CleanupDebugShapes(m_DebugTargets);
- #endif
- #endif
- }
-
-
- // Through this we will evaluate the resize of particles
- override void OnCEUpdate()
- {
- super.OnCEUpdate();
- Tick();
- }
-
- void InitZone()
- {
- // Debug.Log("------------------------------------------");
- // Debug.Log("InitZone: " + m_Name);
-
- m_Position = GetPosition();
- m_PositionTrigger = m_Position;
- m_PositionTrigger[1] = m_Position[1] + ((m_PositiveHeight - m_NegativeHeight) * 0.5); // Cylinder trigger pivot correction
-
- if (!GetGame().IsDedicatedServer())
- InitZoneClient();
-
- if (GetGame().IsServer())
- InitZoneServer();
-
- // Debug.Log("------------------------------------------");
- }
-
- // The following methods are to be overriden to execute specifc logic
- void InitZoneServer();
- void InitZoneClient();
-
- // ----------------------------------------------
- // INTERACTION SETUP
- // ----------------------------------------------
-
- override bool CanPutInCargo( EntityAI parent )
- {
- return false;
- }
-
- override bool CanPutIntoHands( EntityAI parent )
- {
- return false;
- }
-
- override bool DisableVicinityIcon()
- {
- return true;
- }
-
- override bool CanBeTargetedByAI( EntityAI ai )
- {
- return false;
- }
-
- // ----------------------------------------------
- // PARTICLE GENERATION
- // ----------------------------------------------
- // Used to position all particles procedurally
- void PlaceParticles( vector pos, float radius, int nbRings, int innerSpacing, bool outerToggle, int outerSpacing, int outerOffset, int partId )
- {
- // Debug.Log("PlaceParticles: " + pos);
-
- #ifdef NO_GUI
- return; // do not place any particles if there is no GUI
- #endif
- if (partId == 0)
- {
- Error("[WARNING] :: [EffectArea PlaceParticles] :: no particle defined, skipping area particle generation" );
- return;
- }
- if ( radius == 0 )
- {
- Error("[WARNING] :: [EffectArea PlaceParticles] :: Radius of contaminated zone is set to 0, this should not happen");
- return;
- }
- if ( outerToggle && radius == outerOffset )
- {
- Error("[WARNING] :: [EffectArea PlaceParticles] :: Your outerOffset is EQUAL to your Radius, this will result in division by 0");
- return;
- }
-
- int partCount = 0; // Number of spawned emitters
-
- ParticlePropertiesArray props = new ParticlePropertiesArray();
-
- // Inner spacing of 0 would cause infinite loops as no increment would happen
- if (innerSpacing == 0)
- innerSpacing = 1;
-
- // For each concentric ring, we place a particle emitter at a set offset
- for ( int i = 0; i <= nbRings + outerToggle; ++i )
- {
- if (i == 0) // Skipping 0, we want to start by placing a particle at center of area
- {
- SpawnParticles(props, pos, pos, partCount);
- }
- else
- {
- // We prepare the variables to use later in calculation
- float angleIncrement; // The value added to the offset angle to place following particle
- float ab; // Length of a side of triangle used to calculate particle positionning
- vector temp = vector.Zero; // Vector we rotate to position next spawn point
-
- // The particle density is not the same on the final ring which will only happen if toggled
- // Toggle uses bool parameter treated as int, thus i > nbRings test ( allows to limit branching )
- if ( i > nbRings )
- {
- ab = radius - outerOffset; // We want to leave some space to better see area demarcation
-
- // We calculate the rotation angle depending on particle spacing and distance from center
- angleIncrement = Math.Acos( 1 - ( ( outerSpacing * outerSpacing ) / ( 2 * Math.SqrInt(ab) ) ) );
- temp[2] = temp[2] + ab;
-
- //Debug.Log("Radius of last circle " + i + " is : " + ab);
- }
- else
- {
- ab = ( radius / ( nbRings + 1 ) ) * i; // We add the offset from one ring to another
-
- // We calculate the rotation angle depending on particle spacing and distance from center
- angleIncrement = Math.Acos( 1 - ( ( innerSpacing * innerSpacing ) / ( 2 * Math.SqrInt(ab) ) ) );
- temp[2] = temp[2] + ab;
-
- //Debug.Log("Radius of inner circle " + i + " is : " + ab);
- }
- for ( int j = 0; j <= ( Math.PI2 / angleIncrement ); j++ )
- {
- // Determine position of particle emitter
- // Use offset of current ring for vector length
- float sinAngle = Math.Sin(angleIncrement * j);
- float cosAngle = Math.Cos(angleIncrement * j);
- vector partPos = vector.RotateAroundZero( temp, vector.Up, cosAngle, sinAngle );
- partPos += pos;
- SpawnParticles(props, pos, partPos, partCount);
- }
- }
- }
-
- InsertParticles(props, partCount, partId);
- }
-
- // Fill the radius with particle emitters using the Circle packing in a circle method
- void FillWithParticles(vector pos, float areaRadius, float outwardsBleed, float partSize, int partId)
- {
- // Debug.Log("FillWithParticles: " + pos);
-
- #ifdef NO_GUI
- return; // do not place any particles if there is no GUI
- #endif
- if (partId == 0)
- return;
-
- if (partSize <= 0)
- partSize = 1;
-
- int partCount = 0; // Number of spawned emitters
- int ringCount = 0; // Number of area rings
- float ringDist = 0; // Distance between rings
- float radiusMax = areaRadius + outwardsBleed; // Visual radius of the area
- float radiusPart = partSize / 2; // Particle radius
-
- bool centerPart = true; // Spawn central particle?
-
- ParticlePropertiesArray props = new ParticlePropertiesArray();
- // Debug.Log("Area radius: " + radiusMax + "m, Particle radius: " + radiusPart + "m");
-
- if (radiusMax > radiusPart * 1.5) // Area has to be larger than particle, plus some margin
- {
- if (radiusMax < radiusPart * 2.5) // Area fits one ring of particles, but no center particle (minus some overlap margin)
- {
- ringDist = radiusMax - radiusPart; // Snap the particles to outer edge
- ringCount = 1;
- centerPart = false;
- }
- else // Area fits all
- {
- radiusMax -= radiusPart; // Snap the particles to outer edge
- ringCount = Math.Ceil(radiusMax / partSize); // Get number of inner rings
- ringDist = radiusMax / ringCount; // Adjust ring distance after rounding
- }
- }
-
- // Debug.Log("We have : " + ringCount + " rings, " + ringDist + "m apart, center: " + centerPart);
- // Debug.Log("We have : " + m_VerticalLayers + " layers, " + m_VerticalOffset + "m apart");
-
- // For each concentric ring, we place a particle emitter at a set offset
- for (int ring = 0; ring <= ringCount; ++ring)
- {
- if (ring == 0 && centerPart) // We start by placing particle at center of area
- {
- SpawnParticles(props, pos, pos, partCount);
- }
- else if (ring > 0)
- {
- float ringRadius = ringDist * ring;
- float circumference = 2 * Math.PI2 * ringRadius;
-
- int count = Math.Floor(circumference / partSize); // Get number of particles on ring (roughly)
- float angleInc = Math.PI2 / count; // Get angle between particles on ring
-
- for (int i = 0; i < count; ++i) // Insert particles around the ring
- {
- vector partPos = pos;
- float x = ringRadius * Math.Sin(angleInc * i);
- float z = ringRadius * Math.Cos(angleInc * i);
-
- partPos[0] = partPos[0] + x;
- partPos[2] = partPos[2] + z;
-
- SpawnParticles(props, pos, partPos, partCount);
- }
- }
- }
-
- InsertParticles(props, partCount, partId);
- }
-
- protected void SpawnParticles(ParticlePropertiesArray props, vector centerPos, vector partPos, inout int count)
- {
- partPos[1] = GetGame().SurfaceY(partPos[0], partPos[2]); // Snap particles to ground
-
- // We also populate vertically, layer 0 will be snapped to ground, subsequent layers will see particles floating by m_VerticalOffset
- for (int layer = 0; layer <= m_VerticalLayers; ++layer)
- {
- partPos[1] = partPos[1] + (m_VerticalOffset * layer);
-
- // We check that spawned particle is inside the trigger
- if (count < PARTICLES_MAX && Math.IsInRange(partPos[1], centerPos[1] - m_NegativeHeight, centerPos[1] + m_PositiveHeight))
- {
- props.Insert(ParticleProperties(partPos, ParticlePropertiesFlags.PLAY_ON_CREATION, null, GetGame().GetSurfaceOrientation( partPos[0], partPos[2] ), this));
- ++count;
- }
- }
- }
-
- private void InsertParticles(ParticlePropertiesArray props, int count, int partId)
- {
- m_ToxicClouds.Reserve(count);
-
- ParticleManager gPM = ParticleManager.GetInstance();
-
- array<ParticleSource> createdParticles = gPM.CreateParticlesByIdArr(partId, props, count);
- if (createdParticles.Count() != count)
- {
- if (gPM.IsFinishedAllocating())
- {
- ErrorEx(string.Format("Not enough particles in pool for EffectArea: %1", m_Name));
- OnParticleAllocation(gPM, createdParticles);
- }
- else
- {
- gPM.GetEvents().Event_OnAllocation.Insert(OnParticleAllocation);
- }
- }
- else
- {
- OnParticleAllocation(gPM, createdParticles);
- }
-
- // Debug.Log("Emitter count: " + count);
- }
-
- void OnParticleAllocation(ParticleManager pm, array<ParticleSource> particles)
- {
- foreach (ParticleSource p : particles)
- {
- if (p.GetOwner() == this) // Safety, since it can be unrelated particles when done through event
- {
- m_ToxicClouds.Insert(p);
- }
- }
- }
-
- int GetRequesterIndex(string type)
- {
- typename t = type.ToType();
- if (!t)
- return - 1;
- PPERequesterBase req = PPERequesterBank.GetRequester(t);
- if (req)
- return req.GetRequesterIDX();
- return -1;
- }
-
-
- // ----------------------------------------------
- // TRIGGER SETUP
- // ----------------------------------------------
- void CreateTrigger(vector pos, int radius)
- {
- #ifdef DIAG_DEVELOPER
- #ifdef EFFECT_AREA_VISUAL_DEBUG
- Shape dbgShape;
- CleanupDebugShapes(m_DebugTargets);
- #endif
- #endif
-
- // Create new trigger of specified type
- if (Class.CastTo(m_Trigger, GetGame().CreateObjectEx(m_TriggerType, pos, ECE_NONE)))
- {
- // We finalize trigger dimension setup
- float centerHeightCorrection = (m_PositiveHeight - m_NegativeHeight) * 0.5;
-
- m_Trigger.SetCollisionCylinderTwoWay(radius, -(m_NegativeHeight + centerHeightCorrection), (m_PositiveHeight - centerHeightCorrection));
- m_Trigger.SetPosition(pos);
- m_Trigger.Update();
-
- #ifdef DIAG_DEVELOPER
- #ifdef EFFECT_AREA_VISUAL_DEBUG
- /*
- vector cubePos = pos;
- cubePos[0] = cubePos[0] + radius;
- cubePos[1] = cubePos[1] + (m_PositiveHeight - centerHeightCorrection);
- cubePos[2] = cubePos[2] + radius;
- m_DebugTargets.Insert(Debug.DrawCube(cubePos, 0.5, 0x1fff0000));
- */
-
- vector colliderPosDebug = pos;
- //! upper limit
- colliderPosDebug[1] = pos[1] + (m_PositiveHeight - centerHeightCorrection);
- m_DebugTargets.Insert(Debug.DrawSphere(colliderPosDebug, 0.15, 0x1f0000ff, ShapeFlags.NOZWRITE));
- //m_DebugTargets.Insert(Debug.DrawLine(cubePos, colliderPosDebug, 0x1fff0000, ShapeFlags.NOZWRITE)); // connector
- //! center
- m_DebugTargets.Insert(Debug.DrawSphere(pos, 0.15, 0x1fff0000, ShapeFlags.NOZWRITE));
- //m_DebugTargets.Insert(Debug.DrawLine(cubePos, pos, 0x1fff0000, ShapeFlags.NOZWRITE)); // connector
-
- //! bottom limit
- colliderPosDebug[1] = pos[1] - (m_NegativeHeight + centerHeightCorrection);
- m_DebugTargets.Insert(Debug.DrawSphere(colliderPosDebug, 0.15, 0x1f00ff00, ShapeFlags.NOZWRITE));
- //m_DebugTargets.Insert(Debug.DrawLine(cubePos, colliderPosDebug, 0x1fff0000, ShapeFlags.NOZWRITE)); // connector
-
- float triggerHeight = (m_PositiveHeight + m_NegativeHeight);
- m_DebugTargets.Insert(Debug.DrawCylinder(pos, radius, triggerHeight, 0x1f0000ff, ShapeFlags.TRANSP|ShapeFlags.NOZWRITE));
- #endif
- #endif
-
- // If the trigger is lower in hierarchy and can see it's local effects customized, we pass the new parameters
- if ( m_Trigger.IsInherited(EffectTrigger))
- {
- //Debug.Log("We override area local effects");
- EffectTrigger.Cast(m_Trigger).SetLocalEffects(m_AroundParticleID, m_TinyParticleID, m_PPERequesterIdx);
- }
- m_Trigger.Init(this, m_EffectsPriority);
- //Debug.Log("We created the trigger at : " + m_Trigger.GetWorldPosition() );
- }
- }
-
- // ----------------------------------------------
- // AREA DELETION
- // ----------------------------------------------
-
- override void EEDelete( EntityAI parent )
- {
- if ( m_Trigger )
- {
- GetGame().ObjectDelete( m_Trigger );
- }
-
- // We stop playing particles on this client when the base object is deleted ( out of range for example )
- if ( (GetGame().IsClient() || !GetGame().IsMultiplayer()) && m_ToxicClouds )
- {
- foreach ( Particle p : m_ToxicClouds )
- {
- p.Stop();
- }
- }
-
- super.EEDelete( parent );
- }
-
- void OnPlayerEnterServer(PlayerBase player, EffectTrigger trigger)
- {
- player.IncreaseEffectAreaCount();
- }
- void OnPlayerExitServer(PlayerBase player, EffectTrigger trigger)
- {
- player.DecreaseEffectAreaCount();
- }
-
- #ifdef DIAG_DEVELOPER
- #ifdef EFFECT_AREA_VISUAL_DEBUG
- protected ref array<Shape> m_DebugTargets = new array<Shape>();
-
- protected void CleanupDebugShapes(array<Shape> shapes)
- {
- foreach (Shape shape : shapes)
- Debug.RemoveShape(shape);
- shapes.Clear();
- }
- #endif
- #endif
- }
|