public static void CreateItems(List <PurchasedItem> itemsToSpawn) { if (itemsToSpawn.Count == 0) { return; } WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); if (wp == null) { DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!"); return; } Hull cargoRoom = Hull.FindHull(wp.WorldPosition); if (cargoRoom == null) { DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); return; } #if CLIENT new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, true)); #endif Dictionary <ItemContainer, int> availableContainers = new Dictionary <ItemContainer, int>(); ItemPrefab containerPrefab = null; foreach (PurchasedItem pi in itemsToSpawn) { Vector2 position = new Vector2( Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20), cargoRoom.Rect.Y - cargoRoom.Rect.Height + pi.ItemPrefab.Size.Y / 2); ItemContainer itemContainer = null; if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier)) { itemContainer = availableContainers.Keys.ToList().Find(ac => ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier || ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())); if (itemContainer == null) { containerPrefab = MapEntityPrefab.List.Find(ep => ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier || (ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()))) as ItemPrefab; if (containerPrefab == null) { DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!"); continue; } Item containerItem = new Item(containerPrefab, position, wp.Submarine); itemContainer = containerItem.GetComponent <ItemContainer>(); if (itemContainer == null) { DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!"); continue; } availableContainers.Add(itemContainer, itemContainer.Capacity); #if SERVER if (GameMain.Server != null) { Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); } #endif } } for (int i = 0; i < pi.Quantity; i++) { if (itemContainer == null) { //no container, place at the waypoint #if SERVER if (GameMain.Server != null) { Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine); } else { #endif new Item(pi.ItemPrefab, position, wp.Submarine); #if SERVER } #endif continue; } //if the intial container has been removed due to it running out of space, add a new container //of the same type and begin filling it if (!availableContainers.ContainsKey(itemContainer)) { Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine); itemContainer = containerItemOverFlow.GetComponent <ItemContainer>(); availableContainers.Add(itemContainer, itemContainer.Capacity); #if SERVER if (GameMain.Server != null) { Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); } #endif } //place in the container #if SERVER if (GameMain.Server != null) { Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory); } else { #endif var item = new Item(pi.ItemPrefab, position, wp.Submarine); itemContainer.Inventory.TryPutItem(item, null); #if SERVER } #endif //reduce the number of available slots in the container //if there is a container if (availableContainers.ContainsKey(itemContainer)) { availableContainers[itemContainer]--; } if (availableContainers.ContainsKey(itemContainer) && availableContainers[itemContainer] <= 0) { availableContainers.Remove(itemContainer); } } } itemsToSpawn.Clear(); }
public static void CreateItems(List <PurchasedItem> itemsToSpawn) { if (itemsToSpawn.Count == 0) { return; } WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); if (wp == null) { DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!"); return; } Hull cargoRoom = Hull.FindHull(wp.WorldPosition); if (cargoRoom == null) { DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); return; } #if CLIENT new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, true), new string[0], type: GUIMessageBox.Type.InGame, iconStyle: "StoreShoppingCrateIcon"); #else foreach (Client client in GameMain.Server.ConnectedClients) { ChatMessage msg = ChatMessage.Create("", $"CargoSpawnNotification~[roomname]=§{cargoRoom.RoomName}", ChatMessageType.ServerMessageBoxInGame, null); msg.IconStyle = "StoreShoppingCrateIcon"; GameMain.Server.SendDirectChatMessage(msg, client); } #endif Dictionary <ItemContainer, int> availableContainers = new Dictionary <ItemContainer, int>(); ItemPrefab containerPrefab = null; foreach (PurchasedItem pi in itemsToSpawn) { float floorPos = cargoRoom.Rect.Y - cargoRoom.Rect.Height; Vector2 position = new Vector2( Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20), floorPos); //check where the actual floor structure is in case the bottom of the hull extends below it if (Submarine.PickBody( ConvertUnits.ToSimUnits(new Vector2(position.X, cargoRoom.Rect.Y - cargoRoom.Rect.Height / 2)), ConvertUnits.ToSimUnits(position), collisionCategory: Physics.CollisionWall) != null) { float floorStructurePos = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition.Y); if (floorStructurePos > floorPos) { floorPos = floorStructurePos; } } position.Y = floorPos + pi.ItemPrefab.Size.Y / 2; ItemContainer itemContainer = null; if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier)) { itemContainer = availableContainers.Keys.ToList().Find(ac => ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier || ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())); if (itemContainer == null) { containerPrefab = ItemPrefab.Prefabs.Find(ep => ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier || (ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()))); if (containerPrefab == null) { DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!"); continue; } Item containerItem = new Item(containerPrefab, position, wp.Submarine); itemContainer = containerItem.GetComponent <ItemContainer>(); if (itemContainer == null) { DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!"); continue; } availableContainers.Add(itemContainer, itemContainer.Capacity); #if SERVER if (GameMain.Server != null) { Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); } #endif } } for (int i = 0; i < pi.Quantity; i++) { if (itemContainer == null) { //no container, place at the waypoint if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine, onSpawned: itemSpawned); } else { var item = new Item(pi.ItemPrefab, position, wp.Submarine); itemSpawned(item); } continue; } //if the intial container has been removed due to it running out of space, add a new container //of the same type and begin filling it if (!availableContainers.ContainsKey(itemContainer)) { Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine); itemContainer = containerItemOverFlow.GetComponent <ItemContainer>(); availableContainers.Add(itemContainer, itemContainer.Capacity); #if SERVER if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false); } #endif } //place in the container if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory, onSpawned: itemSpawned); } else { var item = new Item(pi.ItemPrefab, position, wp.Submarine); itemContainer.Inventory.TryPutItem(item, null); itemSpawned(item); }
public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker) { if (attack.Range <= 0.0f) { return; } //long range for the broad distance check, because large characters may still be in range even if their collider isn't float broadRange = Math.Max(attack.Range * 10.0f, 10000.0f); foreach (Character c in Character.CharacterList) { if (!c.Enabled || Math.Abs(c.WorldPosition.X - worldPosition.X) > broadRange || Math.Abs(c.WorldPosition.Y - worldPosition.Y) > broadRange) { continue; } Vector2 explosionPos = worldPosition; if (c.Submarine != null) { explosionPos -= c.Submarine.Position; } Hull hull = Hull.FindHull(explosionPos, null, false); bool underWater = hull == null || explosionPos.Y < hull.Surface; explosionPos = ConvertUnits.ToSimUnits(explosionPos); Dictionary <Limb, float> distFactors = new Dictionary <Limb, float>(); Dictionary <Limb, float> damages = new Dictionary <Limb, float>(); List <Affliction> modifiedAfflictions = new List <Affliction>(); foreach (Limb limb in c.AnimController.Limbs) { if (limb.IsSevered || limb.ignoreCollisions) { continue; } float dist = Vector2.Distance(limb.WorldPosition, worldPosition); //calculate distance from the "outer surface" of the physics body //doesn't take the rotation of the limb into account, but should be accurate enough for this purpose float limbRadius = limb.body.GetMaxExtent(); dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius)); if (dist > attack.Range) { continue; } float distFactor = 1.0f - dist / attack.Range; //solid obstacles between the explosion and the limb reduce the effect of the explosion var obstacles = Submarine.PickBodies(limb.SimPosition, explosionPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall); foreach (var body in obstacles) { if (body.UserData is Item item) { var door = item.GetComponent <Door>(); if (door != null && !door.IsBroken) { distFactor *= 0.01f; } } else if (body.UserData is Structure structure) { int sectionIndex = structure.FindSectionIndex(worldPosition, world: true, clamp: true); if (structure.SectionBodyDisabled(sectionIndex)) { continue; } else if (structure.SectionIsLeaking(sectionIndex)) { distFactor *= 0.1f; } else { distFactor *= 0.01f; } } else { distFactor *= 0.1f; } } if (distFactor <= 0.05f) { continue; } distFactors.Add(limb, distFactor); modifiedAfflictions.Clear(); foreach (Affliction affliction in attack.Afflictions.Keys) { //previously the damage would be divided by the number of limbs (the intention was to prevent characters with more limbs taking more damage from explosions) //that didn't work well on large characters like molochs and endworms: the explosions tend to only damage one or two of their limbs, and since the characters //have lots of limbs, they tended to only take a fraction of the damage they should //now we just divide by 10, which keeps the damage to normal-sized characters roughly the same as before and fixes the large characters modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / 10)); } c.LastDamageSource = damageSource; if (attacker == null) { if (damageSource is Item item) { attacker = item.GetComponent <Projectile>()?.User; if (attacker == null) { attacker = item.GetComponent <MeleeWeapon>()?.User; } } } //use a position slightly from the limb's position towards the explosion //ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods Vector2 dir = worldPosition - limb.WorldPosition; Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f; AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker); damages.Add(limb, attackResult.Damage); if (attack.StatusEffects != null && attack.StatusEffects.Any()) { attack.SetUser(attacker); var statusEffectTargets = new List <ISerializableEntity>() { c, limb }; foreach (StatusEffect statusEffect in attack.StatusEffects) { statusEffect.Apply(ActionType.OnUse, 1.0f, damageSource, statusEffectTargets); statusEffect.Apply(ActionType.Always, 1.0f, damageSource, statusEffectTargets); statusEffect.Apply(underWater ? ActionType.InWater : ActionType.NotInWater, 1.0f, damageSource, statusEffectTargets); } } if (limb.WorldPosition != worldPosition && !MathUtils.NearlyEqual(force, 0.0f)) { Vector2 limbDiff = Vector2.Normalize(limb.WorldPosition - worldPosition); if (!MathUtils.IsValid(limbDiff)) { limbDiff = Rand.Vector(1.0f); } Vector2 impulse = limbDiff * distFactor * force; Vector2 impulsePoint = limb.SimPosition - limbDiff * limbRadius; limb.body.ApplyLinearImpulse(impulse, impulsePoint, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.2f); } } if (c == Character.Controlled && !c.IsDead) { Limb head = c.AnimController.GetLimb(LimbType.Head); if (damages.TryGetValue(head, out float headDamage) && headDamage > 0.0f && distFactors.TryGetValue(head, out float headFactor)) { PlayTinnitusProjSpecific(headFactor); } } //sever joints if (attack.SeverLimbsProbability > 0.0f) { foreach (Limb limb in c.AnimController.Limbs) { if (limb.character.Removed || limb.Removed) { continue; } if (limb.IsSevered) { continue; } if (!c.IsDead && !limb.CanBeSeveredAlive) { continue; } if (distFactors.TryGetValue(limb, out float distFactor)) { if (damages.TryGetValue(limb, out float damage)) { c.TrySeverLimbJoints(limb, attack.SeverLimbsProbability * distFactor, damage, allowBeheading: true); } } } } } }
public void Explode(Vector2 worldPosition, Entity damageSource, Character attacker = null) { prevExplosions.Add(new Triplet <Explosion, Vector2, float>(this, worldPosition, (float)Timing.TotalTime)); if (prevExplosions.Count > 100) { prevExplosions.RemoveAt(0); } Hull hull = Hull.FindHull(worldPosition); ExplodeProjSpecific(worldPosition, hull); if (hull != null && !string.IsNullOrWhiteSpace(decal) && decalSize > 0.0f) { hull.AddDecal(decal, worldPosition, decalSize, isNetworkEvent: false); } float displayRange = attack.Range; Vector2 cameraPos = Character.Controlled != null ? Character.Controlled.WorldPosition : GameMain.GameScreen.Cam.Position; float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f; GameMain.GameScreen.Cam.Shake = cameraShake * Math.Max((cameraShakeRange - cameraDist) / cameraShakeRange, 0.0f); #if CLIENT if (screenColor != Color.Transparent) { Color flashColor = Color.Lerp(Color.Transparent, screenColor, Math.Max((screenColorRange - cameraDist) / screenColorRange, 0.0f)); Screen.Selected.ColorFade(flashColor, Color.Transparent, screenColorDuration); } #endif if (displayRange < 0.1f) { return; } if (attack.GetStructureDamage(1.0f) > 0.0f) { RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f), attacker); } if (EmpStrength > 0.0f) { float displayRangeSqr = displayRange * displayRange; foreach (Item item in Item.ItemList) { float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition); if (distSqr > displayRangeSqr) { continue; } float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange; //damage repairable power-consuming items var powered = item.GetComponent <Powered>(); if (powered == null || !powered.VulnerableToEMP) { continue; } if (item.Repairables.Any()) { item.Condition -= item.MaxCondition * EmpStrength * distFactor; } //discharge batteries var powerContainer = item.GetComponent <PowerContainer>(); if (powerContainer != null) { powerContainer.Charge -= powerContainer.Capacity * EmpStrength * distFactor; } } } if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(attack.Stun, 0.0f) && MathUtils.NearlyEqual(attack.GetTotalDamage(false), 0.0f)) { return; } DamageCharacters(worldPosition, attack, force, damageSource, attacker); if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { foreach (Item item in Item.ItemList) { if (item.Condition <= 0.0f) { continue; } if (Vector2.Distance(item.WorldPosition, worldPosition) > attack.Range * 0.5f) { continue; } if (applyFireEffects && !item.FireProof) { //don't apply OnFire effects if the item is inside a fireproof container //(or if it's inside a container that's inside a fireproof container, etc) Item container = item.Container; bool fireProof = false; while (container != null) { if (container.FireProof) { fireProof = true; break; } container = container.Container; } if (!fireProof) { item.ApplyStatusEffects(ActionType.OnFire, 1.0f); if (item.Condition <= 0.0f && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFire }); } } } if (item.Prefab.DamagedByExplosions && !item.Indestructible) { float limbRadius = item.body == null ? 0.0f : item.body.GetMaxExtent(); float dist = Vector2.Distance(item.WorldPosition, worldPosition); dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius)); if (dist > attack.Range) { continue; } float distFactor = 1.0f - dist / attack.Range; float damageAmount = attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier; item.Condition -= damageAmount * distFactor; } } } }
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull) { if (shockwave) { GameMain.ParticleManager.CreateParticle("shockwave", worldPosition, Vector2.Zero, 0.0f, hull); } hull ??= Hull.FindHull(worldPosition, useWorldCoordinates: true); bool underwater = hull == null || worldPosition.Y < hull.WorldSurface; if (underwater && underwaterBubble) { var underwaterExplosion = GameMain.ParticleManager.CreateParticle("underwaterexplosion", worldPosition, Vector2.Zero, 0.0f, hull); if (underwaterExplosion != null) { underwaterExplosion.Size *= MathHelper.Clamp(attack.Range / 150.0f, 0.5f, 10.0f); underwaterExplosion.StartDelay = 0.0f; } } for (int i = 0; i < attack.Range * 0.1f; i++) { if (!underwater) { float particleSpeed = Rand.Range(0.0f, 1.0f); particleSpeed = particleSpeed * particleSpeed * attack.Range; if (flames) { float particleScale = MathHelper.Clamp(attack.Range * 0.0025f, 0.5f, 2.0f); var flameParticle = GameMain.ParticleManager.CreateParticle("explosionfire", ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, attack.Range))), hull), Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull); if (flameParticle != null) { flameParticle.Size *= particleScale; } } if (smoke) { GameMain.ParticleManager.CreateParticle(Rand.Range(0.0f, 1.0f) < 0.5f ? "explosionsmoke" : "smoke", ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, attack.Range))), hull), Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull); } } else if (underwaterBubble) { Vector2 bubblePos = Rand.Vector(Rand.Range(0.0f, attack.Range * 0.5f)); GameMain.ParticleManager.CreateParticle("risingbubbles", worldPosition + bubblePos, Vector2.Zero, 0.0f, hull); if (i < attack.Range * 0.02f) { var underwaterExplosion = GameMain.ParticleManager.CreateParticle("underwaterexplosion", worldPosition + bubblePos, Vector2.Zero, 0.0f, hull); if (underwaterExplosion != null) { underwaterExplosion.Size *= MathHelper.Clamp(attack.Range / 300.0f, 0.5f, 2.0f) * Rand.Range(0.8f, 1.2f); } } } if (sparks) { GameMain.ParticleManager.CreateParticle("spark", worldPosition, Rand.Vector(Rand.Range(500.0f, 800.0f)), 0.0f, hull); } } if (flash) { float displayRange = flashRange.HasValue ? flashRange.Value : attack.Range; if (displayRange < 0.1f) { return; } var light = new LightSource(worldPosition, displayRange, Color.LightYellow, null); CoroutineManager.StartCoroutine(DimLight(light)); } }