private void Start(GameServer server, int traitorCount) { if (server == null) { return; } List <Character> characters = new List <Character>(); //ANYONE can be a target. List <Character> traitorCandidates = new List <Character>(); //Keep this to not re-pick traitors twice foreach (Client client in server.ConnectedClients) { if (client.Character != null) { characters.Add(client.Character); traitorCandidates.Add(client.Character); } } if (server.Character != null) { characters.Add(server.Character); //Add host character traitorCandidates.Add(server.Character); } if (characters.Count < 2) { return; } codeWords = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt); codeResponse = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt); while (traitorCount-- > 0) { if (traitorCandidates.Count <= 0) { break; } int traitorIndex = Rand.Int(traitorCandidates.Count); Character traitorCharacter = traitorCandidates[traitorIndex]; traitorCandidates.Remove(traitorCharacter); //Add them to the list traitorList.Add(new Traitor(traitorCharacter)); } //Now that traitors have been decided, let's do objectives in post for deciding things like Document Exchange. foreach (Traitor traitor in traitorList) { Character traitorCharacter = traitor.Character; int targetIndex = Rand.Int(characters.Count); while (characters[targetIndex] == traitorCharacter) //Cannot target self { targetIndex = Rand.Int(characters.Count); } Character targetCharacter = characters[targetIndex]; traitor.TargetCharacter = targetCharacter; traitor.Greet(server, codeWords, codeResponse); } }
public override void Apply(ActionType type, float deltaTime, Entity entity, List<ISerializableEntity> targets, Character causecharacter = null, string identifier = "") { if (this.type != type || !HasRequiredItems(entity)) return; if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.SequenceEqual(targets))) return; //remove invalid targets if (targetNames != null) { targets.RemoveAll(t => !targetNames.Contains(t.Name)); if (targets.Count == 0) return; } if (identifier == "") identifier = "statuseffect"; DelayedListElement element = new DelayedListElement { Parent = this, StartTimer = delay, Entity = entity, Targets = targets, causecharacter = causecharacter, identifier = identifier }; DelayList.Add(element); }
public Character TargetCharacter; //TODO: make a modular objective system (similar to crew missions) that allows for things OTHER than assasinations. public Traitor(Character character) { Character = character; }
public bool ShouldShowIcon(Character afflictedCharacter) { return(Strength >= (afflictedCharacter == Character.Controlled ? Prefab.ShowIconThreshold : Prefab.ShowIconToOthersThreshold)); }
public override void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Character causecharacter = null, string identifier = "") { if (this.type != type || !HasRequiredItems(entity)) return; if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.Count == 1 && d.Targets[0] == target)) return; if (targetNames != null && !targetNames.Contains(target.Name)) return; if (identifier == "") identifier = "statuseffect"; DelayedListElement element = new DelayedListElement { Parent = this, StartTimer = delay, Entity = entity, Targets = new List<ISerializableEntity>() { target }, causecharacter = causecharacter, identifier = identifier }; DelayList.Add(element); }
private bool IsCaptured(Character character) { return(character.LockHands && character.HasTeamChange(TerroristTeamChangeIdentifier)); }
private void InitEscort() { characters.Clear(); characterItems.Clear(); WayPoint explicitStayInHullPos = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); Rand.RandSync randSync = Rand.RandSync.Server; if (terroristChance > 0f) { // in terrorist missions, reroll characters each retry to avoid confusion as to who the terrorists are randSync = Rand.RandSync.Unsynced; } //if any of the escortees have a job defined, try to use a spawnpoint designated for that job foreach (XElement element in characterConfig.Elements()) { var humanPrefab = GetHumanPrefabFromElement(element); if (humanPrefab == null || string.IsNullOrEmpty(humanPrefab.Job) || humanPrefab.Job.Equals("any", StringComparison.OrdinalIgnoreCase)) { continue; } var jobPrefab = humanPrefab.GetJobPrefab(); if (jobPrefab != null) { var jobSpecificSpawnPos = WayPoint.GetRandom(SpawnType.Human, jobPrefab, Submarine.MainSub); if (jobSpecificSpawnPos != null) { explicitStayInHullPos = jobSpecificSpawnPos; break; } } } foreach (XElement element in characterConfig.Elements()) { int count = CalculateScalingEscortedCharacterCount(inMission: true); for (int i = 0; i < count; i++) { Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync); if (spawnedCharacter.AIController is HumanAIController humanAI) { humanAI.InitMentalStateManager(); } } } if (terroristChance > 0f) { int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count); terroristCount = Math.Clamp(terroristCount, 1, characters.Count); terroristCharacters.Clear(); characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c)); terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f); #if DEBUG DebugConsole.AddWarning("Terrorists will trigger at range " + Math.Sqrt(terroristDistanceSquared)); foreach (Character character in terroristCharacters) { DebugConsole.AddWarning(character.Name + " is a terrorist."); } #endif } }
private bool Survived(Character character) { return(IsAlive(character) && character.CurrentHull != null && character.CurrentHull.Submarine == Submarine.MainSub); }
private bool IsAlive(Character character) { return(character != null && !character.Removed && !character.IsDead); }
public void Update(float deltaTime) { var character = AiController.Character; if (character?.Removed ?? true || character.IsDead) { return; } if (unstunY.HasValue) { if (PlayTimer > 4.0f) { float extent = character.AnimController.MainLimb.body.GetMaxExtent(); if (character.SimPosition.Y < (unstunY.Value + extent * 3.0f) && character.AnimController.MainLimb.body.LinearVelocity.Y < 0.0f) { character.IsRagdolled = false; unstunY = null; } else { character.IsRagdolled = true; } } else { character.IsRagdolled = false; unstunY = null; } } PlayTimer -= deltaTime; if (GameMain.NetworkMember?.IsClient ?? false) { return; } if (Owner != null && (Owner.Removed || Owner.IsDead)) { Owner = null; } Hunger += HungerIncreaseRate * deltaTime; Happiness -= HappinessDecreaseRate * deltaTime; for (int i = 0; i < foods.Count; i++) { Food food = foods[i]; if (Hunger >= food.HungerRange.X && Hunger <= food.HungerRange.Y) { if (food.TargetParams == null && AiController.AIParams.TryAddNewTarget(food.Tag, AIState.Eat, food.Priority, out CharacterParams.TargetParams targetParams)) { targetParams.IgnoreContained = food.IgnoreContained; food.TargetParams = targetParams; } } else if (food.TargetParams != null) { AiController.AIParams.RemoveTarget(food.TargetParams); food.TargetParams = null; } } if (Hunger >= MaxHunger * 0.99f) { character.CharacterHealth.ApplyAffliction(character.AnimController.MainLimb, new Affliction(AfflictionPrefab.InternalDamage, 8.0f * deltaTime)); } else if (Hunger < MaxHunger * 0.1f) { character.CharacterHealth.ReduceAffliction(null, null, 8.0f * deltaTime); } if (character.SelectedBy != null) { character.IsRagdolled = true; unstunY = character.SimPosition.Y; } for (int i = 0; i < itemsToProduce.Count; i++) { itemsToProduce[i].Update(this, deltaTime); } }
partial void OnHealthChangedProjSpecific(Character attacker, float damageAmount) { GameMain.Server.KarmaManager.OnStructureHealthChanged(this, attacker, damageAmount); }
public void Update(float deltaTime) { if (Body.FarseerBody.IsStatic) { return; } ClientUpdatePosition(deltaTime); if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } //if outside left or right edge of the level if (Position.X < 0 || Position.X > Level.Loaded.Size.X) { Rectangle worldBorders = Borders; worldBorders.Location += MathUtils.ToPoint(Position); //push the sub back below the upper "barrier" of the level if (worldBorders.Y > Level.Loaded.Size.Y) { Body.LinearVelocity = new Vector2( Body.LinearVelocity.X, Math.Min(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.Size.Y - worldBorders.Y))); } else if (worldBorders.Y - worldBorders.Height < Level.Loaded.BottomPos) { Body.LinearVelocity = new Vector2( Body.LinearVelocity.X, Math.Max(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.BottomPos - (worldBorders.Y - worldBorders.Height)))); } } //------------------------- Vector2 totalForce = CalculateBuoyancy(); if (Body.LinearVelocity.LengthSquared() > 0.0001f) { //TODO: sync current drag with clients? float attachedMass = 0.0f; JointEdge jointEdge = Body.FarseerBody.JointList; while (jointEdge != null) { Body otherBody = jointEdge.Joint.BodyA == Body.FarseerBody ? jointEdge.Joint.BodyB : jointEdge.Joint.BodyA; Character character = (otherBody.UserData as Limb)?.character; if (character != null) { attachedMass += character.Mass; } jointEdge = jointEdge.Next; } float horizontalDragCoefficient = MathHelper.Clamp(HorizontalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag); totalForce.X -= Math.Sign(Body.LinearVelocity.X) * Body.LinearVelocity.X * Body.LinearVelocity.X * horizontalDragCoefficient * Body.Mass; float verticalDragCoefficient = MathHelper.Clamp(VerticalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag); totalForce.Y -= Math.Sign(Body.LinearVelocity.Y) * Body.LinearVelocity.Y * Body.LinearVelocity.Y * verticalDragCoefficient * Body.Mass; } ApplyForce(totalForce); UpdateDepthDamage(deltaTime); }
public void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null) { List<BallastFloraBehavior> ballastFlorae = new List<BallastFloraBehavior>(); foreach (Hull hull in Hull.hullList) { if (hull.BallastFlora != null) { ballastFlorae.Add(hull.BallastFlora); } } foreach (BallastFloraBehavior ballastFlora in ballastFlorae) { float resistanceMuliplier = ballastFlora.HasBrokenThrough ? 1f : 1f - ballastFlora.ExplosionResistance; ballastFlora.Branches.ForEachMod(branch => { Vector2 branchWorldPos = ballastFlora.GetWorldPosition() + branch.Position; float branchDist = Vector2.Distance(branchWorldPos, worldPosition); if (branchDist < worldRange) { float distFactor = 1.0f - (branchDist / worldRange); if (distFactor <= 0.0f) { return; } Vector2 explosionPos = worldPosition; Vector2 branchPos = branchWorldPos; if (ballastFlora.Parent?.Submarine != null) { explosionPos -= ballastFlora.Parent.Submarine.Position; branchPos -= ballastFlora.Parent.Submarine.Position; } distFactor *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, ConvertUnits.ToSimUnits(branchPos)); ballastFlora.DamageBranch(branch, damage * distFactor * resistanceMuliplier, BallastFloraBehavior.AttackType.Explosives, attacker); } }); } }
/// <summary> /// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken /// </summary> public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null) { List<Structure> structureList = new List<Structure>(); float dist = 600.0f; foreach (MapEntity entity in MapEntity.mapEntityList) { if (!(entity is Structure structure)) { continue; } if (structure.HasBody && !structure.IsPlatform && Vector2.Distance(structure.WorldPosition, worldPosition) < dist * 3.0f) { structureList.Add(structure); } } Dictionary<Structure, float> damagedStructures = new Dictionary<Structure, float>(); foreach (Structure structure in structureList) { for (int i = 0; i < structure.SectionCount; i++) { float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange); if (distFactor <= 0.0f) continue; structure.AddDamage(i, damage * distFactor, attacker); if (damagedStructures.ContainsKey(structure)) { damagedStructures[structure] += damage * distFactor; } else { damagedStructures.Add(structure, damage * distFactor); } } } if (Level.Loaded != null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f)) { for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--) { if (!(Level.Loaded.ExtraWalls[i] is DestructibleLevelWall destructibleWall)) { continue; } foreach (var cell in destructibleWall.Cells) { if (cell.IsPointInside(worldPosition)) { destructibleWall.AddDamage(levelWallDamage, worldPosition); continue; } foreach (var edge in cell.Edges) { if (!MathUtils.GetLineIntersection(worldPosition, cell.Center, edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, out Vector2 intersection)) { continue; } float wallDist = Vector2.DistanceSquared(worldPosition, intersection); if (wallDist < worldRange * worldRange) { destructibleWall.AddDamage(damage, worldPosition); break; } } } } } return damagedStructures; }
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 || !limb.body.Enabled) { 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 distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition); 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), attack.GetLevelWallDamage(1.0f), attacker); } if (BallastFloraDamage > 0.0f) { RangedBallastFloraDamage(worldPosition, displayRange, BallastFloraDamage, 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; } float dist = Vector2.Distance(item.WorldPosition, worldPosition); float itemRadius = item.body == null ? 0.0f : item.body.GetMaxExtent(); dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius)); if (dist > attack.Range) { continue; } if (dist < attack.Range * 0.5f && 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 distFactor = 1.0f - dist / attack.Range; float damageAmount = attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier; Vector2 explosionPos = worldPosition; if (item.Submarine != null) { explosionPos -= item.Submarine.Position; } damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.SimPosition); item.Condition -= damageAmount * distFactor; } } } }