private void UpdateDepthDamage(float deltaTime) { #if CLIENT if (GameMain.GameSession?.GameMode is TestGameMode) { return; } #endif if (Level.Loaded == null) { return; } //camera shake and sounds start playing 500 meters before crush depth float depthEffectThreshold = 500.0f; if (Submarine.RealWorldDepth < Level.Loaded.RealWorldCrushDepth - depthEffectThreshold || Submarine.RealWorldDepth < Submarine.RealWorldCrushDepth - depthEffectThreshold) { return; } depthDamageTimer -= deltaTime; if (depthDamageTimer > 0.0f) { return; } #if CLIENT SoundPlayer.PlayDamageSound("pressure", Rand.Range(0.0f, 100.0f), submarine.WorldPosition + Rand.Vector(Rand.Range(0.0f, Math.Min(submarine.Borders.Width, submarine.Borders.Height))), 20000.0f); #endif foreach (Structure wall in Structure.WallList) { if (wall.Submarine != submarine) { continue; } float wallCrushDepth = wall.CrushDepth; if (submarine.Info.SubmarineClass == SubmarineClass.DeepDiver) { wallCrushDepth *= 1.2f; } float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth; if (pastCrushDepth > 0) { Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f, levelWallDamage: 0.0f); } if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, MathHelper.Clamp(pastCrushDepth * 0.001f, 1.0f, 50.0f)); } } depthDamageTimer = 10.0f; }
private void UpdateDepthDamage(float deltaTime) { #if CLIENT if (GameMain.GameSession?.GameMode is TestGameMode) { return; } #endif if (Level.Loaded == null) { return; } float submarineDepth = submarine.RealWorldDepth; if (!Submarine.AtDamageDepth) { return; } depthDamageTimer -= deltaTime; if (depthDamageTimer > 0.0f) { return; } foreach (Structure wall in Structure.WallList) { if (wall.Submarine != submarine) { continue; } float wallCrushDepth = wall.CrushDepth; if (submarine.Info.SubmarineClass == SubmarineClass.DeepDiver) { wallCrushDepth *= 1.2f; } float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth; if (pastCrushDepth < 0) { return; } Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f, levelWallDamage: 0.0f); if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(pastCrushDepth * 0.001f, 50.0f)); } } depthDamageTimer = 10.0f; }
private void HandleLevelCollision(Impact impact, VoronoiCell cell = null) { if (GameMain.GameSession != null && Timing.TotalTime < GameMain.GameSession.RoundStartTime + 10) { //ignore level collisions for the first 10 seconds of the round in case the sub spawns in a way that causes it to hit a wall //(e.g. level without outposts to dock to and an incorrectly configured ballast that makes the sub go up) return; } float wallImpact = Vector2.Dot(impact.Velocity, -impact.Normal); ApplyImpact(wallImpact, -impact.Normal, impact.ImpactPos); foreach (Submarine dockedSub in submarine.DockedTo) { dockedSub.SubBody.ApplyImpact(wallImpact, -impact.Normal, impact.ImpactPos); } if (cell != null && cell.IsDestructible && wallImpact > 0.0f) { var hitWall = Level.Loaded?.ExtraWalls.Find(w => w.Cells.Contains(cell)); if (hitWall != null && hitWall.WallDamageOnTouch > 0.0f) { var damagedStructures = Explosion.RangedStructureDamage( ConvertUnits.ToDisplayUnits(impact.ImpactPos), 500.0f, hitWall.WallDamageOnTouch, levelWallDamage: 0.0f); #if CLIENT PlayDamageSounds(damagedStructures, impact.ImpactPos, wallImpact, "StructureSlash"); #endif } } #if CLIENT int particleAmount = (int)Math.Min(wallImpact * 10.0f, 50); for (int i = 0; i < particleAmount; i++) { GameMain.ParticleManager.CreateParticle("iceshards", ConvertUnits.ToDisplayUnits(impact.ImpactPos) + Rand.Vector(Rand.Range(1.0f, 50.0f)), Rand.Vector(Rand.Range(50.0f, 500.0f)) + impact.Velocity); } #endif }
private void UpdateDepthDamage(float deltaTime) { if (Position.Y > DamageDepth) { return; } #if CLIENT if (GameMain.GameSession.GameMode is TestGameMode) { return; } #endif float depth = DamageDepth - Position.Y; depthDamageTimer -= deltaTime; if (depthDamageTimer > 0.0f) { return; } foreach (Structure wall in Structure.WallList) { if (wall.Submarine != submarine) { continue; } if (wall.Health < depth * 0.01f) { Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, depth * 0.01f); if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(depth * 0.001f, 50.0f)); } } } depthDamageTimer = 10.0f; }
private void ApplyImpact(float impact, Vector2 direction, Contact contact) { if (impact < 3.0f) { return; } Vector2 tempNormal; FixedArray2 <Vector2> worldPoints; contact.GetWorldManifold(out tempNormal, out worldPoints); Vector2 lastContactPoint = worldPoints[0]; if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { GameMain.GameScreen.Cam.Shake = impact * 2.0f; } Vector2 impulse = direction * impact * 0.5f; impulse = impulse.ClampLength(5.0f); foreach (Character c in Character.CharacterList) { if (c.Submarine != submarine) { continue; } if (impact > 2.0f) { c.SetStun((impact - 2.0f) * 0.1f); } foreach (Limb limb in c.AnimController.Limbs) { limb.body.ApplyLinearImpulse(limb.Mass * impulse, 20.0f); } c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 20.0f); } foreach (Item item in Item.ItemList) { if (item.Submarine != submarine || item.CurrentHull == null || item.body == null || !item.body.Enabled) { continue; } item.body.ApplyLinearImpulse(item.body.Mass * impulse, 20.0f); } var damagedStructures = Explosion.RangedStructureDamage(ConvertUnits.ToDisplayUnits(lastContactPoint), impact * 50.0f, impact * ImpactDamageMultiplier); #if CLIENT //play a damage sound for the structure that took the most damage float maxDamage = 0.0f; Structure maxDamageStructure = null; foreach (KeyValuePair <Structure, float> structureDamage in damagedStructures) { if (maxDamageStructure == null || structureDamage.Value > maxDamage) { maxDamage = structureDamage.Value; maxDamageStructure = structureDamage.Key; } } if (maxDamageStructure != null) { SoundPlayer.PlayDamageSound( "StructureBlunt", impact * 10.0f, ConvertUnits.ToDisplayUnits(lastContactPoint), MathHelper.Clamp(maxDamage * 4.0f, 1000.0f, 4000.0f), maxDamageStructure.Tags); } #endif }
public void Update(float deltaTime) { if (ParentTrigger != null && !ParentTrigger.IsTriggered) { return; } triggerers.RemoveWhere(t => t.Removed); bool isNotClient = true; #if CLIENT isNotClient = GameMain.Client == null; #endif if (!UseNetworkSyncing || isNotClient) { if (ForceFluctuationStrength > 0.0f) { //no need for force fluctuation (or network updates) if the trigger limits velocity and there are no triggerers if (forceMode != TriggerForceMode.LimitVelocity || triggerers.Any()) { forceFluctuationTimer += deltaTime; if (forceFluctuationTimer > ForceFluctuationInterval) { NeedsNetworkSyncing = true; currentForceFluctuation = Rand.Range(1.0f - ForceFluctuationStrength, 1.0f); forceFluctuationTimer = 0.0f; } } } if (randomTriggerProbability > 0.0f) { randomTriggerTimer += deltaTime; if (randomTriggerTimer > randomTriggerInterval) { if (Rand.Range(0.0f, 1.0f) < randomTriggerProbability) { NeedsNetworkSyncing = true; triggeredTimer = stayTriggeredDelay; } randomTriggerTimer = 0.0f; } } } if (stayTriggeredDelay > 0.0f) { if (triggerers.Count == 0) { triggeredTimer -= deltaTime; } else { triggeredTimer = stayTriggeredDelay; } } foreach (Entity triggerer in triggerers) { foreach (StatusEffect effect in statusEffects) { if (triggerer is Character) { effect.Apply(effect.type, deltaTime, triggerer, (Character)triggerer); } else if (triggerer is Item) { effect.Apply(effect.type, deltaTime, triggerer, ((Item)triggerer).AllPropertyObjects); } } if (triggerer is IDamageable damageable) { foreach (Attack attack in attacks) { attack.DoDamage(null, damageable, WorldPosition, deltaTime, false); } } else if (triggerer is Submarine submarine) { foreach (Attack attack in attacks) { float structureDamage = attack.GetStructureDamage(deltaTime); if (structureDamage > 0.0f) { Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage); } } } if (Force.LengthSquared() > 0.01f) { if (triggerer is Character character) { ApplyForce(character.AnimController.Collider, deltaTime); foreach (Limb limb in character.AnimController.Limbs) { ApplyForce(limb.body, deltaTime); } } else if (triggerer is Submarine submarine) { ApplyForce(submarine.SubBody.Body, deltaTime); } } if (triggerer == Character.Controlled || triggerer == Character.Controlled?.Submarine) { GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, cameraShake); } } }
private void ApplyImpact(float impact, Vector2 direction, Vector2 impactPos, bool applyDamage = true) { if (impact < MinCollisionImpact) { return; } Vector2 impulse = direction * impact * 0.5f; impulse = impulse.ClampLength(MaxCollisionImpact); if (!MathUtils.IsValid(impulse)) { string errorMsg = "Invalid impulse in SubmarineBody.ApplyImpact: " + impulse + ". Direction: " + direction + ", body position: " + Body.SimPosition + ", impact: " + impact + "."; if (GameMain.NetworkMember != null) { errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server."; } if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); } GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.ApplyImpact:InvalidImpulse", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } #if CLIENT if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { GameMain.GameScreen.Cam.Shake = impact * 2.0f; if (submarine.Info.Type == SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineType.Player)) { float angularVelocity = (impactPos.X - Body.SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y - (impactPos.Y - Body.SimPosition.Y) / ConvertUnits.ToSimUnits(submarine.Borders.Height / 2) * impulse.X; GameMain.GameScreen.Cam.AngularVelocity = MathHelper.Clamp(angularVelocity * 0.1f, -1.0f, 1.0f); } } #endif foreach (Character c in Character.CharacterList) { if (c.Submarine != submarine) { continue; } foreach (Limb limb in c.AnimController.Limbs) { if (limb.IsSevered) { continue; } limb.body.ApplyLinearImpulse(limb.Mass * impulse, 10.0f); } c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f); bool holdingOntoSomething = false; if (c.SelectedConstruction != null) { var controller = c.SelectedConstruction.GetComponent <Items.Components.Controller>(); holdingOntoSomething = controller != null && controller.LimbPositions.Any(); } //stun for up to 1 second if the impact equal or higher to the maximum impact if (impact >= MaxCollisionImpact && !holdingOntoSomething) { c.SetStun(Math.Min(impulse.Length() * 0.2f, 1.0f)); } } foreach (Item item in Item.ItemList) { if (item.Submarine != submarine || item.CurrentHull == null || item.body == null || !item.body.Enabled) { continue; } item.body.ApplyLinearImpulse(item.body.Mass * impulse, 10.0f); } var damagedStructures = Explosion.RangedStructureDamage( ConvertUnits.ToDisplayUnits(impactPos), impact * 50.0f, applyDamage ? impact * ImpactDamageMultiplier : 0.0f); #if CLIENT //play a damage sound for the structure that took the most damage float maxDamage = 0.0f; Structure maxDamageStructure = null; foreach (KeyValuePair <Structure, float> structureDamage in damagedStructures) { if (maxDamageStructure == null || structureDamage.Value > maxDamage) { maxDamage = structureDamage.Value; maxDamageStructure = structureDamage.Key; } } if (maxDamageStructure != null) { SoundPlayer.PlayDamageSound( "StructureBlunt", impact * 10.0f, ConvertUnits.ToDisplayUnits(impactPos), MathHelper.Lerp(2000.0f, 10000.0f, (impact - MinCollisionImpact) / 2.0f), maxDamageStructure.Tags); } #endif }
private void ApplyImpact(float impact, Vector2 direction, Vector2 impactPos, bool applyDamage = true) { if (impact < MinCollisionImpact) { return; } Vector2 impulse = direction * impact * 0.5f; impulse = impulse.ClampLength(MaxCollisionImpact); if (!MathUtils.IsValid(impulse)) { string errorMsg = "Invalid impulse in SubmarineBody.ApplyImpact: " + impulse + ". Direction: " + direction + ", body position: " + Body.SimPosition + ", impact: " + impact + "."; if (GameMain.NetworkMember != null) { errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server."; } if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); } GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.ApplyImpact:InvalidImpulse", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } #if CLIENT if (Character.Controlled != null && Character.Controlled.Submarine == submarine && Character.Controlled.KnockbackCooldownTimer <= 0.0f) { GameMain.GameScreen.Cam.Shake = Math.Max(impact * 10.0f, GameMain.GameScreen.Cam.Shake); if (submarine.Info.Type == SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineType.Player)) { float angularVelocity = (impactPos.X - Body.SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y - (impactPos.Y - Body.SimPosition.Y) / ConvertUnits.ToSimUnits(submarine.Borders.Height / 2) * impulse.X; GameMain.GameScreen.Cam.AngularVelocity = MathHelper.Clamp(angularVelocity * 0.1f, -1.0f, 1.0f); } } #endif foreach (Character c in Character.CharacterList) { if (c.Submarine != submarine) { continue; } if (c.KnockbackCooldownTimer > 0.0f) { continue; } c.KnockbackCooldownTimer = Character.KnockbackCooldown; foreach (Limb limb in c.AnimController.Limbs) { if (limb.IsSevered) { continue; } limb.body.ApplyLinearImpulse(limb.Mass * impulse, 10.0f); } bool holdingOntoSomething = false; if (c.SelectedConstruction != null) { holdingOntoSomething = c.SelectedConstruction.GetComponent <Ladder>() != null || (c.SelectedConstruction.GetComponent <Controller>()?.LimbPositions.Any() ?? false); } if (!holdingOntoSomething) { c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f); //stun for up to 2 second if the impact equal or higher to the maximum impact if (impact >= MaxCollisionImpact) { c.AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(3.0f).ToEnumerable(), stun: Math.Min(impulse.Length() * 0.2f, 2.0f), playSound: true); } } } foreach (Item item in Item.ItemList) { if (item.Submarine != submarine || item.CurrentHull == null || item.body == null || !item.body.Enabled) { continue; } item.body.ApplyLinearImpulse(item.body.Mass * impulse, 10.0f); item.PositionUpdateInterval = 0.0f; } float dmg = applyDamage ? impact * ImpactDamageMultiplier : 0.0f; var damagedStructures = Explosion.RangedStructureDamage( ConvertUnits.ToDisplayUnits(impactPos), impact * 50.0f, dmg, dmg); #if CLIENT PlayDamageSounds(damagedStructures, impactPos, impact, "StructureBlunt"); #endif }
public void Update(float deltaTime) { if (ParentTrigger != null && !ParentTrigger.IsTriggered) { return; } triggerers.RemoveWhere(t => t.Removed); if (PhysicsBody != null) { //failsafe to ensure triggerers get removed when they're far from the trigger float maxExtent = Math.Max(ConvertUnits.ToDisplayUnits(PhysicsBody.GetMaxExtent() * 5), 5000.0f); triggerers.RemoveWhere(t => { return(Vector2.Distance(t.WorldPosition, WorldPosition) > maxExtent); }); } bool isNotClient = true; #if CLIENT isNotClient = GameMain.Client == null; #endif if (!UseNetworkSyncing || isNotClient) { if (ForceFluctuationStrength > 0.0f) { //no need for force fluctuation (or network updates) if the trigger limits velocity and there are no triggerers if (forceMode != TriggerForceMode.LimitVelocity || triggerers.Any()) { forceFluctuationTimer += deltaTime; if (forceFluctuationTimer > ForceFluctuationInterval) { NeedsNetworkSyncing = true; currentForceFluctuation = Rand.Range(1.0f - ForceFluctuationStrength, 1.0f); forceFluctuationTimer = 0.0f; } } } if (randomTriggerProbability > 0.0f) { randomTriggerTimer += deltaTime; if (randomTriggerTimer > randomTriggerInterval) { if (Rand.Range(0.0f, 1.0f) < randomTriggerProbability) { NeedsNetworkSyncing = true; triggeredTimer = stayTriggeredDelay; } randomTriggerTimer = 0.0f; } } } if (stayTriggeredDelay > 0.0f) { if (triggerers.Count == 0) { triggeredTimer -= deltaTime; } else { triggeredTimer = stayTriggeredDelay; } } if (triggerOnce) { if (triggeredOnce) { return; } if (triggerers.Count > 0) { triggeredOnce = true; } } foreach (Entity triggerer in triggerers) { foreach (StatusEffect effect in statusEffects) { Vector2?position = null; if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = WorldPosition; } if (triggerer is Character character) { effect.Apply(effect.type, deltaTime, triggerer, character, position); if (effect.HasTargetType(StatusEffect.TargetType.Contained) && character.Inventory != null) { foreach (Item item in character.Inventory.AllItemsMod) { if (item.ContainedItems == null) { continue; } foreach (Item containedItem in item.ContainedItems) { effect.Apply(effect.type, deltaTime, triggerer, containedItem.AllPropertyObjects, position); } } } } else if (triggerer is Item item) { effect.Apply(effect.type, deltaTime, triggerer, item.AllPropertyObjects, position); } if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) { var targets = new List <ISerializableEntity>(); effect.GetNearbyTargets(worldPosition, targets); effect.Apply(effect.type, deltaTime, triggerer, targets); } } if (triggerer is IDamageable damageable) { foreach (Attack attack in attacks) { attack.DoDamage(null, damageable, WorldPosition, deltaTime, false); } } else if (triggerer is Submarine submarine) { foreach (Attack attack in attacks) { float structureDamage = attack.GetStructureDamage(deltaTime); if (structureDamage > 0.0f) { Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, levelWallDamage: 0.0f); } } if (!string.IsNullOrWhiteSpace(InfectIdentifier)) { submarine.AttemptBallastFloraInfection(InfectIdentifier, deltaTime, InfectionChance); } } if (Force.LengthSquared() > 0.01f) { if (triggerer is Character character) { ApplyForce(character.AnimController.Collider, deltaTime); foreach (Limb limb in character.AnimController.Limbs) { if (limb.IsSevered) { continue; } ApplyForce(limb.body, deltaTime); } } else if (triggerer is Submarine submarine) { ApplyForce(submarine.SubBody.Body, deltaTime); } } if (triggerer == Character.Controlled || triggerer == Character.Controlled?.Submarine) { GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, cameraShake); } } }
private void ApplyImpact(float impact, Vector2 direction, Contact contact, bool applyDamage = true) { float minImpact = 3.0f; if (impact < minImpact) { return; } contact.GetWorldManifold(out Vector2 tempNormal, out FixedArray2 <Vector2> worldPoints); Vector2 lastContactPoint = worldPoints[0]; Vector2 impulse = direction * impact * 0.5f; impulse = impulse.ClampLength(5.0f); if (!MathUtils.IsValid(impulse)) { string errorMsg = "Invalid impulse in SubmarineBody.ApplyImpact: " + impulse + ". Direction: " + direction + ", body position: " + Body.SimPosition + ", impact: " + impact + "."; if (GameMain.NetworkMember != null) { errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server."; } if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); } GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.ApplyImpact:InvalidImpulse", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } #if CLIENT if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { GameMain.GameScreen.Cam.Shake = impact * 2.0f; float angularVelocity = (lastContactPoint.X - Body.SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y - (lastContactPoint.Y - Body.SimPosition.Y) / ConvertUnits.ToSimUnits(submarine.Borders.Height / 2) * impulse.X; GameMain.GameScreen.Cam.AngularVelocity = MathHelper.Clamp(angularVelocity * 0.1f, -1.0f, 1.0f); } #endif foreach (Character c in Character.CharacterList) { if (c.Submarine != submarine) { continue; } if (impact > 2.0f) { c.SetStun((impact - 2.0f) * 0.1f); } foreach (Limb limb in c.AnimController.Limbs) { limb.body.ApplyLinearImpulse(limb.Mass * impulse, 20.0f); } c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 20.0f); } foreach (Item item in Item.ItemList) { if (item.Submarine != submarine || item.CurrentHull == null || item.body == null || !item.body.Enabled) { continue; } item.body.ApplyLinearImpulse(item.body.Mass * impulse, 20.0f); } var damagedStructures = Explosion.RangedStructureDamage( ConvertUnits.ToDisplayUnits(lastContactPoint), impact * 50.0f, applyDamage ? impact * ImpactDamageMultiplier : 0.0f); #if CLIENT //play a damage sound for the structure that took the most damage float maxDamage = 0.0f; Structure maxDamageStructure = null; foreach (KeyValuePair <Structure, float> structureDamage in damagedStructures) { if (maxDamageStructure == null || structureDamage.Value > maxDamage) { maxDamage = structureDamage.Value; maxDamageStructure = structureDamage.Key; } } if (maxDamageStructure != null) { SoundPlayer.PlayDamageSound( "StructureBlunt", impact * 10.0f, ConvertUnits.ToDisplayUnits(lastContactPoint), MathHelper.Lerp(2000.0f, 10000.0f, (impact - minImpact) / 2.0f), maxDamageStructure.Tags); } #endif }