diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs index d8d022397..9d3900b80 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs @@ -63,7 +63,10 @@ namespace Barotrauma { foreach (var ic in character.MemState[0].SelectedItem.Components) { - if (ic.CanBeSelected) ic.Select(character); + if (ic.CanBeSelected) + { + ic.Select(character); + } } } character.SelectedConstruction = character.MemState[0].SelectedItem; @@ -98,6 +101,16 @@ namespace Barotrauma if (distSqrd > 10.0f || !character.CanMove) { Collider.TargetRotation = newRotation; + if (distSqrd > 10.0f) + { + //teleported very far - see if we need to move to another sub + Hull serverHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(newPosition), CurrentHull, newPosition.Y < lowestSubPos); + if (currentHull != null && serverHull != null && serverHull.Submarine != currentHull.Submarine) + { + character.Submarine = serverHull.Submarine; + character.CurrentHull = CurrentHull = serverHull; + } + } SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false); } else @@ -194,7 +207,7 @@ namespace Barotrauma { if (character.SelectedConstruction != serverPos.SelectedItem) { - serverPos.SelectedItem.TryInteract(character, true, true); + serverPos.SelectedItem.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true); } character.SelectedConstruction = serverPos.SelectedItem; } @@ -448,8 +461,8 @@ namespace Barotrauma { DebugConsole.ThrowError("Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("Ragdoll.Draw:LimbsRemoved", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.ErrorSeverity.Error, + "Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.SpeciesName + "\", removed: " + character.Removed + "\n" + Environment.StackTrace.CleanupStackTrace()); return; } @@ -460,12 +473,17 @@ namespace Barotrauma } float depthOffset = GetDepthOffset(); + if (!MathUtils.NearlyEqual(depthOffset, 0.0f)) + { + foreach (Limb limb in limbs) { limb.ActiveSprite.Depth += depthOffset; } + } for (int i = 0; i < limbs.Length; i++) { - var limb = inversedLimbDrawOrder[i]; - if (depthOffset != 0.0f) { limb.ActiveSprite.Depth += depthOffset; } - limb.Draw(spriteBatch, cam, color); - if (depthOffset != 0.0f) { limb.ActiveSprite.Depth -= depthOffset; } + inversedLimbDrawOrder[i].Draw(spriteBatch, cam, color); + } + if (!MathUtils.NearlyEqual(depthOffset, 0.0f)) + { + foreach (Limb limb in limbs) { limb.ActiveSprite.Depth -= depthOffset; } } LimbJoints.ForEach(j => j.Draw(spriteBatch)); } @@ -486,7 +504,14 @@ namespace Barotrauma if (character.WorldPosition.X < character.SelectedConstruction.WorldPosition.X) { //at the left side of the ladder, needs to be drawn in front of the rungs - depthOffset = Math.Max(ladder.BackgroundSpriteDepth - 0.01f - maxDepth, 0.0f); + if (maxDepth > ladder.BackgroundSpriteDepth) + { + depthOffset = Math.Max(ladder.BackgroundSpriteDepth - 0.01f - maxDepth, 0.0f); + } + else + { + depthOffset = Math.Max(ladder.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth); + } } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index dd4873f66..62ebb226f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs @@ -305,9 +305,9 @@ namespace Barotrauma case 0: //NetEntityEvent.Type.InventoryState if (Inventory == null) { - string errorMsg = "Received an inventory update message for an entity with no inventory (" + Name + ", removed: " + Removed + ")"; - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + string errorMsg = "Received an inventory update message for an entity with no inventory ([name], removed: " + Removed + ")"; + DebugConsole.ThrowError(errorMsg.Replace("[name]", Name)); + GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace("[name]", SpeciesName)); //read anyway to prevent messing up reading the rest of the message _ = msg.ReadUInt16(); @@ -651,7 +651,7 @@ namespace Barotrauma { string errorMsg = $"Error in CharacterNetworking.ReadStatus: affliction not found ({afflictionName})"; causeOfDeathType = CauseOfDeathType.Unknown; - GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:AfflictionIndexOutOfBounts", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:AfflictionIndexOutOfBounts", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } else { @@ -682,7 +682,7 @@ namespace Barotrauma if (severedJointIndex < 0 || severedJointIndex >= AnimController.LimbJoints.Length) { string errorMsg = $"Error in CharacterNetworking.ReadStatus: severed joint index out of bounds (index: {severedJointIndex}, joint count: {AnimController.LimbJoints.Length})"; - GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:JointIndexOutOfBounts", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:JointIndexOutOfBounts", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index c2e1b8461..89578ed98 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -453,13 +453,14 @@ namespace Barotrauma { case Alignment.Left: healthWindow.RectTransform.SetPosition(Anchor.BottomLeft); + healthWindow.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.InventoryAreaLower.X, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding); break; case Alignment.Right: healthWindow.RectTransform.SetPosition(Anchor.BottomRight); + healthWindow.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.Padding, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding); break; } - healthWindow.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.Padding, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding); healthWindow.RectTransform.RecalculateChildren(false); } @@ -648,8 +649,9 @@ namespace Barotrauma grainColor = oxygenLowGrainColor; } - foreach (Affliction affliction in afflictions) + foreach (KeyValuePair kvp in afflictions) { + var affliction = kvp.Key; distortStrength = Math.Max(distortStrength, affliction.GetScreenDistortStrength()); blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength()); radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength()); @@ -662,16 +664,6 @@ namespace Barotrauma grainColor = Color.Lerp(grainColor, afflictionGrainColor, (float)Math.Pow(1.0f - oxygenLowStrength, 2)); } } - foreach (LimbHealth limbHealth in limbHealths) - { - foreach (Affliction affliction in limbHealth.Afflictions) - { - distortStrength = Math.Max(distortStrength, affliction.GetScreenDistortStrength()); - blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength()); - radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength()); - chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength()); - } - } Character.RadialDistortStrength = radialDistortStrength; Character.ChromaticAberrationStrength = chromaticAberrationStrength; @@ -777,7 +769,7 @@ namespace Barotrauma { // If no limb is selected or highlighted, select the one with the most critical afflictions. var affliction = SortAfflictionsBySeverity(GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None)).FirstOrDefault(); - if (affliction.DamagePerSecond > 0 || affliction.Strength > 0) + if (affliction != null && (affliction.DamagePerSecond > 0 || affliction.Strength > 0)) { var limbHealth = GetMatchingLimbHealth(affliction); if (limbHealth != null) @@ -788,7 +780,7 @@ namespace Barotrauma else { // If no affliction is critical, select the limb which has most damage. - var limbHealth = limbHealths.OrderByDescending(l => l.TotalDamage).FirstOrDefault(); + var limbHealth = limbHealths.OrderByDescending(l => GetTotalDamage(l)).FirstOrDefault(); selectedLimbIndex = limbHealths.IndexOf(limbHealth); } } @@ -971,8 +963,9 @@ namespace Barotrauma UpdateAlignment(); } - foreach (Affliction affliction in afflictions) + foreach (KeyValuePair kvp in afflictions) { + var affliction = kvp.Key; if (affliction.Prefab.AfflictionOverlay != null) { Sprite ScreenAfflictionOverlay = affliction.Prefab.AfflictionOverlay; @@ -984,7 +977,7 @@ namespace Barotrauma float damageOverlayAlpha = DamageOverlayTimer; if (Vitality < MaxVitality * 0.1f) { - damageOverlayAlpha = Math.Max(1.0f - (Vitality / maxVitality * 10.0f), damageOverlayAlpha); + damageOverlayAlpha = Math.Max(1.0f - (Vitality / UnmodifiedMaxVitality * 10.0f), damageOverlayAlpha); } else { @@ -1159,18 +1152,34 @@ namespace Barotrauma afflictionIconContainer.Content.ClearChildren(); return; } - var currentAfflictions = GetMatchingAfflictions(selectedLimb, a => a.ShouldShowIcon(Character)); - if (currentAfflictions.Any(a => !displayedAfflictions.Any(d => d.affliction == a)) || - displayedAfflictions.Any(a => !currentAfflictions.Contains(a.affliction))) + + if (afflictionsDirty()) { + var currentAfflictions = afflictions.Where(a => a.Value == selectedLimb && a.Key.ShouldShowIcon(Character)).Select(a => a.Key); CreateAfflictionInfos(currentAfflictions); CreateRecommendedTreatments(); } //update recommended treatments if the strength of some displayed affliction has changed by > 1 - else if (displayedAfflictions.Any(d => Math.Abs(d.strength - currentAfflictions.First(a => a == d.affliction).Strength) > 1.0f)) + else if (displayedAfflictions.Any(d => Math.Abs(d.strength - d.affliction.Strength) > 1.0f)) { CreateRecommendedTreatments(); } + + bool afflictionsDirty() + { + //not displaying one of the current afflictions -> dirty + foreach (KeyValuePair kvp in afflictions) + { + if (kvp.Value != selectedLimb || !kvp.Key.ShouldShowIcon(Character)) { continue; } + if (!displayedAfflictions.Any(d => d.affliction == kvp.Key)) { return true; } + } + //displaying an affliction we no longer have -> dirty + foreach ((Affliction affliction, float strength) in displayedAfflictions) + { + if (!afflictions.Any(a => a.Key == affliction)) { return true; } + } + return false; + } } private void CreateAfflictionInfos(IEnumerable afflictions) @@ -1604,7 +1613,7 @@ namespace Barotrauma int i = 0; foreach (LimbHealth limbHealth in limbHealths) { - if (limbHealth.IndicatorSprite == null) continue; + if (limbHealth.IndicatorSprite == null) { continue; } float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height); @@ -1623,6 +1632,7 @@ namespace Barotrauma } } + private static readonly List afflictionsDisplayedOnLimb = new List(); private void DrawHealthWindow(SpriteBatch spriteBatch, Rectangle drawArea, bool allowHighlight) { if (Character.Removed) { return; } @@ -1633,21 +1643,32 @@ namespace Barotrauma int i = 0; foreach (LimbHealth limbHealth in limbHealths) { - if (limbHealth.IndicatorSprite == null) continue; + if (limbHealth.IndicatorSprite == null) { continue; } Rectangle limbEffectiveArea = new Rectangle(limbHealth.IndicatorSprite.SourceRect.X + limbHealth.HighlightArea.X, limbHealth.IndicatorSprite.SourceRect.Y + limbHealth.HighlightArea.Y, limbHealth.HighlightArea.Width, limbHealth.HighlightArea.Height); - float damageLerp = limbHealth.TotalDamage > 0.0f ? MathHelper.Lerp(0.2f, 1.0f, limbHealth.TotalDamage / 100.0f) : 0.0f; + float totalDamage = GetTotalDamage(limbHealth); - var tempAfflictions = GetMatchingAfflictions(limbHealth, a => true); + float damageLerp = totalDamage > 0.0f ? MathHelper.Lerp(0.2f, 1.0f, totalDamage / 100.0f) : 0.0f; - float negativeEffect = tempAfflictions.Where(a => !a.Prefab.IsBuff && a.ShouldShowIcon(Character)).Sum(a => a.Strength); - //float negativeMaxEffect = tempAfflictions.Where(a => !a.Prefab.IsBuff).Sum(a => a.Prefab.MaxStrength); - float positiveEffect = tempAfflictions.Where(a => a.Prefab.IsBuff && a.ShouldShowIcon(Character)).Sum(a => a.Strength * 0.2f); - //float positiveMaxEffect = tempAfflictions.Where(a => a.Prefab.IsBuff).Sum(a => a.Prefab.MaxStrength); + float negativeEffect = 0.0f, positiveEffect = 0.0f; + foreach (KeyValuePair kvp in afflictions) + { + if (kvp.Value != limbHealth) { continue; } + var affliction = kvp.Key; + if (!affliction.ShouldShowIcon(Character)) { continue; } + if (!affliction.Prefab.IsBuff) + { + negativeEffect += affliction.Strength; + } + else + { + positiveEffect += affliction.Strength * 0.2f; + } + } float midPoint = (float)limbEffectiveArea.Center.Y / (float)limbHealth.IndicatorSprite.Texture.Height; float fadeDist = 0.6f * (float)limbEffectiveArea.Height / (float)limbHealth.IndicatorSprite.Texture.Height; @@ -1714,7 +1735,7 @@ namespace Barotrauma drawArea.Width / (float)limbIndicatorOverlay.FrameSize.X, drawArea.Height / (float)limbIndicatorOverlay.FrameSize.Y); - int frame = 0; + int frame; int frameCount = 17; if (limbIndicatorOverlayAnimState >= frameCount * 2) limbIndicatorOverlayAnimState = 0.0f; if (limbIndicatorOverlayAnimState < frameCount) @@ -1758,14 +1779,28 @@ namespace Barotrauma i = 0; foreach (LimbHealth limbHealth in limbHealths) { - IEnumerable thisAfflictions = limbHealth.Afflictions.Where(a => a.ShouldShowIcon(Character)); - thisAfflictions = thisAfflictions.Concat(afflictions.Where(a => + bool shouldDisplayAffliction(KeyValuePair kvp, LimbHealth limbHealth) { - Limb indicatorLimb = Character.AnimController.GetLimb(a.Prefab.IndicatorLimb); - return indicatorLimb != null && indicatorLimb.HealthIndex == i && a.ShouldShowIcon(Character); - })); + if (!kvp.Key.ShouldShowIcon(Character)) { return false; } + if (kvp.Value == limbHealth) + { + return true; + } + else if (kvp.Value == null) + { + Limb indicatorLimb = Character.AnimController.GetLimb(kvp.Key.Prefab.IndicatorLimb); + return indicatorLimb != null && indicatorLimb.HealthIndex == i; + } + return false; + } - if (thisAfflictions.Count() <= 0) { i++; continue; } + afflictionsDisplayedOnLimb.Clear(); + foreach (var affliction in afflictions) + { + if (shouldDisplayAffliction(affliction, limbHealth)) { afflictionsDisplayedOnLimb.Add(affliction.Key); } + } + + if (!afflictionsDisplayedOnLimb.Any()) { i++; continue; } if (limbHealth.IndicatorSprite == null) { continue; } float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height); @@ -1776,12 +1811,12 @@ namespace Barotrauma Vector2 iconPos = highlightArea.Center.ToVector2(); //Affliction mostSevereAffliction = thisAfflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !thisAfflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? thisAfflictions.FirstOrDefault(); - Affliction mostSevereAffliction = SortAfflictionsBySeverity(thisAfflictions, excludeBuffs: false).FirstOrDefault(); + Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictionsDisplayedOnLimb, excludeBuffs: false).FirstOrDefault(); if (mostSevereAffliction != null) { DrawLimbAfflictionIcon(spriteBatch, mostSevereAffliction, iconScale, ref iconPos); } - if (thisAfflictions.Count() > 1) + if (afflictionsDisplayedOnLimb.Count() > 1) { - string additionalAfflictionCount = $"+{thisAfflictions.Count() - 1}"; + string additionalAfflictionCount = $"+{afflictionsDisplayedOnLimb.Count() - 1}"; Vector2 displace = GUI.SubHeadingFont.MeasureString(additionalAfflictionCount); GUI.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X * 1.1f, -displace.Y * 0.45f), Color.Black * 0.75f); GUI.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X, -displace.Y * 0.5f), Color.White); @@ -1834,8 +1869,7 @@ namespace Barotrauma healthBarHolder.Visible = value; } - private readonly List<(AfflictionPrefab afflictionPrefab, float strength)> newAfflictions = new List<(AfflictionPrefab afflictionPrefab, float strength)>(); - private readonly List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)> newLimbAfflictions = new List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)>(); + private readonly List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)> newAfflictions = new List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)>(); private readonly List<(AfflictionPrefab.PeriodicEffect effect, float timer)> newPeriodicEffects = new List<(AfflictionPrefab.PeriodicEffect effect, float timer)>(); public void ClientRead(IReadMessage inc) @@ -1865,47 +1899,9 @@ namespace Barotrauma float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8); newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); } - newAfflictions.Add((afflictionPrefab, afflictionStrength)); + newAfflictions.Add((null, afflictionPrefab, afflictionStrength)); } - foreach (Affliction affliction in afflictions) - { - //deactivate afflictions that weren't included in the network message - if (!newAfflictions.Any(a => a.afflictionPrefab == affliction.Prefab)) - { - affliction.Strength = 0.0f; - } - } - - foreach (var (afflictionPrefab, strength) in newAfflictions) - { - Affliction existingAffliction = afflictions.Find(a => a.Prefab == afflictionPrefab); - if (existingAffliction == null) - { - existingAffliction = afflictionPrefab.Instantiate(strength); - afflictions.Add(existingAffliction); - } - existingAffliction.SetStrength(strength); - if (existingAffliction == stunAffliction) - { - Character.SetStun(existingAffliction.Strength, true, true); - } - foreach (var periodicEffect in newPeriodicEffects) - { - if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; } - //timer has wrapped around, apply the effect - if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2) - { - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer; - foreach (StatusEffect effect in periodicEffect.effect.StatusEffects) - { - existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: null); - } - } - } - } - - newLimbAfflictions.Clear(); byte limbAfflictionCount = inc.ReadByte(); for (int i = 0; i < limbAfflictionCount; i++) { @@ -1931,43 +1927,50 @@ namespace Barotrauma float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8); newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); } - newLimbAfflictions.Add((limbHealths[limbIndex], afflictionPrefab, afflictionStrength)); + newAfflictions.Add((limbHealths[limbIndex], afflictionPrefab, afflictionStrength)); } - foreach (LimbHealth limbHealth in limbHealths) + foreach (KeyValuePair kvp in afflictions) { - foreach (Affliction affliction in limbHealth.Afflictions) + //deactivate afflictions that weren't included in the network message + if (!newAfflictions.Any(a => kvp.Key.Prefab == a.afflictionPrefab && kvp.Value == a.limb)) { - //deactivate afflictions that weren't included in the network message - if (!newLimbAfflictions.Any(a => a.limb == limbHealth && a.afflictionPrefab == affliction.Prefab)) + kvp.Key.Strength = 0.0f; + } + } + + foreach (var (limb, afflictionPrefab, strength) in newAfflictions) + { + Affliction existingAffliction = null; + foreach (KeyValuePair kvp in afflictions) + { + if (kvp.Key.Prefab == afflictionPrefab && kvp.Value == limb) { - affliction.Strength = 0.0f; + existingAffliction = kvp.Key; + break; } } - - foreach (var (limb, afflictionPrefab, strength) in newLimbAfflictions) + if (existingAffliction == null) { - if (limb != limbHealth) { continue; } - Affliction existingAffliction = limbHealth.Afflictions.Find(a => a.Prefab == afflictionPrefab); - if (existingAffliction == null) + existingAffliction = afflictionPrefab.Instantiate(strength); + afflictions.Add(existingAffliction, limb); + } + existingAffliction.SetStrength(strength); + if (existingAffliction == stunAffliction) + { + Character.SetStun(existingAffliction.Strength, true, true); + } + foreach (var periodicEffect in newPeriodicEffects) + { + if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; } + //timer has wrapped around, apply the effect + if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2) { - existingAffliction = afflictionPrefab.Instantiate(strength); - limbHealth.Afflictions.Add(existingAffliction); - } - existingAffliction.SetStrength(strength); - - foreach (var periodicEffect in newPeriodicEffects) - { - if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; } - //timer has wrapped around, apply the effect - if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2) + existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer; + foreach (StatusEffect effect in periodicEffect.effect.StatusEffects) { - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer; - foreach (StatusEffect effect in periodicEffect.effect.StatusEffects) - { - Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(limb)); - existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb); - } + Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(limb)); + existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb); } } } @@ -1985,14 +1988,13 @@ namespace Barotrauma limb.BurnOverlayStrength = 0.0f; limb.DamageOverlayStrength = 0.0f; - if (limbHealths[limb.HealthIndex].Afflictions.Count == 0) continue; - foreach (Affliction a in limbHealths[limb.HealthIndex].Afflictions) + foreach (KeyValuePair kvp in afflictions) { - limb.BurnOverlayStrength += a.Strength / Math.Min(a.Prefab.MaxStrength, 100) * a.Prefab.BurnOverlayAlpha; - limb.DamageOverlayStrength += a.Strength / Math.Min(a.Prefab.MaxStrength, 100) * a.Prefab.DamageOverlayAlpha; + if (kvp.Value != limbHealths[limb.HealthIndex]) { continue; } + var affliction = kvp.Key; + limb.BurnOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha; + limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha; } - limb.BurnOverlayStrength /= limbHealths[limb.HealthIndex].Afflictions.Count; - limb.DamageOverlayStrength /= limbHealths[limb.HealthIndex].Afflictions.Count; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 7c6c7965d..14119e426 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -553,6 +553,12 @@ namespace Barotrauma levelGenerationParams = LevelGenerationParams.LevelParams.FirstOrDefault(p => p.Identifier == levelGenerationIdentifier); } + if (SubmarineInfo.SavedSubmarines.None(s => s.Name.ToLowerInvariant() == subName.ToLowerInvariant())) + { + ThrowError($"Cannot find a sub that matches the name \"{subName}\"."); + return; + } + GameMain.MainMenuScreen.QuickStart(fixedSeed: false, subName, difficulty, levelGenerationParams); }, getValidArgs: () => new[] { SubmarineInfo.SavedSubmarines.Select(s => s.Name).Distinct().ToArray() })); @@ -1461,10 +1467,11 @@ namespace Barotrauma { foreach (ItemPrefab ingredientItemPrefab in ingredient.ItemPrefabs) { - NewMessage(" Its ingredient " + ingredientItemPrefab.Name + " has base cost " + ingredientItemPrefab.DefaultPrice.Price); - totalPrice += ingredientItemPrefab.DefaultPrice.Price; + int defaultPrice = ingredientItemPrefab.DefaultPrice?.Price ?? 0; + NewMessage(" Its ingredient " + ingredientItemPrefab.Name + " has base cost " + defaultPrice); + totalPrice += defaultPrice; totalBestPrice += ingredientItemPrefab.GetMinPrice(); - int basePrice = ingredientItemPrefab.DefaultPrice.Price; + int basePrice = defaultPrice; foreach (KeyValuePair ingredientItemLocationPrice in ingredientItemPrefab.GetBuyPricesUnder()) { if (basePrice > ingredientItemLocationPrice.Value.Price) @@ -1630,7 +1637,7 @@ namespace Barotrauma var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == parentItem); int totalValue = 0; - NewMessage(parentItem.Name + " has the price " + parentItem.DefaultPrice.Price); + NewMessage(parentItem.Name + " has the price " + (parentItem.DefaultPrice?.Price ?? 0)); if (fabricationRecipe != null) { NewMessage(" It constructs from:"); @@ -1639,8 +1646,9 @@ namespace Barotrauma { foreach (ItemPrefab itemPrefab in requiredItem.ItemPrefabs) { - NewMessage(" " + itemPrefab.Name + " has the price " + itemPrefab.DefaultPrice.Price); - totalValue += itemPrefab.DefaultPrice.Price; + int defaultPrice = itemPrefab.DefaultPrice?.Price ?? 0; + NewMessage(" " + itemPrefab.Name + " has the price " + defaultPrice); + totalValue += defaultPrice; } } NewMessage("Its total value was: " + totalValue); @@ -1651,10 +1659,16 @@ namespace Barotrauma { ItemPrefab itemPrefab = (MapEntityPrefab.Find(deconstructItem.ItemIdentifier, identifier: null, showErrorMessages: false) ?? - MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab; + MapEntityPrefab.Find(null, identifier: deconstructItem.ItemIdentifier, showErrorMessages: false)) as ItemPrefab; + if (itemPrefab == null) + { + ThrowError($" Couldn't find deconstruct product \"{deconstructItem.ItemIdentifier}\"!"); + continue; + } - NewMessage(" " + itemPrefab.Name + " has the price " + itemPrefab.DefaultPrice.Price); - totalValue += itemPrefab.DefaultPrice.Price; + int defaultPrice = itemPrefab.DefaultPrice?.Price ?? 0; + NewMessage(" " + itemPrefab.Name + " has the price " + defaultPrice); + totalValue += defaultPrice; } NewMessage("Its deconstruct value was: " + totalValue); @@ -2485,8 +2499,6 @@ namespace Barotrauma NewMessage("Resolution set to 0 x 0 (screen resolution will be used)", Color.Green); NewMessage("Fullscreen enabled", Color.Green); - GameSettings.ShowUserStatisticsPrompt = true; - GameSettings.VerboseLogging = false; if (GameMain.Config.MasterServerUrl != "http://www.undertowgames.com/baromaster") @@ -3162,7 +3174,7 @@ namespace Barotrauma { string errorMsg = "Failed to spawn a submarine. Arguments: \"" + string.Join(" ", args) + "\"."; ThrowError(errorMsg, e); - GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnSubmarine:Error", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + '\n' + e.Message + '\n' + e.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnSubmarine:Error", GameAnalyticsManager.ErrorSeverity.Error, errorMsg + '\n' + e.Message + '\n' + e.StackTrace.CleanupStackTrace()); } }, () => diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs index abe80ac1c..6e3ae862d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs @@ -363,7 +363,7 @@ namespace Barotrauma OnSecondaryClicked = (_, o) => { if (!(o is Client client)) { return false; } - GameMain.GameSession?.CrewManager?.CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client); + NetLobbyScreen.CreateModerationContextMenu(client); return true; }, Text = senderName @@ -397,6 +397,7 @@ namespace Barotrauma if (GameMain.NetLobbyScreen != null && GameMain.NetworkMember != null) { clickableArea.OnClick = GameMain.NetLobbyScreen.SelectPlayer; + clickableArea.OnSecondaryClick = GameMain.NetLobbyScreen.ShowPlayerContextMenu; } msgText.ClickableAreas.Add(clickableArea); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index 51c0b2d04..7d932bcc9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -79,6 +79,8 @@ namespace Barotrauma public bool AllowMouseWheelScroll { get; set; } = true; + public bool AllowArrowKeyScroll { get; set; } = true; + /// /// Scrolls the list smoothly /// @@ -1254,10 +1256,16 @@ namespace Barotrauma switch (key) { case Keys.Down: - SelectNext(); + if (!isHorizontal && AllowArrowKeyScroll) { SelectNext(); } break; case Keys.Up: - SelectPrevious(); + if (!isHorizontal && AllowArrowKeyScroll) { SelectPrevious(); } + break; + case Keys.Left: + if (isHorizontal && AllowArrowKeyScroll) { SelectPrevious(); } + break; + case Keys.Right: + if (isHorizontal && AllowArrowKeyScroll) { SelectNext(); } break; case Keys.Enter: case Keys.Space: diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs index 33f42b55d..0fe0f0675 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs @@ -30,7 +30,7 @@ namespace Barotrauma { GameAnalyticsManager.AddErrorEventOnce( "GUIProgressBar.BarSize_setter", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Attempted to set the BarSize of a GUIProgressBar to an invalid value (" + value + ")\n" + Environment.StackTrace.CleanupStackTrace()); return; } @@ -105,7 +105,7 @@ namespace Barotrauma { GameAnalyticsManager.AddErrorEventOnce( "GUIProgressBar.Draw:GetProgress", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "ProgressGetter of a GUIProgressBar (" + ProgressGetter.Target.ToString() + " - " + ProgressGetter.Method.ToString() + ") returned an invalid value (" + newSize + ")\n" + Environment.StackTrace.CleanupStackTrace()); } else diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs index fbd7b2c77..6dc1d5f03 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs @@ -276,6 +276,7 @@ namespace Barotrauma public delegate void OnClickDelegate(GUITextBlock textBlock, ClickableArea area); public OnClickDelegate OnClick; + public OnClickDelegate OnSecondaryClick; } public List ClickableAreas { get; private set; } = new List(); @@ -528,7 +529,7 @@ namespace Barotrauma { base.Update(deltaTime); - if (ClickableAreas.Any() && (GUI.MouseOn?.IsParentOf(this) ?? true)) + if (ClickableAreas.Any() && ((GUI.MouseOn?.IsParentOf(this) ?? true) || GUI.MouseOn == this)) { if (!Rect.Contains(PlayerInput.MousePosition)) { return; } int index = GetCaretIndexFromScreenPos(PlayerInput.MousePosition); @@ -541,6 +542,10 @@ namespace Barotrauma { clickableArea.OnClick?.Invoke(this, clickableArea); } + if (PlayerInput.SecondaryMouseButtonClicked()) + { + clickableArea.OnSecondaryClick?.Invoke(this, clickableArea); + } break; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index ee5cefd50..a5554ebf2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -562,7 +562,7 @@ namespace Barotrauma frame.OnSecondaryClicked += (component, data) => { - GameMain.GameSession?.CrewManager?.CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client); + NetLobbyScreen.CreateModerationContextMenu(client); return true; }; @@ -917,7 +917,8 @@ namespace Barotrauma textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea() { Data = data, - OnClick = GameMain.NetLobbyScreen.SelectPlayer + OnClick = GameMain.NetLobbyScreen.SelectPlayer, + OnSecondaryClick = GameMain.NetLobbyScreen.ShowPlayerContextMenu }); } } @@ -1486,12 +1487,13 @@ namespace Barotrauma experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft), barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: GUI.Style.Green) { - IsHorizontal = true + IsHorizontal = true, }; experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight) { - Shadow = true + Shadow = true, + ToolTip = TextManager.Get("experiencetooltip") }; talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUI.SubHeadingFont, parseRichText: true, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true }; @@ -1643,6 +1645,7 @@ namespace Barotrauma GameMain.Client.CreateEntityEvent(controlledCharacter, new object[] { NetEntityEvent.Type.UpdateTalents }); } } + selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); UpdateTalentButtons(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index 9f9234a7e..1994a8d59 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -846,23 +846,27 @@ namespace Barotrauma var currentOrPending = item.PendingItemSwap ?? item.Prefab; string name = currentOrPending.Name; - string quantityText = ""; + string nameWithQuantity = ""; if (linkedItems.Count > 1) { foreach (ItemPrefab distinctItem in linkedItems.Select(it => it.Prefab).Distinct()) { - if (quantityText != string.Empty) + if (nameWithQuantity != string.Empty) { - quantityText += ", "; + nameWithQuantity += ", "; } int count = linkedItems.Count(it => it.Prefab == distinctItem); - quantityText += distinctItem.Name; + nameWithQuantity += distinctItem.Name; if (count > 1) { - quantityText += " " + TextManager.GetWithVariable("campaignstore.quantity", "[amount]", count.ToString()); + nameWithQuantity += " " + TextManager.GetWithVariable("campaignstore.quantity", "[amount]", count.ToString()); } } } + else + { + nameWithQuantity = name; + } bool isOpen = false; GUIButton toggleButton = new GUIButton(rectT(1f, 0.1f, parent.Content), text: string.Empty, style: "SlideDown") @@ -884,7 +888,7 @@ namespace Barotrauma new GUITextBlock(rectT(0.3f, 1f, buttonLayout), text: slotText, font: GUI.SubHeadingFont); GUILayoutGroup group = new GUILayoutGroup(rectT(0.7f, 1f, buttonLayout), isHorizontal: true) { Stretch = true }; - string title = item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : quantityText; + string title = item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : nameWithQuantity; GUITextBlock text = new GUITextBlock(rectT(0.7f, 1f, group), text: title, font: GUI.SubHeadingFont, textAlignment: Alignment.Right, parseRichText: true) { TextColor = GUI.Style.Orange @@ -907,7 +911,7 @@ namespace Barotrauma if (isUninstallPending) { canUninstall = false; } frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), currentOrPending.UpgradePreviewSprite, - item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : TextManager.GetWithVariable("upgrades.installeditem", "[itemname]", quantityText), + item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : TextManager.GetWithVariable("upgrades.installeditem", "[itemname]", nameWithQuantity), currentOrPending.Description, 0, null, addBuyButton: canUninstall, addProgressBar: false, buttonStyle: "WeaponUninstallButton")); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameAnalytics/GameAnalyticsManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameAnalytics/GameAnalyticsManager.cs new file mode 100644 index 000000000..b8663977e --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GameAnalytics/GameAnalyticsManager.cs @@ -0,0 +1,114 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Barotrauma.IO; +using Microsoft.Xna.Framework; + +namespace Barotrauma +{ + public static partial class GameAnalyticsManager + { + static partial void CreateConsentPrompt() + { + if (consentTextAvailable) + { + var background = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker"); + var frame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.7f), background.RectTransform, Anchor.Center) { MaxSize = new Point(800, int.MaxValue) }); + + var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), frame.RectTransform, Anchor.Center)) + { + Stretch = true, + AbsoluteSpacing = GUI.IntScale(15) + }; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.Get("statisticsconsentheader"), font: GUI.SubHeadingFont, textColor: Color.White); + var mainText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.Get("statisticsconsenttext"), wrap: true, parseRichText: true); + + foreach (var data in mainText.RichTextData) + { + mainText.ClickableAreas.Add(new GUITextBlock.ClickableArea() + { + Data = data, + OnClick = (GUITextBlock component, GUITextBlock.ClickableArea area) => + { + GameMain.Instance.ShowOpenUrlInWebBrowserPrompt("https://gameanalytics.com/privacy/"); + } + }); + } + + string privacyPolicyText = File.ReadAllText("daedalic_privacypolicy.txt"); + var privacyPolicyBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform) { MaxSize = new Point(int.MaxValue, GUI.IntScale(200)) }); + var privacyPolicy = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), privacyPolicyBox.Content.RectTransform), privacyPolicyText, wrap: true) + { + CanBeFocused = false + }; + privacyPolicy.RectTransform.MinSize = new Point(0, (int)privacyPolicy.TextSize.Y); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.Get("statisticsconsentstatement"), wrap: true); + + var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), isHorizontal: true); + + void buttonContainerSpacing(float width) + => new GUIFrame(new RectTransform(new Vector2(width, 1.0f), buttonContainer.RectTransform), style: null); + + buttonContainerSpacing(0.1f); + var yesBtn = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("Yes")); + yesBtn.OnClicked += (btn, userdata) => + { + GUIMessageBox.MessageBoxes.Remove(background); + SetConsentInternal(Consent.Yes); + return true; + }; + yesBtn.Enabled = false; + + IEnumerable enableAfterTime(WaitForSeconds time, params GUIComponent[] components) + { + yield return time; + foreach (var c in components) + { + c.Enabled = true; + } + yield return CoroutineStatus.Success; + } + + buttonContainerSpacing(0.2f); + + var noBtn = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("No")); + noBtn.OnClicked += (btn, userdata) => + { + GUIMessageBox.MessageBoxes.Remove(background); + SetConsent(Consent.No); + return true; + }; + noBtn.Enabled = false; + + CoroutineManager.StartCoroutine(enableAfterTime(new WaitForSeconds(0.3f), yesBtn, noBtn)); + + buttonContainerSpacing(0.1f); + + buttonContainer.RectTransform.MinSize = new Point(0, yesBtn.RectTransform.MinSize.Y); + buttonContainer.RectTransform.MaxSize = new Point(int.MaxValue, yesBtn.RectTransform.MinSize.Y); + + foreach (var child in content.Children) + { + if (child is GUITextBlock textBlock) + { + textBlock.RectTransform.MinSize = new Point(0, (int)textBlock.TextSize.Y); + textBlock.RectTransform.MaxSize = new Point(int.MaxValue, (int)textBlock.TextSize.Y + GUI.IntScale(15)); + } + } + + frame.RectTransform.MaxSize = new Point( + frame.RectTransform.MaxSize.X, + (int)(content.Children.Sum(c => c.RectTransform.MaxSize.Y + content.AbsoluteSpacing) / content.RectTransform.RelativeSize.Y)); + + GUIMessageBox.MessageBoxes.Add(background); + } + else + { + //user statistics disabled by default if the prompt cannot be shown in the user's language + SetConsent(Consent.Unknown); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 9d4e6e7e7..b64d30d04 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -11,7 +11,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; -using GameAnalyticsSDK.Net; using Barotrauma.IO; using System.Threading; using Barotrauma.Tutorials; @@ -384,51 +383,6 @@ namespace Barotrauma loadingCoroutine = CoroutineManager.StartCoroutine(Load(canLoadInSeparateThread), "Load", canLoadInSeparateThread); } - private void InitUserStats() - { - return; - - if (GameSettings.ShowUserStatisticsPrompt) - { - if (TextManager.ContainsTag("statisticspromptheader") && TextManager.ContainsTag("statisticsprompttext")) - { - var userStatsPrompt = new GUIMessageBox( - TextManager.Get("statisticspromptheader"), - TextManager.Get("statisticsprompttext"), - new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); - userStatsPrompt.Buttons[0].OnClicked += (btn, userdata) => - { - GameSettings.ShowUserStatisticsPrompt = false; - GameSettings.SendUserStatistics = true; - GameAnalyticsManager.Init(); - Config.SaveNewPlayerConfig(); - return true; - }; - userStatsPrompt.Buttons[0].OnClicked += userStatsPrompt.Close; - userStatsPrompt.Buttons[1].OnClicked += (btn, userdata) => - { - GameSettings.ShowUserStatisticsPrompt = false; - GameSettings.SendUserStatistics = false; - Config.SaveNewPlayerConfig(); - return true; - }; - userStatsPrompt.Buttons[1].OnClicked += userStatsPrompt.Close; - } - else - { - //user statistics enabled by default if the prompt cannot be shown in the user's language - GameSettings.ShowUserStatisticsPrompt = false; - GameSettings.SendUserStatistics = true; - GameAnalyticsManager.Init(); - Config.SaveNewPlayerConfig(); - } - } - else if (GameSettings.SendUserStatistics) - { - GameAnalyticsManager.Init(); - } - } - public class LoadingException : Exception { public LoadingException(Exception e) : base("Loading was interrupted due to an error.", innerException: e) @@ -522,14 +476,9 @@ namespace Barotrauma DebugConsole.Log("Selected content packages: " + string.Join(", ", Config.AllEnabledPackages.Select(cp => cp.Name))); } -#if DEBUG - GameSettings.ShowUserStatisticsPrompt = false; - GameSettings.SendUserStatistics = false; -#endif + GameAnalyticsManager.InitIfConsented(); - InitUserStats(); - - yield return CoroutineStatus.Running; + yield return CoroutineStatus.Running; Debug.WriteLine("sounds"); @@ -841,6 +790,8 @@ namespace Barotrauma } #endif + NetworkMember?.Update((float)Timing.Step); + if (!hasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine)) { throw new LoadingException(loadingCoroutine.Exception); @@ -915,6 +866,11 @@ namespace Barotrauma { gameSession.ToggleTabMenu(); } + else if (GUIMessageBox.VisibleBox as GUIMessageBox != null && + GUIMessageBox.VisibleBox.UserData as string == "bugreporter") + { + ((GUIMessageBox)GUIMessageBox.VisibleBox).Close(); + } else if (GUI.PauseMenuOpen) { GUI.TogglePauseMenu(); @@ -999,11 +955,11 @@ namespace Barotrauma } } + NetworkMember?.Update((float)Timing.Step); + GUI.Update((float)Timing.Step); } - NetworkMember?.Update((float)Timing.Step); - CoroutineManager.Update((float)Timing.Step, Paused ? 0.0f : (float)Timing.Step); SteamManager.Update((float)Timing.Step); @@ -1123,6 +1079,10 @@ namespace Barotrauma if (GameSession != null) { + double roundDuration = Timing.TotalTime - GameSession.RoundStartTime; + GameAnalyticsManager.AddProgressionEvent(GameAnalyticsManager.ProgressionStatus.Fail, + GameSession.GameMode?.Name ?? "none", + roundDuration); if (Tutorial.Initialized) { ((TutorialMode)GameSession.GameMode).Tutorial?.Stop(); @@ -1131,6 +1091,7 @@ namespace Barotrauma GUIMessageBox.CloseAll(); MainMenuScreen.Select(); GameSession = null; + } public void ShowCampaignDisclaimer(Action onContinue = null) @@ -1254,7 +1215,7 @@ namespace Barotrauma DebugConsole.ThrowError("Error while cleaning unnecessary save files", e); } - if (GameSettings.SendUserStatistics) { GameAnalytics.OnQuit(); } + if (GameAnalyticsManager.SendUserStatistics) { GameAnalyticsManager.ShutDown(); } if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging) { DebugConsole.SaveLogs(); } base.OnExiting(sender, args); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 2ef81f846..43f30d3b4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -330,7 +330,7 @@ namespace Barotrauma if (data == null) { return false; } if (GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character == data) is Client client) { - CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client); + NetLobbyScreen.CreateModerationContextMenu(client); return true; } return false; @@ -789,7 +789,7 @@ namespace Barotrauma } else if (orderGiver != null) { - OrderChatMessage msg = new OrderChatMessage(order, option, priority, order?.TargetSpatialEntity ?? order?.TargetItemComponent?.Item as ISpatialEntity, character, orderGiver); + OrderChatMessage msg = new OrderChatMessage(order, option, priority, order?.TargetSpatialEntity ?? order?.TargetItemComponent?.Item, character, orderGiver); GameMain.Client?.SendChatMessage(msg); } } @@ -968,7 +968,8 @@ namespace Barotrauma { if (!CanIssueOrders) { return false; } var orderInfo = (OrderInfo)userData; - SetCharacterOrder(character, orderInfo.Order, orderInfo.OrderOption, CharacterInfo.HighestManualOrderPriority, Character.Controlled); + int priority = GetManualOrderPriority(character, orderInfo.Order); + SetCharacterOrder(character, orderInfo.Order, orderInfo.OrderOption, priority, Character.Controlled); return true; }, OnSecondaryClicked = (button, userData) => @@ -1153,105 +1154,6 @@ namespace Barotrauma } } - #region Context Menu - - public void CreateModerationContextMenu(Point mousePos, Client client) - { - if (GUIContextMenu.CurrentContextMenu != null) { return; } - if (IsSinglePlayer || client == null || ((!GameMain.Client?.PreviouslyConnectedClients?.Contains(client)) ?? true)) { return; } - - - bool hasSteam = client.SteamID > 0 && SteamManager.IsInitialized, - canKick = GameMain.Client.HasPermission(ClientPermissions.Kick), - canBan = GameMain.Client.HasPermission(ClientPermissions.Ban) && client.AllowKicking, - canPromo = GameMain.Client.HasPermission(ClientPermissions.ManagePermissions); - - // Disable options if we are targeting ourselves - if (client.ID == GameMain.Client?.ID) - { - canKick = canBan = canPromo = false; - } - - List options = new List(); - - options.Add(new ContextMenuOption("ViewSteamProfile", isEnabled: hasSteam, onSelected: delegate - { - Steamworks.SteamFriends.OpenWebOverlay($"https://steamcommunity.com/profiles/{client.SteamID}"); - })); - - options.Add(new ContextMenuOption("ModerationMenu.UserDetails", isEnabled: true, onSelected: delegate - { - GameMain.NetLobbyScreen?.SelectPlayer(client); - })); - - - // Creates sub context menu options for all the ranks - List permissionOptions = new List(); - foreach (PermissionPreset rank in PermissionPreset.List) - { - permissionOptions.Add(new ContextMenuOption(rank.Name, isEnabled: true, onSelected: () => - { - string label = TextManager.GetWithVariables(rank.Permissions == ClientPermissions.None ? "clearrankprompt" : "giverankprompt", new []{ "[user]", "[rank]" }, new []{ client.Name, rank.Name }); - GUIMessageBox msgBox = new GUIMessageBox(string.Empty, label, new[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }); - - msgBox.Buttons[0].OnClicked = delegate - { - client.SetPermissions(rank.Permissions, rank.PermittedCommands); - GameMain.Client.UpdateClientPermissions(client); - msgBox.Close(); - return true; - }; - msgBox.Buttons[1].OnClicked = delegate - { - msgBox.Close(); - return true; - }; - }) { Tooltip = rank.Description }); - } - - options.Add(new ContextMenuOption("Permissions", isEnabled: canPromo, options: permissionOptions.ToArray())); - - Color clientColor = client.Character?.Info?.Job.Prefab.UIColor ?? Color.White; - - if (GameMain.Client.ConnectedClients.Contains(client)) - { - options.Add(new ContextMenuOption(client.MutedLocally ? "Unmute" : "Mute", isEnabled: client.ID != GameMain.Client?.ID, onSelected: delegate - { - client.MutedLocally = !client.MutedLocally; - })); - - bool kickEnabled = client.ID != GameMain.Client?.ID && client.AllowKicking; - - // if the user can kick create a kick option else create the votekick option - ContextMenuOption kickOption; - if (canKick) - { - kickOption = new ContextMenuOption("Kick", isEnabled: kickEnabled, onSelected: delegate - { - GameMain.Client?.CreateKickReasonPrompt(client.Name, false); - }); - } - else - { - kickOption = new ContextMenuOption("VoteToKick", isEnabled: kickEnabled, onSelected: delegate - { - GameMain.Client?.VoteForKick(client); - }); - } - - options.Add(kickOption); - } - - options.Add(new ContextMenuOption("Ban", isEnabled: canBan, onSelected: delegate - { - GameMain.Client?.CreateKickReasonPrompt(client.Name, true); - })); - - GUIContextMenu.CreateContextMenu(null, client.Name, headerColor: clientColor, options.ToArray()); - } - - #endregion - public void AddToGUIUpdateList() { if (GUI.DisableHUD) { return; } @@ -1902,7 +1804,7 @@ namespace Barotrauma private Hull hullContext; private WallSection wallContext; private bool isContextual; - private readonly List contextualOrders = new List(); + private readonly List contextualOrders = new List(); private Point shorcutCenterNodeOffset; private const int maxShortcutNodeCount = 4; @@ -2605,7 +2507,7 @@ namespace Barotrauma { order = orders[i]; disableNode = !CanCharacterBeHeard() || - (order.MustSetTarget && (order.ItemComponentType != null || order.TargetItems.Length > 0) && + (order.MustSetTarget && (order.ItemComponentType != null || order.GetTargetItems().Any() || order.RequireItems.Any()) && order.GetMatchingItems(true, interactableFor: characterContext ?? Character.Controlled).None()); optionNodes.Add(new Tuple( CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), order, (i + 1) % 10, disableNode: disableNode, checkIfOrderCanBeHeard: false), @@ -2621,7 +2523,6 @@ namespace Barotrauma if (contextualOrders.None()) { string orderIdentifier; - // Check if targeting an item or a hull if (itemContext != null && itemContext.IsPlayerTeamInteractable) { @@ -2630,84 +2531,89 @@ namespace Barotrauma { targetComponent = null; if (p.UseController && itemContext.Components.None(c => c is Controller)) { continue; } - if ((p.TargetItems.Length > 0 && (p.TargetItems.Contains(itemContext.Prefab.Identifier) || itemContext.HasTag(p.TargetItems))) || - p.TryGetTargetItemComponent(itemContext, out targetComponent)) + if (p.HasOptionSpecificTargetItems) { - contextualOrders.Add(p.HasOptions ? p : new Order(p, itemContext, targetComponent, Character.Controlled)); + foreach (string option in p.Options) + { + if (p.TargetItemsMatchItem(itemContext, option)) + { + contextualOrders.Add(new OrderInfo(new Order(p, itemContext, targetComponent, Character.Controlled), option)); + } + } + } + else if (p.TargetItemsMatchItem(itemContext) || p.TryGetTargetItemComponent(itemContext, out targetComponent)) + { + contextualOrders.Add(new OrderInfo(p.HasOptions ? p : new Order(p, itemContext, targetComponent, Character.Controlled), null)); } } - // If targeting a periscope connected to a turret, show the 'operateweapons' order orderIdentifier = "operateweapons"; var operateWeaponsPrefab = Order.GetPrefab(orderIdentifier); - if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && itemContext.Components.Any(c => c is Controller)) + if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier)) && itemContext.Components.Any(c => c is Controller)) { - var turret = itemContext.GetConnectedComponents().FirstOrDefault(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems)) ?? - itemContext.GetConnectedComponents(recursive: true).FirstOrDefault(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems)); - if (turret != null) { contextualOrders.Add(new Order(operateWeaponsPrefab, turret.Item, turret, Character.Controlled)); } + var turret = itemContext.GetConnectedComponents().FirstOrDefault(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item)) ?? + itemContext.GetConnectedComponents(recursive: true).FirstOrDefault(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item)); + if (turret != null) + { + contextualOrders.Add(new OrderInfo(new Order(operateWeaponsPrefab, turret.Item, turret, Character.Controlled), null)); + } } - // If targeting a repairable item with condition below the repair threshold, show the 'repairsystems' order orderIdentifier = "repairsystems"; - if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && itemContext.Repairables.Any(r => r.IsBelowRepairThreshold)) + if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier)) && itemContext.Repairables.Any(r => r.IsBelowRepairThreshold)) { if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical")))) { - contextualOrders.Add(new Order(Order.GetPrefab("repairelectrical"), itemContext, targetItem: null, Character.Controlled)); + contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab("repairelectrical"), itemContext, targetItem: null, Character.Controlled), null)); } else if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("mechanical")))) { - contextualOrders.Add(new Order(Order.GetPrefab("repairmechanical"), itemContext, targetItem: null, Character.Controlled)); + contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab("repairmechanical"), itemContext, targetItem: null, Character.Controlled), null)); } else { - contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), itemContext, targetItem: null, Character.Controlled)); + contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), itemContext, targetItem: null, Character.Controlled), null)); } } - // Remove the 'pumpwater' order if the target pump is auto-controlled (as it will immediately overwrite the work done by the bot) orderIdentifier = "pumpwater"; - if (contextualOrders.FirstOrDefault(o => o.Identifier.Equals(orderIdentifier)) is Order o && - itemContext.Components.FirstOrDefault(c => c.GetType() == o.ItemComponentType) is Pump pump) + if (contextualOrders.FirstOrDefault(info => info.Order.Identifier.Equals(orderIdentifier)) is OrderInfo pumpOrderInfo && pumpOrderInfo.Order is Order pumpOrder && + itemContext.Components.FirstOrDefault(c => c.GetType() == pumpOrder.ItemComponentType) is Pump pump && pump.IsAutoControlled) { - if (pump.IsAutoControlled) { contextualOrders.Remove(o); } + contextualOrders.Remove(pumpOrderInfo); } - if (contextualOrders.None()) { orderIdentifier = "cleanupitems"; - if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier))) + if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier))) { if (AIObjectiveCleanupItems.IsValidTarget(itemContext, Character.Controlled, checkInventory: false) || AIObjectiveCleanupItems.IsValidContainer(itemContext, Character.Controlled)) { - contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), itemContext, targetItem: null, Character.Controlled)); + contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), itemContext, targetItem: null, Character.Controlled), null)); } } } - AddIgnoreOrder(itemContext); } else if (hullContext != null) { - contextualOrders.Add(new Order(Order.GetPrefab("fixleaks"), hullContext, targetItem: null, Character.Controlled)); - + contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab("fixleaks"), hullContext, targetItem: null, Character.Controlled), null)); if (wallContext != null) { AddIgnoreOrder(wallContext); } } - void AddIgnoreOrder(IIgnorable target) { var orderIdentifier = "ignorethis"; - if (!target.OrderedToBeIgnored && contextualOrders.None(o => o.Identifier == orderIdentifier)) + if (!target.OrderedToBeIgnored && contextualOrders.None(info => info.Order.Identifier == orderIdentifier)) { AddOrder(); } else { orderIdentifier = "unignorethis"; - if (target.OrderedToBeIgnored && contextualOrders.None(o => o.Identifier == orderIdentifier)) + if (target.OrderedToBeIgnored && contextualOrders.None(info => info.Order.Identifier == orderIdentifier)) { AddOrder(); } @@ -2717,64 +2623,61 @@ namespace Barotrauma { if (target is WallSection ws) { - contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), ws.Wall, ws.Wall.Sections.IndexOf(ws), orderGiver: Character.Controlled)); + contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), ws.Wall, ws.Wall.Sections.IndexOf(ws), orderGiver: Character.Controlled), null)); } else { - contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), target as Entity, null, Character.Controlled)); + contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), target as Entity, null, Character.Controlled), null)); } } } - orderIdentifier = "wait"; - if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier))) + if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier))) { Vector2 position = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); Hull hull = Hull.FindHull(position, guess: Character.Controlled?.CurrentHull); - contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), new OrderTarget(position, hull), Character.Controlled)); + contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), new OrderTarget(position, hull), Character.Controlled), null)); } - - if (contextualOrders.None(o => o.Category != OrderCategory.Movement) && characters.Any(c => c != Character.Controlled)) + if (contextualOrders.None(info => info.Order.Category != OrderCategory.Movement) && characters.Any(c => c != Character.Controlled)) { orderIdentifier = "follow"; - if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier))) + if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier))) { - contextualOrders.Add(Order.GetPrefab(orderIdentifier)); + contextualOrders.Add(new OrderInfo(Order.GetPrefab(orderIdentifier), null)); } } - // Show 'dismiss' order only when there are crew members with active orders orderIdentifier = "dismissed"; - if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && characters.Any(c => !c.IsDismissed)) + if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier)) && characters.Any(c => !c.IsDismissed)) { - contextualOrders.Add(Order.GetPrefab(orderIdentifier)); + contextualOrders.Add(new OrderInfo(Order.GetPrefab(orderIdentifier), null)); } } - var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, contextualOrders.Count, MathHelper.ToRadians(90f + 180f / contextualOrders.Count)); bool disableNode = !CanCharacterBeHeard(); for (int i = 0; i < contextualOrders.Count; i++) { - optionNodes.Add(new Tuple( - CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), contextualOrders[i], (i + 1) % 10, disableNode: disableNode, checkIfOrderCanBeHeard: false), - !disableNode ? Keys.D0 + (i + 1) % 10 : Keys.None)); + var info = contextualOrders[i]; + int hotkey = (i + 1) % 10; + var component = string.IsNullOrEmpty(info.OrderOption) ? + CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), info.Order, hotkey, disableNode: disableNode, checkIfOrderCanBeHeard: false) : + CreateOrderOptionNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), info.Order, info.OrderOption, info.Order.Prefab.GetOptionName(info.OrderOption), hotkey); + optionNodes.Add(new Tuple(component, !disableNode ? Keys.D0 + (i + 1) % 10 : Keys.None)); } } // TODO: there's duplicate logic here and above -> would be better to refactor so that the conditions are only defined in one place public static bool DoesItemHaveContextualOrders(Item item) { - if (Order.PrefabList.Any(o => o.TargetItems.Length > 0 && o.TargetItems.Contains(item.Prefab.Identifier))) { return true; } - if (Order.PrefabList.Any(o => item.HasTag(o.TargetItems))) { return true; } + if (Order.PrefabList.Any(o => o.TargetItemsMatchItem(item))) { return true; } if (Order.PrefabList.Any(o => o.TryGetTargetItemComponent(item, out _))) { return true; } if (AIObjectiveCleanupItems.IsValidTarget(item, Character.Controlled, checkInventory: false)) { return true; } if (AIObjectiveCleanupItems.IsValidContainer(item, Character.Controlled)) { return true; } - + if (Order.GetPrefab("loaditems") is Order loadItemsPrefab && AIObjectiveLoadItems.IsValidTarget(item, Character.Controlled, targetContainerTags: loadItemsPrefab.GetTargetItems())) { return true; } if (item.Repairables.Any(r => r.IsBelowRepairThreshold)) { return true; } - var operateWeaponsPrefab = Order.GetPrefab("operateweapons"); - return item.Components.Any(c => c is Controller) && - (item.GetConnectedComponents().Any(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems)) || - item.GetConnectedComponents(recursive: true).Any(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems))); + return Order.GetPrefab("operateweapons") is Order operateWeaponsPrefab && item.Components.Any(c => c is Controller) && + (item.GetConnectedComponents().Any(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item)) || + item.GetConnectedComponents(recursive: true).Any(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item))); } /// Use a negative value (e.g. -1) if there should be no hotkey associated with the node @@ -2831,7 +2734,8 @@ namespace Barotrauma o = new Order(o.Prefab, orderTargetEntity, orderTargetEntity.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: order.OrderGiver); } var character = !o.TargetAllCharacters ? characterContext ?? GetCharacterForQuickAssignment(o) : null; - SetCharacterOrder(character, o, null, CharacterInfo.HighestManualOrderPriority, Character.Controlled); + int priority = GetManualOrderPriority(character, o); + SetCharacterOrder(character, o, null, priority, Character.Controlled); DisableCommandUI(); } return true; @@ -2943,7 +2847,9 @@ namespace Barotrauma } else { - SetCharacterOrder(characterContext ?? GetCharacterForQuickAssignment(o.Item1), o.Item1, o.Item2, CharacterInfo.HighestManualOrderPriority, Character.Controlled); + var character = characterContext ?? GetCharacterForQuickAssignment(o.Item1); + int priority = GetManualOrderPriority(character, o.Item1); + SetCharacterOrder(character, o.Item1, o.Item2, priority, Character.Controlled); DisableCommandUI(); } return true; @@ -3013,7 +2919,9 @@ namespace Barotrauma } else { - SetCharacterOrder(characterContext ?? GetCharacterForQuickAssignment(o.Item1), o.Item1, o.Item2, CharacterInfo.HighestManualOrderPriority, Character.Controlled); + var character = characterContext ?? GetCharacterForQuickAssignment(o.Item1); + int priority = GetManualOrderPriority(character, o.Item1); + SetCharacterOrder(character, o.Item1, o.Item2, priority, Character.Controlled); DisableCommandUI(); } return true; @@ -3218,7 +3126,9 @@ namespace Barotrauma OnClicked = (_, userData) => { if (!CanIssueOrders) { return false; } - SetCharacterOrder(userData as Character, order.Item1, order.Item2, CharacterInfo.HighestManualOrderPriority, Character.Controlled); + var character = userData as Character; + int priority = GetManualOrderPriority(character, order.Item1); + SetCharacterOrder(character, order.Item1, order.Item2, priority, Character.Controlled); DisableCommandUI(); return true; } @@ -3496,6 +3406,11 @@ namespace Barotrauma return order.Name; } + private int GetManualOrderPriority(Character character, Order order) + { + return character?.Info?.GetManualOrderPriority(order) ?? CharacterInfo.HighestManualOrderPriority; + } + #region Crew Member Assignment Logic private bool CanOpenManualAssignment(GUIComponent node) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs index e7bd89023..95d8d567e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs @@ -65,7 +65,7 @@ namespace Barotrauma public override void ShowStartMessage() { - foreach (Mission mission in Missions) + foreach (Mission mission in Missions.ToList()) { new GUIMessageBox( mission.Prefab.IsSideObjective ? TextManager.AddPunctuation(':', TextManager.Get("sideobjective"), mission.Name) : mission.Name, diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index e88f312a9..a0876e3da 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -60,7 +60,7 @@ namespace Barotrauma var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); - GameMain.NetLobbyScreen.CampaignSetupUI = new MultiPlayerCampaignSetupUI(newCampaignContainer, loadCampaignContainer, null, saveFiles); + GameMain.NetLobbyScreen.CampaignSetupUI = new MultiPlayerCampaignSetupUI(newCampaignContainer, loadCampaignContainer, saveFiles); var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform), TextManager.Get("NewCampaign"), style: "GUITabButton") diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs index 0112281b7..3a22e29d8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs @@ -475,12 +475,12 @@ namespace Barotrauma ItemComponent targetItem = null; if (orderPrefab.MustSetTarget) { - targetEntity = orderPrefab.GetMatchingItems(true, interactableFor: Character.Controlled).FirstOrDefault(); + targetEntity = orderPrefab.GetMatchingItems(true, interactableFor: Character.Controlled, orderOption: orderInfo.option).FirstOrDefault(); if (targetEntity == null) { return; } targetItem = orderPrefab.GetTargetItemComponent(targetEntity); } var order = new Order(orderPrefab, targetEntity as Entity, targetItem, orderGiver: Character.Controlled); - GameMain.GameSession.CrewManager.SetCharacterOrder(Character.Controlled, order, orderInfo.option, CharacterInfo.HighestManualOrderPriority, Character.Controlled); + GameMain.GameSession?.CrewManager?.SetCharacterOrder(Character.Controlled, order, orderInfo.option, CharacterInfo.HighestManualOrderPriority, Character.Controlled); }); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs index e69e11bae..f0db4ea5b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs @@ -635,9 +635,9 @@ namespace Barotrauma } else if (characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction && characterInfo.CauseOfDeath.Affliction == null) { - string errorMsg = "Character \"" + characterInfo.Name + "\" had an invalid cause of death (the type of the cause of death was Affliction, but affliction was not specified)."; - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("RoundSummary:InvalidCauseOfDeath", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + string errorMsg = "Character \"[name]\" had an invalid cause of death (the type of the cause of death was Affliction, but affliction was not specified)."; + DebugConsole.ThrowError(errorMsg.Replace("[name]", characterInfo.Name)); + GameAnalyticsManager.AddErrorEventOnce("RoundSummary:InvalidCauseOfDeath", GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace("[name]", characterInfo.SpeciesName)); statusText = TextManager.Get("CauseOfDeathDescription.Unknown"); statusColor = GUI.Style.Red; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs index 7260479fa..e71e6010c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs @@ -1,12 +1,10 @@ -using Barotrauma.Extensions; -using Barotrauma.Networking; +using Barotrauma.Networking; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using OpenAL; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Xml.Linq; @@ -365,6 +363,12 @@ namespace Barotrauma TextManager.Get("Settings"), textAlignment: Alignment.TopLeft, font: GUI.LargeFont) { ForceUpperCase = true }; + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), settingsTitle.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.Smallest), style: "GUIBugButton") + { + ToolTip = TextManager.Get("bugreportbutton") + $" (v{GameMain.Version})", + OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; } + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform), TextManager.Get("ContentPackages"), font: GUI.SubHeadingFont); var corePackageDropdown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform)) @@ -509,17 +513,53 @@ namespace Barotrauma ApplySettings(); GameMain.Instance.Exit(); return true; - }; msgBox.Buttons[1].OnClicked += (btn, userdata) => + }; + msgBox.Buttons[1].OnClicked += (btn, userdata) => { Language = prevLanguage; languageDD.SelectItem(Language); msgBox.Close(); return true; }; - return true; }; + var statisticsTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.045f), leftPanel.RectTransform), TextManager.Get("statisticsconsenttickbox")) + { + OnSelected = (GUITickBox tickBox) => + { + GameAnalyticsManager.SetConsent( + tickBox.Selected + ? GameAnalyticsManager.Consent.Ask + : GameAnalyticsManager.Consent.No); + return false; + } + }; + void updateGATickBoxToolTip() + => statisticsTickBox.ToolTip = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}"); + updateGATickBoxToolTip(); + + var cachedConsent = GameAnalyticsManager.Consent.Unknown; + var statisticsTickBoxUpdater = new GUICustomComponent( + new RectTransform(Vector2.Zero, statisticsTickBox.RectTransform), + onUpdate: (deltaTime, component) => + { + bool shouldTickBoxBeSelected = GameAnalyticsManager.UserConsented == GameAnalyticsManager.Consent.Yes; + + bool shouldUpdateTickBoxState = cachedConsent != GameAnalyticsManager.UserConsented + || statisticsTickBox.Selected != shouldTickBoxBeSelected; + + if (!shouldUpdateTickBoxState) { return; } + + updateGATickBoxToolTip(); + cachedConsent = GameAnalyticsManager.UserConsented; + GUITickBox.OnSelectedHandler prevHandler = statisticsTickBox.OnSelected; + statisticsTickBox.OnSelected = null; + statisticsTickBox.Selected = shouldTickBoxBeSelected; + statisticsTickBox.OnSelected = prevHandler; + statisticsTickBox.Enabled = GameAnalyticsManager.UserConsented != GameAnalyticsManager.Consent.Error; + }); + // right panel -------------------------------------- var rightPanel = new GUILayoutGroup(new RectTransform(new Vector2(0.99f - leftPanel.RectTransform.RelativeSize.X, leftPanel.RectTransform.RelativeSize.Y), @@ -556,13 +596,6 @@ namespace Barotrauma tabButtons[(int)tab].Text = ToolBox.LimitString(buttonText, tabButtons[(int)tab].Font, (int)(0.75f * tabWidth * tabButtonHolder.Rect.Width)); } - new GUIButton(new RectTransform(new Vector2(0.05f, 0.75f), tabButtonHolder.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.0f, 0.2f) }, style: "GUIBugButton") - { - ToolTip = TextManager.Get("bugreportbutton"), - OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; } - }; - - /// Graphics tab -------------------------------------------------------------- var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.46f, 0.95f), tabs[(int)Tab.Graphics].RectTransform, Anchor.TopLeft) @@ -1168,7 +1201,7 @@ namespace Barotrauma catch (Exception e) { DebugConsole.ThrowError("Failed to set voice capture mode.", e); - GameAnalyticsManager.AddErrorEventOnce("SetVoiceCaptureMode", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to set voice capture mode. " + e.Message + "\n" + e.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.AddErrorEventOnce("SetVoiceCaptureMode", GameAnalyticsManager.ErrorSeverity.Error, "Failed to set voice capture mode. " + e.Message + "\n" + e.StackTrace.CleanupStackTrace()); VoiceSetting = VoiceMode.Disabled; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index f68a28184..f42e06aa0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -406,7 +406,7 @@ namespace Barotrauma.Items.Components DebugConsole.Log("Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume); GameAnalyticsManager.AddErrorEventOnce( "ItemComponent.PlaySound:" + item.Name + GetType().ToString(), - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume); return 0.0f; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Ladder.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Ladder.cs index 08958f682..52f925579 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Ladder.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Ladder.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml.Linq; -using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using System.Xml.Linq; namespace Barotrauma.Items.Components { @@ -11,7 +8,7 @@ namespace Barotrauma.Items.Components { public float BackgroundSpriteDepth { - get { return item.GetDrawDepth() + 0.1f; } + get { return item.GetDrawDepth() + 0.05f; } } public Vector2 DrawSize diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index 85629e383..1f2d6882f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -672,36 +672,33 @@ namespace Barotrauma.Items.Components return; } - if (currentMode == MiniMapMode.HullStatus) + if (currentMode == MiniMapMode.HullStatus && item.Submarine != null) { Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; spriteBatch.End(); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect; - if (item.Submarine != null) + var sprite = GUI.Style.UIGlowSolidCircular?.Sprite; + float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f; + if (sprite != null) { - var sprite = GUI.Style.UIGlowSolidCircular?.Sprite; - float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f; - if (sprite != null) + Vector2 spriteSize = sprite.size; + Rectangle worldBorders = item.Submarine.GetDockedBorders(); + worldBorders.Location += item.Submarine.WorldPosition.ToPoint(); + foreach (Gap gap in Gap.GapList) { - Vector2 spriteSize = sprite.size; - Rectangle worldBorders = item.Submarine.GetDockedBorders(); - worldBorders.Location += item.Submarine.WorldPosition.ToPoint(); - foreach (Gap gap in Gap.GapList) - { - if (gap.IsRoomToRoom || gap.Submarine != item.Submarine || gap.ConnectedDoor != null || gap.HiddenInGame) { continue; } - RectangleF entityRect = ScaleRectToUI(gap, miniMapFrame.Rect, worldBorders); + if (gap.IsRoomToRoom || gap.linkedTo.Count == 0 || gap.Submarine != item.Submarine || gap.ConnectedDoor != null || gap.HiddenInGame) { continue; } + RectangleF entityRect = ScaleRectToUI(gap, miniMapFrame.Rect, worldBorders); - Vector2 scale = new Vector2(entityRect.Size.X / spriteSize.X, entityRect.Size.Y / spriteSize.Y) * 2.0f; + Vector2 scale = new Vector2(entityRect.Size.X / spriteSize.X, entityRect.Size.Y / spriteSize.Y) * 2.0f; - Color color = ToolBox.GradientLerp(gap.Open, GUI.Style.HealthBarColorMedium, GUI.Style.HealthBarColorLow) * alpha; - sprite.Draw(spriteBatch, - miniMapFrame.Rect.Location.ToVector2() + entityRect.Center, - color, origin: sprite.Origin, rotate: 0.0f, scale: scale); - } + Color color = ToolBox.GradientLerp(gap.Open, GUI.Style.HealthBarColorMedium, GUI.Style.HealthBarColorLow) * alpha; + sprite.Draw(spriteBatch, + miniMapFrame.Rect.Location.ToVector2() + entityRect.Center, + color, origin: sprite.Origin, rotate: 0.0f, scale: scale); } - } + } if (currentMode == MiniMapMode.HullStatus && hullStatusComponents != null) { @@ -991,8 +988,40 @@ namespace Barotrauma.Items.Components continue; } - hullData.HullOxygenAmount = RequireOxygenDetectors ? hullData.ReceivedOxygenAmount : hull.OxygenPercentage; - hullData.HullWaterAmount = RequireWaterDetectors ? hullData.ReceivedWaterAmount : Math.Min(hull.WaterVolume / hull.Volume, 1.0f); + if (RequireOxygenDetectors) + { + hullData.HullOxygenAmount = hullData.ReceivedOxygenAmount; + } + else if (hullData.LinkedHulls.Any()) + { + hullData.HullOxygenAmount = 0.0f; + foreach (Hull linkedHull in hullData.LinkedHulls) + { + hullData.HullOxygenAmount += linkedHull.OxygenPercentage; + } + hullData.HullOxygenAmount /= hullData.LinkedHulls.Count; + } + else + { + hullData.HullOxygenAmount = hull.OxygenPercentage; + } + if (RequireWaterDetectors) + { + hullData.HullWaterAmount = hullData.ReceivedWaterAmount; + } + else if (hullData.LinkedHulls.Any()) + { + hullData.HullWaterAmount = 0.0f; + foreach (Hull linkedHull in hullData.LinkedHulls) + { + hullData.HullWaterAmount += Math.Min(linkedHull.WaterVolume / linkedHull.Volume, 1.0f); + } + hullData.HullWaterAmount /= hullData.LinkedHulls.Count; + } + else + { + hullData.HullWaterAmount = Math.Min(hull.WaterVolume / hull.Volume, 1.0f); + } float gapOpenSum = 0.0f; @@ -1013,15 +1042,6 @@ namespace Barotrauma.Items.Components float? oxygenAmount = hullData.HullOxygenAmount, waterAmount = hullData.HullWaterAmount; - foreach (Hull linkedHull in hullData.LinkedHulls) - { - oxygenAmount += linkedHull.OxygenPercentage; - waterAmount += Math.Min(linkedHull.WaterVolume / linkedHull.Volume, 1.0f); - } - - oxygenAmount /= (hullData.LinkedHulls.Count + 1); - waterAmount /= (hullData.LinkedHulls.Count + 1); - string line1 = gapOpenSum > 0.1f ? TextManager.Get("MiniMapHullBreach") : string.Empty; Color line1Color = GUI.Style.Red; @@ -1111,10 +1131,9 @@ namespace Barotrauma.Items.Components private void DrawHUDBack(SpriteBatch spriteBatch, GUICustomComponent container) { - if (item.Submarine != null) - { - DrawSubmarine(spriteBatch); - } + if (item.Submarine == null) { return; } + + DrawSubmarine(spriteBatch); if (Voltage < MinVoltage) { return; } Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; @@ -1155,7 +1174,8 @@ namespace Barotrauma.Items.Components if (hullsVisible && hullData.HullWaterAmount is { } waterAmount) { - if (hullFrame.Rect.Height * waterAmount > 3.0f) + if (!RequireWaterDetectors) { waterAmount = hull.WaterPercentage / 100.0f; } + if (hullFrame.Rect.Height * waterAmount > 1.0f) { RectangleF waterRect = new RectangleF(hullFrame.Rect.X, hullFrame.Rect.Y + hullFrame.Rect.Height * (1.0f - waterAmount), hullFrame.Rect.Width, hullFrame.Rect.Height * waterAmount); @@ -1418,7 +1438,7 @@ namespace Barotrauma.Items.Components { if (linkedEntity is Hull linkedHull) { - if (linkedHulls.Contains(linkedHull)) { continue; } + if (linkedHulls.Contains(linkedHull) || linkedHull.HiddenInGame) { continue; } linkedHulls.Add(linkedHull); GetLinkedHulls(linkedHull, linkedHulls); } @@ -1598,7 +1618,7 @@ namespace Barotrauma.Items.Components bool IsPartofSub(MapEntity entity) { - if (entity.Submarine != sub && !connectedSubs.Contains(entity.Submarine)) { return false; } + if (entity.Submarine != sub && !connectedSubs.Contains(entity.Submarine) || entity.HiddenInGame) { return false; } return !settings.IgnoreOutposts || sub.IsEntityFoundOnThisSub(entity, true); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index 74b61c33a..d8831b9db 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -1623,6 +1623,11 @@ namespace Barotrauma.Items.Components markerDistances.Add(targetIdentifier, cachedDistance); dist = path.TotalLength; } + else + { + var cachedDistance = new CachedDistance(transducerPosition, worldPosition, linearDist, Timing.TotalTime + Rand.Range(4.0f, 7.0f)); + markerDistances.Add(targetIdentifier, cachedDistance); + } } Vector2 position = worldPosition - transducerPosition; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index 23d3a6839..6a6d3edc4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -35,6 +35,13 @@ namespace Barotrauma.Items.Components private FixActions requestStartFixAction; + private bool qteSuccess; + + private float qteTimer; + private const float qteTime = 0.5f; + private float qteCooldown; + private const float qteCooldownTime = 0.5f; + public float FakeBrokenTimer; [Serialize("", false, description: "An optional description of the needed repairs displayed in the repair interface.")] @@ -55,16 +62,13 @@ namespace Barotrauma.Items.Components if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) { return false; } if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; } - float maxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(character); - float defaultMaxCondition = item.MaxCondition / maxRepairConditionMultiplier; + float defaultMaxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier; if (MathUtils.Percentage(item.Condition, defaultMaxCondition) < RepairThreshold) { return true; } if (CurrentFixer == character) { - float condition = item.Condition / item.MaxRepairConditionMultiplier; - float maxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier; - if (condition < maxCondition * maxRepairConditionMultiplier) + if (item.Condition < item.MaxCondition) { return true; } @@ -145,11 +149,14 @@ namespace Barotrauma.Items.Components progressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), progressBarHolder.RectTransform), color: GUI.Style.Green, barSize: 0.0f, style: "DeviceProgressBar"); + progressBarOverlayText = new GUITextBlock(new RectTransform(Vector2.One, progressBar.RectTransform), string.Empty, font: GUI.SubHeadingFont, textAlignment: Alignment.Center) { IgnoreLayoutGroups = true }; + qteTimer = qteTime; + repairButtonText = TextManager.Get("RepairButton"); repairingText = TextManager.Get("Repairing"); RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText) @@ -159,6 +166,11 @@ namespace Barotrauma.Items.Components requestStartFixAction = FixActions.Repair; item.CreateClientEvent(this); return true; + }, + OnButtonDown = () => + { + QTEAction(); + return true; } }; RepairButton.TextBlock.AutoScaleHorizontal = true; @@ -183,6 +195,11 @@ namespace Barotrauma.Items.Components requestStartFixAction = FixActions.Sabotage; item.CreateClientEvent(this); return true; + }, + OnButtonDown = () => + { + QTEAction(); + return true; } }; @@ -253,6 +270,21 @@ namespace Barotrauma.Items.Components { repairSoundChannel = SoundPlayer.PlaySound("repair", item.WorldPosition, hullGuess: item.CurrentHull); } + + if (qteCooldown > 0.0f) + { + qteCooldown -= deltaTime; + if (qteCooldown <= 0.0f) + { + qteTimer = qteTime; + } + } + else + { + qteTimer -= deltaTime * (qteTimer / qteTime); + if (qteTimer < 0.0f) qteTimer = qteTime; + + } } else { @@ -270,6 +302,26 @@ namespace Barotrauma.Items.Components progressBar.BarSize = item.Condition / defaultMaxCondition; progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green); + Rectangle sliderRect = progressBar.GetSliderRect(1.0f); + Color qteSliderColor = Color.White; + if (qteCooldown > 0.0f) + { + qteSliderColor = qteSuccess ? GUI.Style.Green : GUI.Style.Red * 0.5f; + progressBar.Color = ToolBox.GradientLerp(qteCooldown / qteCooldownTime, progressBar.Color, qteSliderColor, Color.White); + } + else + { + if (qteTimer / qteTime <= item.Condition / item.MaxCondition) + { + qteSliderColor = Color.Lerp(qteSliderColor, GUI.Style.Green, 0.5f); + } + } + + progressBar.Parent.Parent.Parent.DrawManually(spriteBatch, true); + GUI.DrawRectangle(spriteBatch, + new Rectangle(sliderRect.X + (int)((qteTimer / qteTime) * sliderRect.Width), sliderRect.Y - 5, 2, sliderRect.Height + 10), + qteSliderColor, true); + if (item.Condition > defaultMaxCondition) { float extraCondition = item.MaxCondition * (item.MaxRepairConditionMultiplier - 1.0f); @@ -282,7 +334,7 @@ namespace Barotrauma.Items.Components progressBarOverlayText.Visible = false; } - RepairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition && IsBelowRepairThreshold; + RepairButton.Enabled = (currentFixerAction == FixActions.None || CurrentFixer == character) && !item.IsFullCondition; RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ? repairButtonText : repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); @@ -352,6 +404,29 @@ namespace Barotrauma.Items.Components repairSoundChannel = null; } + private void QTEAction() + { + if (currentFixerAction == FixActions.Repair) + { + qteSuccess = qteCooldown <= 0.0f && qteTimer / qteTime <= item.Condition / item.MaxCondition; + } + else + { + return; + } + + if (!GameMain.IsMultiplayer) { RepairBoost(qteSuccess); } + + SoundPlayer.PlayUISound(qteSuccess ? GUISoundType.IncreaseQuantity : GUISoundType.DecreaseQuantity); + + //on failure during cooldown reset cursor to beginning + if (!qteSuccess && qteCooldown > 0.0f) { qteTimer = qteTime; } + qteCooldown = qteCooldownTime; + //this will be set on button down so we can reset it here + requestStartFixAction = FixActions.None; + item.CreateClientEvent(this); + } + public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) { deteriorationTimer = msg.ReadSingle(); @@ -363,11 +438,13 @@ namespace Barotrauma.Items.Components currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2); CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null; item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer); + repairBoost = msg.ReadSingle(); } public void ClientWrite(IWriteMessage msg, object[] extraData = null) { msg.WriteRangedInteger((int)requestStartFixAction, 0, 2); + msg.Write(qteSuccess); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs index 1f425fca0..f9c20a0ff 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs @@ -26,12 +26,7 @@ namespace Barotrauma.Items.Components AutoHideScrollBar = false }; - // Create fillerBlock to cover historyBox so new values appear at the bottom of historyBox - // This could be removed if GUIListBox supported aligning its children - fillerBlock = new GUITextBlock(new RectTransform(new Vector2(1, 1), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), string.Empty) - { - CanBeFocused = false - }; + CreateFillerBlock(); new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine"); @@ -55,6 +50,16 @@ namespace Barotrauma.Items.Components }; } + // Create fillerBlock to cover historyBox so new values appear at the bottom of historyBox + // This could be removed if GUIListBox supported aligning its children + public void CreateFillerBlock() + { + fillerBlock = new GUITextBlock(new RectTransform(new Vector2(1, 1), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), string.Empty) + { + CanBeFocused = false + }; + } + private void SendOutput(string input) { if (input.Length > MaxMessageLength) @@ -130,7 +135,12 @@ namespace Barotrauma.Items.Components public void ClientWrite(IWriteMessage msg, object[] extraData = null) { - msg.Write((string)extraData[2]); + if (extraData is null) { return; } + + if (extraData[2] is string str) + { + msg.Write(str); + } } public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs index acec84bc8..0c31c61a5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs @@ -160,7 +160,7 @@ namespace Barotrauma.Items.Components { errorMsg += "\nTrying to dock the submarine to itself."; } - GameAnalyticsManager.AddErrorEventOnce("DockingPort.ClientRead:JointNotCreated", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("DockingPort.ClientRead:JointNotCreated", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } if (isLocked) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 98ae88380..98c58d8e1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -1445,7 +1445,7 @@ namespace Barotrauma #else if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); } #endif - GameAnalyticsManager.AddErrorEventOnce("Item.ClientReadPosition:nophysicsbody", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.ClientReadPosition:nophysicsbody", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } @@ -1586,7 +1586,7 @@ namespace Barotrauma string errorMsg = "Failed to spawn item, prefab not found (name: " + (itemName ?? "null") + ", identifier: " + (itemIdentifier ?? "null") + ")"; errorMsg += "\n" + string.Join(", ", GameMain.Config.AllEnabledPackages.Select(cp => cp.Name)); GameAnalyticsManager.AddErrorEventOnce("Item.ReadSpawnData:PrefabNotFound" + (itemName ?? "null") + (itemIdentifier ?? "null"), - GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, + GameAnalyticsManager.ErrorSeverity.Critical, errorMsg); DebugConsole.ThrowError(errorMsg); return null; @@ -1607,7 +1607,7 @@ namespace Barotrauma string errorMsg = $"Failed to spawn item \"{(itemIdentifier ?? "null")}\" in the inventory of \"{parentItem.prefab.Identifier} ({parentItem.ID})\" (component index out of range). Index: {itemContainerIndex}, components: {parentItem.components.Count}."; GameAnalyticsManager.AddErrorEventOnce("Item.ReadSpawnData:ContainerIndexOutOfRange" + (itemName ?? "null") + (itemIdentifier ?? "null"), - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, errorMsg); DebugConsole.ThrowError(errorMsg); inventory = parentItem.GetComponent()?.Inventory; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs index 20eaf6139..ad94444ce 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs @@ -98,7 +98,7 @@ namespace Barotrauma private IEnumerable DimLight(LightSource light) { float currBrightness = 1.0f; - while (light.Color.A > 0.0f && flashDuration > 0.0f) + while (light.Color.A > 0.0f && flashDuration > 0.0f && currBrightness > 0.0f) { light.Color = new Color(light.Color.R, light.Color.G, light.Color.B, (byte)(currBrightness * 255)); currBrightness -= 1.0f / flashDuration * CoroutineManager.DeltaTime; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/CaveGenerator.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/CaveGenerator.cs index 7fa3a7021..9a071dfb8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/CaveGenerator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/CaveGenerator.cs @@ -92,7 +92,7 @@ namespace Barotrauma DebugConsole.ThrowError("Invalid left normal"); #endif GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidLeftNormal:" + level.Seed, - GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, + GameAnalyticsManager.ErrorSeverity.Warning, "Invalid left normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + leftNormal + ", seed: " + level.Seed + ")"); if (cell.Body != null) @@ -127,7 +127,7 @@ namespace Barotrauma DebugConsole.ThrowError("Invalid right normal"); #endif GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidRightNormal:" + level.Seed, - GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, + GameAnalyticsManager.ErrorSeverity.Warning, "Invalid right normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + rightNormal + ", seed: " + level.Seed + ")"); if (cell.Body != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs index f178bf7f5..f7826794f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs @@ -497,11 +497,13 @@ namespace Barotrauma.Lights return true; } + private readonly Dictionary visibleHulls = new Dictionary(); private Dictionary GetVisibleHulls(Camera cam) { - Dictionary visibleHulls = new Dictionary(); + visibleHulls.Clear(); foreach (Hull hull in Hull.hullList) { + if (hull.HiddenInGame) { continue; } var drawRect = hull.Submarine == null ? hull.Rect : diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs index f160435ea..d8df8b2a9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs @@ -536,7 +536,7 @@ namespace Barotrauma invalidMessage = true; string errorMsg = $"Error while reading a network event for the structure \"{Name} ({ID})\". Section count does not match (server: {sectionCount} client: {Sections.Length})"; DebugConsole.NewMessage(errorMsg, Color.Red); - GameAnalyticsManager.AddErrorEventOnce("Structure.ClientRead:SectionCountMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Structure.ClientRead:SectionCountMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } for (int i = 0; i < sectionCount; i++) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs index 6d4db8e52..2890e470f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs @@ -98,7 +98,7 @@ namespace Barotrauma { string errorMsg = "Error when loading round sound (" + element + ") - file path not set"; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FilePathEmpty" + element.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FilePathEmpty" + element.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); return null; } @@ -124,7 +124,7 @@ namespace Barotrauma { string errorMsg = "Failed to load sound file \"" + filename + "\"."; DebugConsole.ThrowError(errorMsg, e); - GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FileNotFound" + filename, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FileNotFound" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); return null; } } @@ -148,7 +148,7 @@ namespace Barotrauma { string errorMsg = "Failed to load sound file \"" + roundSound.Filename + "\"."; DebugConsole.ThrowError(errorMsg, e); - GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FileNotFound" + roundSound.Filename, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FileNotFound" + roundSound.Filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); return; } } @@ -183,13 +183,14 @@ namespace Barotrauma visibleSubs.Clear(); foreach (Submarine sub in Loaded) { - if (sub.WorldPosition.Y < Level.MaxEntityDepth) continue; + if (sub.WorldPosition.Y < Level.MaxEntityDepth) { continue; } + int margin = 500; Rectangle worldBorders = new Rectangle( - sub.Borders.X + (int)sub.WorldPosition.X - 500, - sub.Borders.Y + (int)sub.WorldPosition.Y + 500, - sub.Borders.Width + 1000, - sub.Borders.Height + 1000); + sub.VisibleBorders.X + (int)sub.WorldPosition.X - margin, + sub.VisibleBorders.Y + (int)sub.WorldPosition.Y + margin, + sub.VisibleBorders.Width + margin * 2, + sub.VisibleBorders.Height + margin * 2); if (RectsOverlap(worldBorders, cam.WorldView)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs index 9610cddd1..c50ae90a9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs @@ -26,7 +26,7 @@ namespace Barotrauma catch (Exception e) { DebugConsole.ThrowError("Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted.", e); - GameAnalyticsManager.AddErrorEventOnce("Submarine..ctor:PreviewImageLoadingFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.AddErrorEventOnce("Submarine..ctor:PreviewImageLoadingFailed", GameAnalyticsManager.ErrorSeverity.Error, "Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted."); PreviewImage = null; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 0b7d8e563..620feacd3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -649,7 +649,7 @@ namespace Barotrauma.Networking { errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace(); } - GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); DebugConsole.ThrowError("Error while reading a message from server.", e); new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", new string[2] { "[message]", "[targetsite]" }, new string[2] { e.Message, e.TargetSite.ToString() })); Disconnect(); @@ -779,7 +779,7 @@ namespace Barotrauma.Networking #if DEBUG DebugConsole.ThrowError("Error while reading an ingame update message from server.", e); #endif - GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); throw; } break; @@ -791,7 +791,7 @@ namespace Barotrauma.Networking errorMsg += "\n" + Environment.StackTrace.CleanupStackTrace(); GameAnalyticsManager.AddErrorEventOnce( "GameClient.ReadDataMessage:VoipClientNull", - GameMain.Client == null ? GameAnalyticsSDK.Net.EGAErrorSeverity.Error : GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, + GameMain.Client == null ? GameAnalyticsManager.ErrorSeverity.Error : GameAnalyticsManager.ErrorSeverity.Warning, errorMsg); return; } @@ -989,7 +989,7 @@ namespace Barotrauma.Networking { string errorMsg = "Submarine equality check failed. The submarine loaded at your end doesn't match the one loaded by the server." + " There may have been an error in receiving the up-to-date submarine file from the server."; - GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:SubsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:SubsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); throw new Exception(errorMsg); } @@ -1010,7 +1010,7 @@ namespace Barotrauma.Networking if (!GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier).OrderBy(id => id).SequenceEqual(serverMissionIdentifiers.OrderBy(id => id))) { string errorMsg = $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server (server: {string.Join(", ", serverMissionIdentifiers)}, client: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})"; - GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); throw new Exception(errorMsg); } GameMain.GameSession.EnforceMissionOrder(serverMissionIdentifiers); @@ -1031,7 +1031,7 @@ namespace Barotrauma.Networking ", seed: " + Level.Loaded.Seed + ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortHash + ")" + ", mirrored: " + Level.Loaded.Mirrored + ")."; - GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); throw new Exception(errorMsg); } else @@ -1047,7 +1047,7 @@ namespace Barotrauma.Networking ", seed: " + Level.Loaded.Seed + ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortHash + ")" + ", mirrored: " + Level.Loaded.Mirrored + ")."; - GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); throw new Exception(errorMsg); } } @@ -1117,8 +1117,8 @@ namespace Barotrauma.Networking { GameAnalyticsManager.AddErrorEventOnce( "GameClient.HandleDisconnectMessage", - GameAnalyticsSDK.Net.EGAErrorSeverity.Debug, - "Client received a disconnect message. Reason: " + disconnectReason.ToString() + ", message: " + disconnectMsg); + GameAnalyticsManager.ErrorSeverity.Debug, + "Client received a disconnect message. Reason: " + disconnectReason.ToString()); } if (disconnectReason == DisconnectReason.ServerFull) @@ -1532,7 +1532,7 @@ namespace Barotrauma.Networking gameStarted = true; GameMain.NetLobbyScreen.Select(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectSub" + subName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectSub" + subName, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); roundInitStatus = RoundInitStatus.Interrupted; yield return CoroutineStatus.Failure; } @@ -1544,7 +1544,7 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.Select(); string errorMsg = "Failed to select shuttle \"" + shuttleName + "\" (hash: " + shuttleHash + ")."; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectShuttle" + shuttleName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectShuttle" + shuttleName, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); roundInitStatus = RoundInitStatus.Interrupted; yield return CoroutineStatus.Failure; } @@ -2116,9 +2116,6 @@ namespace Barotrauma.Networking bool autoRestartEnabled = inc.ReadBoolean(); float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f; - bool radiationEnabled = inc.ReadBoolean(); - byte maxMissionCount = inc.ReadByte(); - //ignore the message if we already a more up-to-date one //or if we're still waiting for the initial update if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID) && @@ -2168,22 +2165,15 @@ namespace Barotrauma.Networking if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, "campaign")) { GameMain.NetLobbyScreen.CampaignSubmarines.Add(sub); - } - } - - if (HasPermission(ClientPermissions.ManageCampaign) && !gameStarted && GameMain.NetLobbyScreen?.CampaignSetupUI != null) - { - GameMain.NetLobbyScreen.CampaignSetupUI.RefreshMultiplayerCampaignSubUI(GameMain.NetLobbyScreen.CampaignSubmarines); + } } } GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating); GameMain.NetLobbyScreen.LevelSeed = levelSeed; GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty); - GameMain.NetLobbyScreen.SetRadiationEnabled(radiationEnabled); GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode); GameMain.NetLobbyScreen.SetBotCount(botCount); - GameMain.NetLobbyScreen.SetMaxMissionCount(maxMissionCount); GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer); serverSettings.VoiceChatEnabled = voiceChatEnabled; @@ -2355,7 +2345,7 @@ namespace Barotrauma.Networking { errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text); } - GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, string.Join("\n", errorLines)); + GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsManager.ErrorSeverity.Critical, string.Join("\n", errorLines)); DebugConsole.ThrowError("Writing object data to \"networkerror_data.log\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues"); @@ -2591,29 +2581,29 @@ namespace Barotrauma.Networking subElement.ToolTip = newSub.Description; } - if (GameMain.NetLobbyScreen.FailedSelectedSub != null && - GameMain.NetLobbyScreen.FailedSelectedSub.First == newSub.Name && - GameMain.NetLobbyScreen.FailedSelectedSub.Second == newSub.MD5Hash.Hash) + if (GameMain.NetLobbyScreen.FailedSelectedSub.HasValue && + GameMain.NetLobbyScreen.FailedSelectedSub.Value.Name == newSub.Name && + GameMain.NetLobbyScreen.FailedSelectedSub.Value.Hash == newSub.MD5Hash.Hash) { GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.Hash, GameMain.NetLobbyScreen.SubList); } - if (GameMain.NetLobbyScreen.FailedSelectedShuttle != null && - GameMain.NetLobbyScreen.FailedSelectedShuttle.First == newSub.Name && - GameMain.NetLobbyScreen.FailedSelectedShuttle.Second == newSub.MD5Hash.Hash) + if (GameMain.NetLobbyScreen.FailedSelectedShuttle.HasValue && + GameMain.NetLobbyScreen.FailedSelectedShuttle.Value.Name == newSub.Name && + GameMain.NetLobbyScreen.FailedSelectedShuttle.Value.Hash == newSub.MD5Hash.Hash) { GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.Hash, GameMain.NetLobbyScreen.ShuttleList.ListBox); } - Pair failedCampaignSub = GameMain.NetLobbyScreen.FailedCampaignSubs.Find(s => s.First == newSub.Name && s.Second == newSub.MD5Hash.Hash); - if (failedCampaignSub != null) + NetLobbyScreen.FailedSubInfo failedCampaignSub = GameMain.NetLobbyScreen.FailedCampaignSubs.Find(s => s.Name == newSub.Name && s.Hash == newSub.MD5Hash.Hash); + if (failedCampaignSub != default) { GameMain.NetLobbyScreen.CampaignSubmarines.Add(newSub); GameMain.NetLobbyScreen.FailedCampaignSubs.Remove(failedCampaignSub); } - Pair failedOwnedSub = GameMain.NetLobbyScreen.FailedOwnedSubs.Find(s => s.First == newSub.Name && s.Second == newSub.MD5Hash.Hash); - if (failedOwnedSub != null) + NetLobbyScreen.FailedSubInfo failedOwnedSub = GameMain.NetLobbyScreen.FailedOwnedSubs.Find(s => s.Name == newSub.Name && s.Hash == newSub.MD5Hash.Hash); + if (failedOwnedSub != default) { GameMain.NetLobbyScreen.ServerOwnedSubmarines.Add(newSub); GameMain.NetLobbyScreen.FailedOwnedSubs.Remove(failedOwnedSub); @@ -2946,7 +2936,6 @@ namespace Barotrauma.Networking IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.SelectSub); - msg.Write(false); msg.Write(isShuttle); msg.WritePadBits(); msg.Write((UInt16)subIndex); msg.Write((byte)ServerNetObject.END_OF_MESSAGE); @@ -2954,23 +2943,6 @@ namespace Barotrauma.Networking clientPeer.Send(msg, DeliveryMethod.Reliable); } - /// - /// Tell the server to add / remove a purchasable submarine (permission required) - /// - public void RequestCampaignSub(SubmarineInfo sub, bool add) - { - if (!HasPermission(ClientPermissions.SelectSub) || sub == null) return; - IWriteMessage msg = new WriteOnlyMessage(); - msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); - msg.Write((UInt16)ClientPermissions.SelectSub); - msg.Write(true); - msg.Write(sub.EqualityCheckVal); - msg.Write(add); - msg.Write((byte)ServerNetObject.END_OF_MESSAGE); - - clientPeer.Send(msg, DeliveryMethod.Reliable); - } - /// /// Tell the server to select a mode (permission required) /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs index 23bcbdeea..893cfe2e2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -233,7 +233,7 @@ namespace Barotrauma.Networking DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); //TODO: force the BitPosition to correct place? Having some entity in a potentially incorrect state is not as bad as a desync kick //msg.BitPosition = (int)(msgPosition + msgLength * 8); @@ -252,7 +252,7 @@ namespace Barotrauma.Networking DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e); GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.ErrorSeverity.Error, errorMsg); msg.BitPosition = (int)(msgPosition + msgLength * 8); msg.ReadPadBits(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs index cd1c81315..0aacf802e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs @@ -115,13 +115,11 @@ namespace Barotrauma.Networking { if (!isActive) { return; } - byte incByte = inc.ReadByte(); - bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0; - bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0; + PacketHeader packetHeader = (PacketHeader)inc.ReadByte(); //Console.WriteLine(isCompressed + " " + isConnectionInitializationStep + " " + (int)incByte); - if (isConnectionInitializationStep && initializationStep != ConnectionInitialization.Success) + if (packetHeader.IsConnectionInitializationStep() && initializationStep != ConnectionInitialization.Success) { ReadConnectionInitializationStep(new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false)); } @@ -133,7 +131,7 @@ namespace Barotrauma.Networking initializationStep = ConnectionInitialization.Success; } UInt16 length = inc.ReadUInt16(); - IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, ServerConnection); + IReadMessage msg = new ReadOnlyMessage(inc.Data, packetHeader.IsCompressed(), inc.PositionInBytes, length, ServerConnection); OnMessageReceived?.Invoke(msg); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs index 911d2eb23..70e9b0728 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs @@ -112,33 +112,28 @@ namespace Barotrauma.Networking NetworkConnection.TimeoutThresholdInGame : NetworkConnection.TimeoutThreshold; - byte incByte = data[0]; - bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0; - bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0; - bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0; - bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0; - bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0; + PacketHeader packetHeader = (PacketHeader)data[0]; - if (!isServerMessage) { return; } + if (!packetHeader.IsServerMessage()) { return; } - if (isConnectionInitializationStep) + if (packetHeader.IsConnectionInitializationStep()) { ulong low = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8); ulong high = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8 + 32); ulong lobbyId = low + (high << 32); Steam.SteamManager.JoinLobby(lobbyId, false); - IReadMessage inc = new ReadOnlyMessage(data, false, 1 + 8, dataLength - 9, ServerConnection); + IReadMessage inc = new ReadOnlyMessage(data, false, 1 + 8, dataLength - (1 + 8), ServerConnection); if (initializationStep != ConnectionInitialization.Success) { incomingInitializationMessages.Add(inc); } } - else if (isHeartbeatMessage) + else if (packetHeader.IsHeartbeatMessage()) { return; //TODO: implement heartbeats } - else if (isDisconnectMessage) + else if (packetHeader.IsDisconnectMessage()) { IReadMessage inc = new ReadOnlyMessage(data, false, 1, dataLength - 1, ServerConnection); string msg = inc.ReadString(); @@ -147,10 +142,9 @@ namespace Barotrauma.Networking } else { - UInt16 length = data[1]; - length |= (UInt16)(((UInt32)data[2]) << 8); + UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(data, 16, 8); - IReadMessage inc = new ReadOnlyMessage(data, isCompressed, 3, length, ServerConnection); + IReadMessage inc = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 3, length, ServerConnection); incomingDataMessages.Add(inc); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs index 19b81611c..9f4bc841b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs @@ -147,18 +147,13 @@ namespace Barotrauma.Networking DeliveryMethod deliveryMethod = (DeliveryMethod)data[0]; - byte incByte = data[1]; - bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0; - bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0; - bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0; - bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0; - bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0; + PacketHeader packetHeader = (PacketHeader)data[1]; - if (!remotePeer.Authenticated & !remotePeer.Authenticating && isConnectionInitializationStep) + if (!remotePeer.Authenticated & !remotePeer.Authenticating && packetHeader.IsConnectionInitializationStep()) { remotePeer.DisconnectTime = null; - IReadMessage authMsg = new ReadOnlyMessage(data, isCompressed, 2, dataLength - 2, null); + IReadMessage authMsg = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 2, dataLength - 2, null); ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte(); if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) { @@ -242,17 +237,11 @@ namespace Barotrauma.Networking int p2pDataStart = inc.BytePosition; - byte incByte = inc.ReadByte(); - - bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0; - bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0; - bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0; - bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0; - bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0; + PacketHeader packetHeader = (PacketHeader)inc.ReadByte(); if (recipientSteamId != selfSteamID) { - if (!isServerMessage) + if (!packetHeader.IsServerMessage()) { DebugConsole.ThrowError("Received non-server message meant for remote peer"); return; @@ -262,7 +251,7 @@ namespace Barotrauma.Networking if (peer == null) { return; } - if (isDisconnectMessage) + if (packetHeader.IsDisconnectMessage()) { DisconnectPeer(peer, inc.ReadString()); return; @@ -273,8 +262,8 @@ namespace Barotrauma.Networking { case DeliveryMethod.Reliable: case DeliveryMethod.ReliableOrdered: - //the documentation seems to suggest that the Reliable send type - //enforces packet order (TODO: verify) + //the documentation seems to suggest that the + //Reliable send type enforces packet order sendType = Steamworks.P2PSend.Reliable; break; default: @@ -284,17 +273,31 @@ namespace Barotrauma.Networking byte[] p2pData; - if (isConnectionInitializationStep) + if (packetHeader.IsConnectionInitializationStep()) { p2pData = new byte[inc.LengthBytes - p2pDataStart + 8]; p2pData[0] = inc.Buffer[p2pDataStart]; - Lidgren.Network.NetBitWriter.WriteUInt64(SteamManager.CurrentLobbyID, 64, p2pData, 8); - Array.Copy(inc.Buffer, p2pDataStart+1, p2pData, 9, inc.LengthBytes - p2pDataStart - 1); + Lidgren.Network.NetBitWriter.WriteUInt64(SteamManager.CurrentLobbyID, 8 * 8, p2pData, 1 * 8); + Array.Copy(inc.Buffer, p2pDataStart+1, p2pData, 1 + 8, inc.LengthBytes - p2pDataStart - 1); } else { p2pData = new byte[inc.LengthBytes - p2pDataStart]; Array.Copy(inc.Buffer, p2pDataStart, p2pData, 0, p2pData.Length); + + if (!packetHeader.IsHeartbeatMessage() && !packetHeader.IsDisconnectMessage()) + { + UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(p2pData, 16, 8); + if (length > p2pData.Length - 2) + { + string errorMsg = $"Length written in message to send to client is larger than buffer size ({length} > {p2pData.Length - 2})"; + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce( + "SteamP2POwnerPeerLengthValidationFail", + GameAnalyticsManager.ErrorSeverity.Error, + errorMsg); + } + } } if (p2pData.Length + 4 >= MsgConstants.MTU) @@ -323,21 +326,21 @@ namespace Barotrauma.Networking } else { - if (isDisconnectMessage) + if (packetHeader.IsDisconnectMessage()) { DebugConsole.ThrowError("Received disconnect message from owned server"); return; } - if (!isServerMessage) + if (!packetHeader.IsServerMessage()) { DebugConsole.ThrowError("Received non-server message from owned server"); return; } - if (isHeartbeatMessage) + if (packetHeader.IsHeartbeatMessage()) { - return; //timeout is handled by Lidgren, ignore this message + return; //no timeout since we're using pipes, ignore this message } - if (isConnectionInitializationStep) + if (packetHeader.IsConnectionInitializationStep()) { IWriteMessage outMsg = new WriteOnlyMessage(); outMsg.Write(selfSteamID); @@ -358,7 +361,7 @@ namespace Barotrauma.Networking initializationStep = ConnectionInitialization.Success; } UInt16 length = inc.ReadUInt16(); - IReadMessage msg = new ReadOnlyMessage(inc.Buffer, isCompressed, inc.BytePosition, length, ServerConnection); + IReadMessage msg = new ReadOnlyMessage(inc.Buffer, packetHeader.IsCompressed(), inc.BytePosition, length, ServerConnection); OnMessageReceived?.Invoke(msg); return; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerLog.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerLog.cs index 03e0618e0..8e18c1c82 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerLog.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerLog.cs @@ -242,7 +242,8 @@ namespace Barotrauma.Networking textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea() { Data = data, - OnClick = GameMain.NetLobbyScreen.SelectPlayer + OnClick = GameMain.NetLobbyScreen.SelectPlayer, + OnSecondaryClick = GameMain.NetLobbyScreen.ShowPlayerContextMenu }); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs index 4b9497ab8..16c351b4c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs @@ -92,8 +92,9 @@ namespace Barotrauma.Networking } } - public void ClientAdminRead(IReadMessage incMsg) + public void ClientAdminRead(IReadMessage incMsg, NetFlags requiredFlags) { + if (!requiredFlags.HasFlag(NetFlags.Properties)) { return; } int count = incMsg.ReadUInt16(); for (int i = 0; i < count; i++) { @@ -128,8 +129,18 @@ namespace Barotrauma.Networking { cachedServerListInfo = null; - ServerName = incMsg.ReadString(); - ServerMessageText = incMsg.ReadString(); + NetFlags requiredFlags = (NetFlags)incMsg.ReadByte(); + + if (requiredFlags.HasFlag(NetFlags.Name)) + { + ServerName = incMsg.ReadString(); + } + + if (requiredFlags.HasFlag(NetFlags.Message)) + { + ServerMessageText = incMsg.ReadString(); + } + PlayStyle = (PlayStyle)incMsg.ReadByte(); MaxPlayers = incMsg.ReadByte(); HasPassword = incMsg.ReadBoolean(); IsPublic = incMsg.ReadBoolean(); @@ -139,23 +150,23 @@ namespace Barotrauma.Networking TickRate = incMsg.ReadRangedInteger(1, 60); GameMain.NetworkMember.TickRate = TickRate; - ReadExtraCargo(incMsg); - + if (requiredFlags.HasFlag(NetFlags.Properties)) + { + ReadExtraCargo(incMsg); + } + ReadHiddenSubs(incMsg); - GameMain.NetLobbyScreen.UpdateSubVisibility(); - Voting.ClientRead(incMsg); - bool isAdmin = incMsg.ReadBoolean(); incMsg.ReadPadBits(); if (isAdmin) { - ClientAdminRead(incMsg); + ClientAdminRead(incMsg, requiredFlags); } } - public void ClientAdminWrite(NetFlags dataToSend, int? missionTypeOr = null, int? missionTypeAnd = null, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0, bool? radiationEnabled = null, bool? useRespawnShuttle = null, int maxMissionCount = 0) + public void ClientAdminWrite(NetFlags dataToSend, int? missionTypeOr = null, int? missionTypeAnd = null, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0, bool? useRespawnShuttle = null) { if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return; @@ -225,8 +236,6 @@ namespace Barotrauma.Networking outMsg.Write(autoRestart != null); outMsg.Write(autoRestart ?? false); - outMsg.Write(radiationEnabled ?? RadiationEnabled); - outMsg.Write((byte)maxMissionCount + 1); outMsg.WritePadBits(); } @@ -711,7 +720,6 @@ namespace Barotrauma.Networking RelativeSpacing = 0.05f }; - if (ip.InventoryIcon != null || ip.sprite != null) { GUIImage img = new GUIImage(new RectTransform(new Point(itemFrame.Rect.Height), itemFrame.RectTransform), @@ -734,7 +742,7 @@ namespace Barotrauma.Networking GUINumberInput.NumberType.Int, textAlignment: Alignment.CenterLeft) { MinValueInt = 0, - MaxValueInt = 100, + MaxValueInt = MaxExtraCargoItemsOfType, IntValue = cargoVal }; amountInput.OnValueChanged += (numberInput) => @@ -742,16 +750,26 @@ namespace Barotrauma.Networking if (ExtraCargo.ContainsKey(ip)) { ExtraCargo[ip] = numberInput.IntValue; - if (numberInput.IntValue <= 0) ExtraCargo.Remove(ip); + if (numberInput.IntValue <= 0) { ExtraCargo.Remove(ip); } } - else + else if (ExtraCargo.Keys.Count() < MaxExtraCargoItemTypes) { ExtraCargo.Add(ip, numberInput.IntValue); } + numberInput.IntValue = ExtraCargo.ContainsKey(ip) ? ExtraCargo[ip] : 0; + CoroutineManager.Invoke(() => + { + foreach (var child in cargoFrame.Content.GetAllChildren()) + { + if (child.GetChild() is GUINumberInput otherNumberInput) + { + otherNumberInput.PlusButton.Enabled = ExtraCargo.Keys.Count() < MaxExtraCargoItemTypes && otherNumberInput.IntValue < otherNumberInput.MaxValueInt; + } + } + }, 0.0f); }; } - //-------------------------------------------------------------------------------- // antigriefing //-------------------------------------------------------------------------------- @@ -913,6 +931,7 @@ namespace Barotrauma.Networking //-------------------------------------------------------------------------------- Whitelist.CreateWhiteListFrame(settingsTabs[(int)SettingsTab.Whitelist]); + Whitelist.localEnabled = Whitelist.Enabled; } private void CreateLabeledSlider(GUIComponent parent, string labelTag, out GUIScrollBar slider, out GUITextBlock label) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs index d71b3868f..29295f520 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs @@ -147,7 +147,14 @@ namespace Barotrauma.Steam if (currentLobby == null) { - DebugConsole.ThrowError("Failed to create Steam lobby"); + DebugConsole.ThrowError("Failed to create Steam lobby: returned lobby was null"); + lobbyState = LobbyState.NotConnected; + return; + } + + if (currentLobby.Value.Result != Steamworks.Result.OK) + { + DebugConsole.ThrowError($"Failed to create Steam lobby: result was {currentLobby.Value.Result}"); lobbyState = LobbyState.NotConnected; return; } @@ -525,18 +532,6 @@ namespace Barotrauma.Steam } #region Connecting to servers - private static Steamworks.AuthTicket currentTicket = null; - public static Steamworks.AuthTicket GetAuthSessionTicket() - { - if (!isInitialized) - { - return null; - } - - currentTicket?.Cancel(); - currentTicket = Steamworks.SteamUser.GetAuthSessionTicket(); - return currentTicket; - } public static Steamworks.BeginAuthResult StartAuthSession(byte[] authTicketData, ulong clientSteamID) { @@ -884,9 +879,9 @@ namespace Barotrauma.Steam catch (Exception e) { - string errorMsg = "Failed to save workshop item preview image to \"" + previewImagePath + "\" when creating workshop item staging folder."; + string errorMsg = "Failed to save workshop item preview image when creating workshop item staging folder."; GameAnalyticsManager.AddErrorEventOnce("SteamManager.CreateWorkshopItemStaging:WriteAllBytesFailed" + previewImagePath, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + e.Message); + GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + e.Message); } return true; @@ -1434,8 +1429,8 @@ namespace Barotrauma.Steam "\" not found. Could not combine path (" + (item.Directory ?? "directory name empty") + ")."; DebugConsole.ThrowError(errorMessage); GameAnalyticsManager.AddErrorEventOnce("SteamManager.CheckWorkshopItemInstalled:PathCombineException" + item.Title, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - errorMessage); + GameAnalyticsManager.ErrorSeverity.Error, + "Metadata file for a Workshop item not found. Could not combine path."); return false; } @@ -1567,8 +1562,8 @@ namespace Barotrauma.Steam } GameAnalyticsManager.AddErrorEventOnce( "SteamManager.AutoUpdateWorkshopItems:" + e.Message, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Failed to autoupdate workshop item \"" + item.Title + "\". " + e.Message + "\n" + e.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.ErrorSeverity.Error, + "Failed to autoupdate workshop item. " + e.Message + "\n" + e.StackTrace.CleanupStackTrace()); }); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs index 49058b640..a32ef3bd0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs @@ -113,7 +113,7 @@ namespace Barotrauma.Networking UserData = "capturedevicenotfound" }; } - GameAnalyticsManager.AddErrorEventOnce("Alc.CaptureDeviceOpenFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.AddErrorEventOnce("Alc.CaptureDeviceOpenFailed", GameAnalyticsManager.ErrorSeverity.Error, "Alc.CaptureDeviceOpen(" + deviceName + ") failed. Error code: " + errorCode); GameMain.Config.VoiceSetting = GameSettings.VoiceMode.Disabled; Instance?.Dispose(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/WhiteList.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/WhiteList.cs index 0406f6703..554d32169 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/WhiteList.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/WhiteList.cs @@ -56,17 +56,12 @@ namespace Barotrauma.Networking { nameBox.Enabled = box.Selected; ipBox.Enabled = box.Selected; - addNewButton.Enabled = box.Selected; - + addNewButton.Enabled = box.Selected && !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text); localEnabled = box.Selected; - - return true; } }; - localEnabled = Enabled; - var listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), whitelistFrame.RectTransform)); foreach (WhiteListedPlayer wlp in whitelistedPlayers) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs index 1ecd582e7..0f535c9d7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs @@ -136,7 +136,7 @@ namespace Barotrauma.Particles Prefab = prefab; } - public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, Tuple tracerPoints = null) + public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, bool mirrorAngle = false, Tuple tracerPoints = null) { if (GameMain.Client?.MidRoundSyncing ?? false) { return; } @@ -159,7 +159,7 @@ namespace Barotrauma.Particles for (float z = 0.0f; z < dist; z += Prefab.Properties.EmitAcrossRayInterval) { Vector2 pos = tracerPoints.Item1 + dir * z; - Emit(pos, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: null); + Emit(pos, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: null); } } } @@ -169,7 +169,7 @@ namespace Barotrauma.Particles float emitInterval = 1.0f / Prefab.Properties.ParticlesPerSecond; while (emitTimer > emitInterval) { - Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: tracerPoints); + Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: tracerPoints); emitTimer -= emitInterval; } } @@ -183,7 +183,7 @@ namespace Barotrauma.Particles } } - private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, Tuple tracerPoints = null) + private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, bool mirrorAngle = false, Tuple tracerPoints = null) { var particlePrefab = overrideParticle ?? Prefab.ParticlePrefab; if (particlePrefab == null) { return; } @@ -191,7 +191,7 @@ namespace Barotrauma.Particles Vector2 velocity = Vector2.Zero; if (!MathUtils.NearlyEqual(Prefab.Properties.VelocityMax * velocityMultiplier, 0.0f) || !MathUtils.NearlyEqual(Prefab.Properties.DistanceMax, 0.0f)) { - angle += Rand.Range(Prefab.Properties.AngleMinRad, Prefab.Properties.AngleMaxRad); + angle += Rand.Range(Prefab.Properties.AngleMinRad, Prefab.Properties.AngleMaxRad) * (mirrorAngle ? -1 : 1); Vector2 dir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)); velocity = dir * Rand.Range(Prefab.Properties.VelocityMin, Prefab.Properties.VelocityMax) * velocityMultiplier; position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaClient/ClientSource/Physics/PhysicsBody.cs index 9d444cd03..989e459a4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Physics/PhysicsBody.cs @@ -201,7 +201,7 @@ namespace Barotrauma DebugConsole.ThrowError(errorMsg); #endif GameAnalyticsManager.AddErrorEventOnce("PhysicsBody.ClientRead:InvalidData" + parentDebugName, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return null; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Program.cs b/Barotrauma/BarotraumaClient/ClientSource/Program.cs index e612e92f1..cbc8188bf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Program.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Program.cs @@ -1,11 +1,9 @@ #region Using Statements using System; -using System.Collections.Generic; using Barotrauma.IO; using System.Linq; using System.Text; -using GameAnalyticsSDK.Net; using Barotrauma.Steam; using System.Diagnostics; using System.Runtime.InteropServices; @@ -245,6 +243,13 @@ namespace Barotrauma } } + if (GameAnalyticsManager.SendUserStatistics) + { + //send crash report before appending debug console messages (which may contain non-anonymous information) + GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Critical, sb.ToString()); + GameAnalyticsManager.ShutDown(); + } + sb.AppendLine("Last debug messages:"); for (int i = DebugConsole.Messages.Count - 1; i >= 0; i--) { @@ -257,11 +262,9 @@ namespace Barotrauma if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging) { DebugConsole.SaveLogs(); } - if (GameSettings.SendUserStatistics) + if (GameAnalyticsManager.SendUserStatistics) { CrashMessageBox("A crash report (\"" + filePath + "\") was saved in the root folder of the game and sent to the developers.", filePath); - GameAnalytics.AddErrorEvent(EGAErrorSeverity.Critical, crashReport); - GameAnalytics.OnQuit(); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs index 81ead93e1..ff0f4ffc2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs @@ -7,14 +7,10 @@ namespace Barotrauma { protected readonly GUIComponent newGameContainer, loadGameContainer; - protected GUIListBox subList; protected GUIListBox saveList; - protected List subTickBoxes; protected GUITextBox saveNameBox, seedBox; - protected GUILayoutGroup subPreviewContainer; - protected GUIButton loadGameButton; public Action StartNewGame; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs index 289937c2b..afe8fb55a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Xml.Linq; using System.Globalization; using Barotrauma.Extensions; +using Barotrauma.Networking; namespace Barotrauma { @@ -14,55 +15,79 @@ namespace Barotrauma { private GUIButton deleteMpSaveButton; - public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable submarines, IEnumerable saveFiles = null) + public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable saveFiles = null) : base(newGameContainer, loadGameContainer) { - var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true) + var verticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: false) { Stretch = true, RelativeSpacing = 0.0f }; - var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform)) - { - Stretch = true, - RelativeSpacing = 0.015f - }; - - var rightColumn = new GUILayoutGroup(new RectTransform(Vector2.Zero, columnContainer.RectTransform)) - { - Stretch = true, - RelativeSpacing = 0.015f - }; - - columnContainer.Recalculate(); - - // New game left side - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUI.SubHeadingFont); - saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, string.Empty) + // New game + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft); + saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, string.Empty) { textFilterFunction = (string str) => { return ToolBox.RemoveInvalidFileNameChars(str); } }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUI.SubHeadingFont); - seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8)); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft); + seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8)); - // Spacing to fix the multiplayer campaign setup layout - CreateMultiplayerCampaignSubList(leftColumn.RectTransform); - - //spacing - //new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform), style: null); - - // New game right side - subPreviewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)) + GUIFrame radiationBoxContainer + = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), verticalLayout.RectTransform), style: null); + GUITickBox radiationEnabledTickBox = null; + if (MapGenerationParams.Instance.RadiationParams != null) { - Stretch = true + radiationEnabledTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.5f), radiationBoxContainer.RectTransform, Anchor.Center), TextManager.Get("CampaignOption.EnableRadiation"), font: GUI.Style.Font) + { + Selected = true, + OnSelected = box => true + }; + } + + var maxMissionCountSettingHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), verticalLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; + var maxMissionCountDescription = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), maxMissionCountSettingHolder.RectTransform), TextManager.Get("maxmissioncount", fallBackTag: "missions"), wrap: true) + { + ToolTip = TextManager.Get("maxmissioncounttooltip") }; + int maxMissionCount = GameMain.NetworkMember.ServerSettings.MaxMissionCount; + var maxMissionCountContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), maxMissionCountSettingHolder.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { RelativeSpacing = 0.05f, Stretch = true }; + var maxMissionCountButtons = new GUIButton[2]; + maxMissionCountButtons[0] + = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), maxMissionCountContainer.RectTransform), + style: "GUIButtonToggleLeft"); + var maxMissionCountText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), maxMissionCountContainer.RectTransform), "0", textAlignment: Alignment.Center, style: "GUITextBox"); - var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.12f), - leftColumn.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true); + void updateMissionCountText() + { + maxMissionCount = MathHelper.Clamp(maxMissionCount, + CampaignSettings.MinMissionCountLimit, + CampaignSettings.MaxMissionCountLimit); + maxMissionCountText.Text = maxMissionCount.ToString(CultureInfo.InvariantCulture); + } + maxMissionCountButtons[1] + = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), maxMissionCountContainer.RectTransform), + style: "GUIButtonToggleRight"); + maxMissionCountButtons[0].OnClicked = (button, o) => + { + maxMissionCount--; + updateMissionCountText(); + return false; + }; + maxMissionCountButtons[1].OnClicked = (button, o) => + { + maxMissionCount++; + updateMissionCountText(); + return false; + }; + updateMissionCountText(); + maxMissionCountSettingHolder.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip); - StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), buttonContainer.RectTransform, Anchor.BottomRight) { MaxSize = new Point(350, 60) }, TextManager.Get("StartCampaignButton")) + var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.04f), + verticalLayout.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true); + + StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), buttonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("StartCampaignButton")) { OnClicked = (GUIButton btn, object userData) => { @@ -85,20 +110,19 @@ namespace Barotrauma if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash)) { - ((GUITextBlock)subList.SelectedComponent).TextColor = Color.DarkRed * 0.8f; - subList.SelectedComponent.CanBeFocused = false; - subList.Deselect(); + new GUIMessageBox(TextManager.Get("error"), TextManager.Get("nohashsubmarineselected")); return false; } string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveNameBox.Text); bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled; - CampaignSettings settings = new CampaignSettings(); + CampaignSettings settings = new CampaignSettings + { + RadiationEnabled = radiationEnabledTickBox?.Selected ?? GameMain.NetworkMember.ServerSettings.RadiationEnabled, + MaxMissionCount = maxMissionCount + }; - settings.RadiationEnabled = GameMain.NetLobbyScreen.IsRadiationEnabled(); - settings.MaxMissionCount = GameMain.NetLobbyScreen.GetMaxMissionCount(); - if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages) { if (!hasRequiredContentPackages) @@ -148,7 +172,9 @@ namespace Barotrauma return true; } }; - + StartButton.RectTransform.MaxSize = RectTransform.MaxPoint; + StartButton.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint); + InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), buttonContainer.RectTransform), "", font: GUI.Style.SmallFont, textColor: GUI.Style.Green) { TextGetter = () => @@ -163,114 +189,11 @@ namespace Barotrauma } }; - columnContainer.Recalculate(); - leftColumn.Recalculate(); - rightColumn.Recalculate(); + verticalLayout.Recalculate(); - if (submarines != null) { UpdateSubList(submarines); } UpdateLoadMenu(saveFiles); } - private void CreateMultiplayerCampaignSubList(RectTransform parent) - { - GUILayoutGroup subHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.725f), parent)) - { - RelativeSpacing = 0.005f, - Stretch = true - }; - - var subLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), subHolder.RectTransform) { MinSize = new Point(0, 25) }, TextManager.Get("purchasablesubmarines", fallBackTag: "workshoplabelsubmarines"), font: GUI.SubHeadingFont); - - var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), subHolder.RectTransform), isHorizontal: true) - { - Stretch = true - }; - var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUI.Font); - var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUI.Font, createClearButton: true); - filterContainer.RectTransform.MinSize = searchBox.RectTransform.MinSize; - searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; }; - searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; }; - searchBox.OnTextChanged += (textBox, text) => - { - foreach (GUIComponent child in subList.Content.Children) - { - if (!(child.UserData is SubmarineInfo sub)) { continue; } - child.Visible = string.IsNullOrEmpty(text) ? true : sub.DisplayName.ToLower().Contains(text.ToLower()); - } - return true; - }; - - subList = new GUIListBox(new RectTransform(Vector2.One, subHolder.RectTransform)); - subTickBoxes = new List(); - - for (int i = 0; i < GameMain.Client.ServerSubmarines.Count; i++) - { - SubmarineInfo sub = GameMain.Client.ServerSubmarines[i]; - - if (!sub.IsCampaignCompatible) continue; - - var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), subList.Content.RectTransform) { MinSize = new Point(0, 20) }, - style: "ListBoxElement") - { - ToolTip = sub.Description, - UserData = sub - }; - - int buttonSize = (int)(frame.Rect.Height * 0.8f); - - GUITickBox tickBox = new GUITickBox(new RectTransform(new Vector2(0.8f, 1.0f), frame.RectTransform, Anchor.CenterLeft), ToolBox.LimitString(sub.DisplayName, GUI.Font, subList.Content.Rect.Width - 65)) - { - UserData = sub, - OnSelected = (GUITickBox box) => - { - GameMain.Client.RequestCampaignSub(box.UserData as SubmarineInfo, box.Selected); - return true; - } - }; - subTickBoxes.Add(tickBox); - tickBox.Selected = GameMain.NetLobbyScreen.CampaignSubmarines.Contains(sub); - - frame.RectTransform.MinSize = new Point(0, tickBox.RectTransform.MinSize.Y); - - var subTextBlock = tickBox.TextBlock; - - var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name && s.MD5Hash?.Hash == sub.MD5Hash?.Hash); - if (matchingSub == null) matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name); - - if (matchingSub == null) - { - subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f); - frame.ToolTip = TextManager.Get("SubNotFound"); - } - else if (matchingSub?.MD5Hash == null || matchingSub.MD5Hash?.Hash != sub.MD5Hash?.Hash) - { - subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f); - frame.ToolTip = TextManager.Get("SubDoesntMatch"); - } - - if (!sub.RequiredContentPackagesInstalled) - { - subTextBlock.TextColor = Color.Lerp(subTextBlock.TextColor, Color.DarkRed, 0.5f); - frame.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + frame.RawToolTip; - } - - var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight), - TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) - { - TextColor = subTextBlock.TextColor * 0.8f, - ToolTip = subTextBlock.RawToolTip - }; - } - } - - public void RefreshMultiplayerCampaignSubUI(List campaignSubs) - { - for (int i = 0; i < subTickBoxes.Count; i++) - { - subTickBoxes[i].Selected = campaignSubs.Contains(subTickBoxes[i].UserData as SubmarineInfo); - } - } - private IEnumerable WaitForCampaignSetup() { GUI.SetCursorWaiting(); @@ -298,64 +221,6 @@ namespace Barotrauma yield return CoroutineStatus.Success; } - public void UpdateSubList(IEnumerable submarines) - { - List subsToShow; - string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder); - subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && Path.GetDirectoryName(Path.GetFullPath(s.FilePath)) != downloadFolder).ToList(); - - subsToShow.Sort((s1, s2) => - { - int p1 = s1.Price > CampaignMode.InitialMoney ? 10 : 0; - int p2 = s2.Price > CampaignMode.InitialMoney ? 10 : 0; - return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name); - }); - - subList.ClearChildren(); - - foreach (SubmarineInfo sub in subsToShow) - { - var textBlock = new GUITextBlock( - new RectTransform(new Vector2(1, 0.1f), subList.Content.RectTransform) { MinSize = new Point(0, 30) }, - ToolBox.LimitString(sub.DisplayName, GUI.Font, subList.Rect.Width - 65), style: "ListBoxElement") - { - ToolTip = sub.Description, - UserData = sub - }; - - if (!sub.RequiredContentPackagesInstalled) - { - textBlock.TextColor = Color.Lerp(textBlock.TextColor, Color.DarkRed, .5f); - textBlock.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + textBlock.RawToolTip; - } - - var priceText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), - TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) - { - TextColor = sub.Price > CampaignMode.InitialMoney ? GUI.Style.Red : textBlock.TextColor * 0.8f, - ToolTip = textBlock.ToolTip - }; -#if !DEBUG - if (!GameMain.DebugDraw) - { - if (sub.Price > CampaignMode.InitialMoney || !sub.IsCampaignCompatible) - { - textBlock.CanBeFocused = false; - textBlock.TextColor *= 0.5f; - } - } -#endif - } - if (SubmarineInfo.SavedSubmarines.Any()) - { - var validSubs = subsToShow.Where(s => s.IsCampaignCompatible && s.Price <= CampaignMode.InitialMoney).ToList(); - if (validSubs.Count > 0) - { - subList.Select(validSubs[Rand.Int(validSubs.Count)]); - } - } - } - private List prevSaveFiles; public void UpdateLoadMenu(IEnumerable saveFiles = null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs index 17616623b..2ce5f1b82 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs @@ -12,6 +12,10 @@ namespace Barotrauma { class SinglePlayerCampaignSetupUI : CampaignSetupUI { + private GUIListBox subList; + + protected GUILayoutGroup subPreviewContainer; + public CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus { get; private set; } private GUIButton nextButton; @@ -38,7 +42,7 @@ namespace Barotrauma pageContainer.BarScroll = targetScroll; } - for (int i=0; i - s.Name.ToLower() == subName.ToLower()); - + selectedSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name.ToLowerInvariant() == subName.ToLowerInvariant()); if (selectedSub == null) { DebugConsole.NewMessage($"Cannot find a sub that matches the name \"{subName}\".", Color.Red); @@ -1055,17 +1053,23 @@ namespace Barotrauma GUI.Draw(Cam, spriteBatch); -#if !UNSTABLE - string versionString = "Barotrauma v" + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")"; - GUI.SmallFont.DrawString(spriteBatch, versionString, new Vector2(HUDLayoutSettings.Padding, GameMain.GraphicsHeight - GUI.SmallFont.MeasureString(versionString).Y - HUDLayoutSettings.Padding * 0.75f), Color.White * 0.7f); -#endif + if (selectedTab != Tab.Credits) { +#if !UNSTABLE + string versionString = "Barotrauma v" + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")"; + GUI.SmallFont.DrawString(spriteBatch, versionString, new Vector2(HUDLayoutSettings.Padding, GameMain.GraphicsHeight - GUI.SmallFont.MeasureString(versionString).Y - HUDLayoutSettings.Padding * 0.75f), Color.White * 0.7f); +#endif + string gameAnalyticsStatus = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}"); + Vector2 textSize = GUI.SmallFont.MeasureString(gameAnalyticsStatus).ToPoint().ToVector2(); + GUI.SmallFont.DrawString(spriteBatch, gameAnalyticsStatus, new Vector2(HUDLayoutSettings.Padding, GameMain.GraphicsHeight - GUI.SmallFont.LineHeight * 2 - HUDLayoutSettings.Padding * 0.75f), Color.White * 0.7f); + + Vector2 textPos = new Vector2(GameMain.GraphicsWidth - HUDLayoutSettings.Padding, GameMain.GraphicsHeight - HUDLayoutSettings.Padding * 0.75f); for (int i = legalCrap.Length - 1; i >= 0; i--) { - Vector2 textSize = GUI.SmallFont.MeasureString(legalCrap[i]); - textSize = new Vector2((int)textSize.X, (int)textSize.Y); + textSize = GUI.SmallFont.MeasureString(legalCrap[i]) + .ToPoint().ToVector2(); bool mouseOn = i == 0 && PlayerInput.MousePosition.X > textPos.X - textSize.X && PlayerInput.MousePosition.X < textPos.X && PlayerInput.MousePosition.Y > textPos.Y - textSize.Y && PlayerInput.MousePosition.Y < textPos.Y; @@ -1121,8 +1125,8 @@ namespace Barotrauma DebugConsole.ThrowError("Copying the file \"" + selectedSub.FilePath + "\" failed. The file may have been deleted or in use by another process. Try again or select another submarine.", e); GameAnalyticsManager.AddErrorEventOnce( "MainMenuScreen.StartGame:IOException" + selectedSub.Name, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Copying the file \"" + selectedSub.FilePath + "\" failed.\n" + e.Message + "\n" + Environment.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.ErrorSeverity.Error, + "Copying a submarine file failed. " + e.Message + "\n" + Environment.StackTrace.CleanupStackTrace()); return; } @@ -1446,7 +1450,7 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Fetching remote content to the main menu failed.", e); #endif - GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.FetchRemoteContent:Exception", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.FetchRemoteContent:Exception", GameAnalyticsManager.ErrorSeverity.Error, "Fetching remote content to the main menu failed. " + e.Message); return; } @@ -1489,7 +1493,7 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Reading received remote main menu content failed.", e); #endif - GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.WairForRemoteContentReceived:Exception", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.WairForRemoteContentReceived:Exception", GameAnalyticsManager.ErrorSeverity.Error, "Reading received remote main menu content failed. " + e.Message); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 1d1c19e48..5e59cbc53 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using Barotrauma.IO; using System.Linq; using System.Xml.Linq; +using Barotrauma.Steam; namespace Barotrauma { @@ -40,12 +41,6 @@ namespace Barotrauma private readonly GUIScrollBar levelDifficultyScrollBar; - private readonly GUITickBox radiationEnabledTickBox; - - private readonly GUIButton[] maxMissionCountButtons; - private readonly GUITextBlock maxMissionCountText; - private readonly GUITextBlock maxMissionCountDescription; - private readonly GUIButton[] traitorProbabilityButtons; private readonly GUITextBlock traitorProbabilityText; @@ -192,6 +187,9 @@ namespace Barotrauma } } + public IReadOnlyList GetSubList() + => SubList.Content.Children.Select(c => c.UserData as SubmarineInfo).ToArray(); + public readonly GUIListBox PlayerList; public GUITextBox CharacterNameBox @@ -1185,45 +1183,6 @@ namespace Barotrauma } }; - if (MapGenerationParams.Instance.RadiationParams != null) - { - radiationEnabledTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), settingsContent.RectTransform), TextManager.Get("CampaignOption.EnableRadiation"), font: GUI.Style.Font) - { - Selected = true, - OnSelected = box => - { - GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, radiationEnabled: box.Selected); - return true; - } - }; - } - - var maxMissionCountSettingHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), settingsContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; - maxMissionCountDescription = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), maxMissionCountSettingHolder.RectTransform), TextManager.Get("maxmissioncount", fallBackTag: "missions"), wrap: true) - { - ToolTip = TextManager.Get("maxmissioncounttooltip") - }; - var maxMissionCountContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), maxMissionCountSettingHolder.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { RelativeSpacing = 0.05f, Stretch = true }; - maxMissionCountButtons = new GUIButton[2]; - maxMissionCountButtons[0] = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleLeft") - { - OnClicked = (button, obj) => - { - GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, maxMissionCount: -1); - return true; - } - }; - maxMissionCountText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), maxMissionCountContainer.RectTransform), "0", textAlignment: Alignment.Center, style: "GUITextBox"); - maxMissionCountButtons[1] = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleRight") - { - OnClicked = (button, obj) => - { - GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, maxMissionCount: 1); - return true; - } - }; - maxMissionCountSettingHolder.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip); - List settingsElements = settingsContent.Children.ToList(); for (int i = 0; i < settingsElements.Count; i++) { @@ -1372,16 +1331,6 @@ namespace Barotrauma } SeedBox.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); levelDifficultyScrollBar.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); - if (radiationEnabledTickBox != null) - { - radiationEnabledTickBox.Enabled = CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); - } - maxMissionCountDescription.Enabled = CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); - maxMissionCountText.Enabled = CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); - foreach (var button in maxMissionCountButtons) - { - button.Enabled = CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); - } traitorProbabilityButtons[0].Enabled = traitorProbabilityButtons[1].Enabled = traitorProbabilityText.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); @@ -2163,17 +2112,126 @@ namespace Barotrauma if (child != null) { PlayerList.RemoveChild(child); } } + private Client ExtractClientFromClickableArea(GUITextBlock.ClickableArea area) + { + if (!UInt64.TryParse(area.Data.Metadata, out UInt64 id)) { return null; } + Client client = GameMain.Client.ConnectedClients.Find(c => c.SteamID == id) + ?? GameMain.Client.ConnectedClients.Find(c => c.ID == id) + ?? GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.SteamID == id) + ?? GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.ID == id); + return client; + } + public void SelectPlayer(GUITextBlock component, GUITextBlock.ClickableArea area) { - if (!UInt64.TryParse(area.Data.Metadata, out UInt64 id)) { return; } - Client client = GameMain.Client.ConnectedClients.Find(c => c.SteamID == id) - ?? GameMain.Client.ConnectedClients.Find(c => c.ID == id) - ?? GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.SteamID == id) - ?? GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.ID == id); - if (client == null) { return; } + var client = ExtractClientFromClickableArea(area); + if (client is null) { return; } GameMain.NetLobbyScreen.SelectPlayer(client); } + public void ShowPlayerContextMenu(GUITextBlock component, GUITextBlock.ClickableArea area) + { + var client = ExtractClientFromClickableArea(area); + if (client is null) { return; } + CreateModerationContextMenu(client); + } + + #region Context Menu + public static void CreateModerationContextMenu(Client client) + { + if (GUIContextMenu.CurrentContextMenu != null) { return; } + if (GameMain.IsSingleplayer || client == null || ((!GameMain.Client?.PreviouslyConnectedClients?.Contains(client)) ?? true)) { return; } + bool hasSteam = client.SteamID > 0 && SteamManager.IsInitialized, + canKick = GameMain.Client.HasPermission(ClientPermissions.Kick), + canBan = GameMain.Client.HasPermission(ClientPermissions.Ban) && client.AllowKicking, + canPromo = GameMain.Client.HasPermission(ClientPermissions.ManagePermissions); + + // Disable options if we are targeting ourselves + if (client.ID == GameMain.Client?.ID) + { + canKick = canBan = canPromo = false; + } + + List options = new List(); + + options.Add(new ContextMenuOption("ViewSteamProfile", isEnabled: hasSteam, onSelected: delegate + { + Steamworks.SteamFriends.OpenWebOverlay($"https://steamcommunity.com/profiles/{client.SteamID}"); + })); + + options.Add(new ContextMenuOption("ModerationMenu.UserDetails", isEnabled: true, onSelected: delegate + { + GameMain.NetLobbyScreen?.SelectPlayer(client); + })); + + + // Creates sub context menu options for all the ranks + List permissionOptions = new List(); + foreach (PermissionPreset rank in PermissionPreset.List) + { + permissionOptions.Add(new ContextMenuOption(rank.Name, isEnabled: true, onSelected: () => + { + string label = TextManager.GetWithVariables(rank.Permissions == ClientPermissions.None ? "clearrankprompt" : "giverankprompt", new []{ "[user]", "[rank]" }, new []{ client.Name, rank.Name }); + GUIMessageBox msgBox = new GUIMessageBox(string.Empty, label, new[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }); + + msgBox.Buttons[0].OnClicked = delegate + { + client.SetPermissions(rank.Permissions, rank.PermittedCommands); + GameMain.Client.UpdateClientPermissions(client); + msgBox.Close(); + return true; + }; + msgBox.Buttons[1].OnClicked = delegate + { + msgBox.Close(); + return true; + }; + }) { Tooltip = rank.Description }); + } + + options.Add(new ContextMenuOption("Permissions", isEnabled: canPromo, options: permissionOptions.ToArray())); + + Color clientColor = client.Character?.Info?.Job.Prefab.UIColor ?? Color.White; + + if (GameMain.Client.ConnectedClients.Contains(client)) + { + options.Add(new ContextMenuOption(client.MutedLocally ? "Unmute" : "Mute", isEnabled: client.ID != GameMain.Client?.ID, onSelected: delegate + { + client.MutedLocally = !client.MutedLocally; + })); + + bool kickEnabled = client.ID != GameMain.Client?.ID && client.AllowKicking; + + // if the user can kick create a kick option else create the votekick option + ContextMenuOption kickOption; + if (canKick) + { + kickOption = new ContextMenuOption("Kick", isEnabled: kickEnabled, onSelected: delegate + { + GameMain.Client?.CreateKickReasonPrompt(client.Name, false); + }); + } + else + { + kickOption = new ContextMenuOption("VoteToKick", isEnabled: kickEnabled, onSelected: delegate + { + GameMain.Client?.VoteForKick(client); + }); + } + + options.Add(kickOption); + } + + options.Add(new ContextMenuOption("Ban", isEnabled: canBan, onSelected: delegate + { + GameMain.Client?.CreateKickReasonPrompt(client.Name, true); + })); + + GUIContextMenu.CreateContextMenu(null, client.Name, headerColor: clientColor, options.ToArray()); + } + + #endregion + public bool SelectPlayer(Client selectedClient) { bool myClient = selectedClient.ID == GameMain.Client.ID; @@ -2763,7 +2821,8 @@ namespace Barotrauma msg.ClickableAreas.Add(new GUITextBlock.ClickableArea() { Data = data, - OnClick = GameMain.NetLobbyScreen.SelectPlayer + OnClick = GameMain.NetLobbyScreen.SelectPlayer, + OnSecondaryClick = GameMain.NetLobbyScreen.ShowPlayerContextMenu }); } } @@ -3221,6 +3280,7 @@ namespace Barotrauma { CreateSubPreview(submarine); } + UpdateSubVisibility(); } private bool ViewJobInfo(GUIButton button, object obj) @@ -3369,14 +3429,28 @@ namespace Barotrauma return btn; } - public Pair FailedSelectedSub; - public Pair FailedSelectedShuttle; + public readonly struct FailedSubInfo + { + public readonly string Name; + public readonly string Hash; + public FailedSubInfo(string name, string hash) { Name = name; Hash = hash; } + public void Deconstruct(out string name, out string hash) { name = Name; hash = Hash; } + public static bool operator ==(FailedSubInfo a, FailedSubInfo b) + => a.Name.Equals(b.Name, StringComparison.OrdinalIgnoreCase) + && a.Hash.Equals(b.Hash, StringComparison.OrdinalIgnoreCase); + public static bool operator !=(FailedSubInfo a, FailedSubInfo b) + => !(a == b); + } - public List> FailedCampaignSubs = new List>(); - public List> FailedOwnedSubs = new List>(); + public FailedSubInfo? FailedSelectedSub; + public FailedSubInfo? FailedSelectedShuttle; + + public List FailedCampaignSubs = new List(); + public List FailedOwnedSubs = new List(); public bool TrySelectSub(string subName, string md5Hash, GUIListBox subList) { + UpdateSubVisibility(); if (GameMain.Client == null) { return false; } //already downloading the selected sub file @@ -3441,9 +3515,13 @@ namespace Barotrauma //if we get to this point, a matching sub was not found or it has an incorrect MD5 hash if (subList == SubList) - FailedSelectedSub = new Pair(subName, md5Hash); + { + FailedSelectedSub = new FailedSubInfo(subName, md5Hash); + } else - FailedSelectedShuttle = new Pair(subName, md5Hash); + { + FailedSelectedShuttle = new FailedSubInfo(subName, md5Hash); + } string errorMsg = ""; if (sub == null || !SubmarineInfo.SavedSubmarines.Contains(sub)) @@ -3542,22 +3620,22 @@ namespace Barotrauma { UserData = "request" + serverSubmarine.Name }; - requestFileBox.Buttons[0].UserData = new string[] { serverSubmarine.Name, serverSubmarine.MD5Hash.Hash }; + requestFileBox.Buttons[0].UserData = new FailedSubInfo(serverSubmarine.Name, serverSubmarine.MD5Hash.Hash); requestFileBox.Buttons[0].OnClicked += requestFileBox.Close; requestFileBox.Buttons[0].OnClicked += (GUIButton button, object userdata) => { - string[] fileInfo = (string[])userdata; + FailedSubInfo fileInfo = (FailedSubInfo)userdata; - if (deliveryData == "owned") + if (deliveryData == "owned") //owned!!!! { - FailedOwnedSubs.Add(new Pair(fileInfo[0], fileInfo[1])); + FailedOwnedSubs.Add(fileInfo); } else if (deliveryData == "campaign") { - FailedCampaignSubs.Add(new Pair(fileInfo[0], fileInfo[1])); + FailedCampaignSubs.Add(fileInfo); } - GameMain.Client?.RequestFile(FileTransferType.Submarine, fileInfo[0], fileInfo[1]); + GameMain.Client?.RequestFile(FileTransferType.Submarine, fileInfo.Name, fileInfo.Hash); return true; }; requestFileBox.Buttons[1].OnClicked += requestFileBox.Close; @@ -3691,7 +3769,7 @@ namespace Barotrauma }; var subName = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), frameContent.RectTransform), - text: sub.Name) + text: sub.DisplayName) { CanBeFocused = false }; @@ -3823,7 +3901,9 @@ namespace Barotrauma foreach (GUIComponent child in SubList.Content.Children) { if (!(child.UserData is SubmarineInfo sub)) { continue; } - child.Visible = !GameMain.Client.ServerSettings.HiddenSubs.Contains(sub.Name) + child.Visible = + (!GameMain.Client.ServerSettings.HiddenSubs.Contains(sub.Name) + || (GameMain.GameSession?.SubmarineInfo != null && GameMain.GameSession.SubmarineInfo.Name.Equals(sub.Name, StringComparison.OrdinalIgnoreCase))) && (string.IsNullOrEmpty(subSearchBox.Text) || sub.DisplayName.Contains(subSearchBox.Text, StringComparison.OrdinalIgnoreCase)); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs index 0d07b20c1..9c004b7e3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs @@ -2376,10 +2376,9 @@ namespace Barotrauma } catch (Exception ex) { - string errorMsg = "Failed to ping a server (" + ip + ") - " + (ex?.InnerException?.Message ?? ex.Message); - GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + ip, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + ip, GameAnalyticsManager.ErrorSeverity.Warning, "Failed to ping a server - " + (ex?.InnerException?.Message ?? ex.Message)); #if DEBUG - DebugConsole.NewMessage(errorMsg, Color.Red); + DebugConsole.NewMessage("Failed to ping a server (" + ip + ") - " + (ex?.InnerException?.Message ?? ex.Message), Color.Red); #endif } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs index 9da115082..94064f687 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs @@ -824,9 +824,8 @@ namespace Barotrauma } catch (Exception e) { - string errorMsg = "Failed to save workshop item preview image to \"" + previewImagePath + "\"."; GameAnalyticsManager.AddErrorEventOnce("SteamWorkshopScreen.OnItemPreviewDownloaded:WriteAllBytesFailed" + previewImagePath, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + e.Message); + GameAnalyticsManager.ErrorSeverity.Error, "Failed to save workshop item preview image.\n" + e.Message); return; } } @@ -1195,7 +1194,7 @@ namespace Barotrauma { string errorMsg = "Failed to edit workshop item (content package null)\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("SteamWorkshopScreen.ShowCreateItemFrame:ContentPackageNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("SteamWorkshopScreen.ShowCreateItemFrame:ContentPackageNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs index 1fc36bc42..66e9507f3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs @@ -358,7 +358,7 @@ namespace Barotrauma } else { - displayName = TextManager.Get(fallbackTag, true); + displayName = TextManager.Get(fallbackTag, true) ?? TextManager.Get($"sp.{fallbackTag}.name", true); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs index 3bdc5a04d..281b031f2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs @@ -228,7 +228,7 @@ namespace Barotrauma.Sounds uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); float effectiveGain = gain; - if (category != null) effectiveGain *= Sound.Owner.GetCategoryGainMultiplier(category); + if (category != null) { effectiveGain *= Sound.Owner.GetCategoryGainMultiplier(category); } Al.Sourcef(alSource, Al.Gain, effectiveGain); int alError = Al.GetError(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index edfccb5b1..9c8e2f0f4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -470,14 +470,14 @@ namespace Barotrauma { string errorMsg = "Failed to update water ambience volume - submarine's movement value invalid (" + movementSoundVolume + ", sub velocity: " + sub.Velocity + ")"; DebugConsole.Log(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("SoundPlayer.UpdateWaterAmbience:InvalidVolume", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("SoundPlayer.UpdateWaterAmbience:InvalidVolume", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); movementSoundVolume = 0.0f; } if (!MathUtils.IsValid(insideSubFactor)) { string errorMsg = "Failed to update water ambience volume - inside sub value invalid (" + insideSubFactor + ")"; DebugConsole.Log(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("SoundPlayer.UpdateWaterAmbience:InvalidVolume", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("SoundPlayer.UpdateWaterAmbience:InvalidVolume", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); insideSubFactor = 0.0f; } } @@ -609,7 +609,7 @@ namespace Barotrauma flowSoundChannels[i] = FlowSounds[i].Play(1.0f, FlowSoundRange, soundPos); flowSoundChannels[i].Looping = true; } - flowSoundChannels[i].Gain = Math.Max(flowVolumeRight[i], flowVolumeLeft[i]); + flowSoundChannels[i].Gain = Math.Min(Math.Max(flowVolumeRight[i], flowVolumeLeft[i]), 1.0f); flowSoundChannels[i].Position = new Vector3(soundPos, 0.0f); } } @@ -790,7 +790,7 @@ namespace Barotrauma if (sound == null) { string errorMsg = "Error in SoundPlayer.PlaySound (sound was null)\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("SoundPlayer.PlaySound:SoundNull" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("SoundPlayer.PlaySound:SoundNull" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return null; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs index a65c33d7b..3d23f6590 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs @@ -64,6 +64,7 @@ namespace Barotrauma { float angle = 0.0f; float particleRotation = 0.0f; + bool mirrorAngle = false; if (emitter.Prefab.Properties.CopyEntityAngle) { Limb targetLimb = null; @@ -71,7 +72,11 @@ namespace Barotrauma { angle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); particleRotation = -item.body.Rotation; - if (item.body.Dir < 0.0f) { particleRotation += MathHelper.Pi; } + if (item.body.Dir < 0.0f) + { + particleRotation += MathHelper.Pi; + mirrorAngle = true; + } } else if (entity is Character c && !c.Removed && targetLimbs?.FirstOrDefault(l => l != LimbType.None) is LimbType l) { @@ -85,11 +90,15 @@ namespace Barotrauma { angle = targetLimb.body.Rotation + ((targetLimb.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); particleRotation = -targetLimb.body.Rotation; - if (targetLimb.body.Dir < 0.0f) { particleRotation += MathHelper.Pi; } + if (targetLimb.body.Dir < 0.0f) + { + particleRotation += MathHelper.Pi; + mirrorAngle = true; + } } } - emitter.Emit(deltaTime, worldPosition, hull, angle: angle, particleRotation: particleRotation); + emitter.Emit(deltaTime, worldPosition, hull, angle: angle, particleRotation: particleRotation, mirrorAngle: mirrorAngle); } } @@ -108,7 +117,7 @@ namespace Barotrauma if (sound?.Sound == null) { string errorMsg = $"Error in StatusEffect.ApplyProjSpecific1 (sound \"{sound?.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull1" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull1" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } soundChannel = SoundPlayer.PlaySound(sound.Sound, worldPosition, sound.Volume, sound.Range, hullGuess: hull, ignoreMuffling: sound.IgnoreMuffling); @@ -135,7 +144,7 @@ namespace Barotrauma if (selectedSound?.Sound == null) { string errorMsg = $"Error in StatusEffect.ApplyProjSpecific2 (sound \"{selectedSound?.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull2" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull2" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } if (selectedSound.Sound.Disposed) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs index 9c9646925..ce055cf1e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs @@ -421,7 +421,7 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Empty color array passed to the GradientLerp method.\n" + Environment.StackTrace.CleanupStackTrace()); #endif - GameAnalyticsManager.AddErrorEventOnce("ToolBox.GradientLerp:EmptyColorArray", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.AddErrorEventOnce("ToolBox.GradientLerp:EmptyColorArray", GameAnalyticsManager.ErrorSeverity.Error, "Empty color array passed to the GradientLerp method.\n" + Environment.StackTrace.CleanupStackTrace()); return Color.Black; } diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 26d363261..b21cc1d64 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.15.15.0 + 0.15.17.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma @@ -55,6 +55,7 @@ + @@ -99,7 +100,6 @@ - @@ -109,7 +109,6 @@ - @@ -189,4 +188,9 @@ + + linux-x64 + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index ee44c8626..ee768d921 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.15.15.0 + 0.15.17.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma @@ -58,6 +58,7 @@ + @@ -100,7 +101,6 @@ - @@ -110,7 +110,6 @@ - @@ -201,4 +200,9 @@ + + osx-x64 + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index cb3312d5f..6480ae7b4 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.15.15.0 + 0.15.17.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma @@ -63,6 +63,7 @@ + @@ -103,7 +104,6 @@ - @@ -113,7 +113,6 @@ - @@ -220,4 +219,9 @@ + + win-x64 + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/app.manifest b/Barotrauma/BarotraumaClient/app.manifest index ff31a5f7c..54b3a73be 100644 --- a/Barotrauma/BarotraumaClient/app.manifest +++ b/Barotrauma/BarotraumaClient/app.manifest @@ -1,62 +1,62 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true/pm - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + + + + + + + diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 83c32f101..5a6f3e57f 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -1,141 +1,144 @@ - - - - Exe - netcoreapp3.1 - Barotrauma - FakeFish, Undertow Games - Barotrauma Dedicated Server - 0.15.15.0 - Copyright © FakeFish 2018-2020 - AnyCPU;x64 - DedicatedServer - ..\BarotraumaShared\Icon.ico - Debug;Release;Unstable - - - - DEBUG;TRACE;SERVER;LINUX;USE_STEAM - x64 - ..\bin\$(Configuration)Linux\ - - - - TRACE;DEBUG;SERVER;LINUX;X64;USE_STEAM - x64 - ..\bin\$(Configuration)Linux\ - - - - TRACE;SERVER;LINUX;USE_STEAM - x64 - ..\bin\$(Configuration)Linux\ - - - - TRACE;SERVER;LINUX;USE_STEAM - x64 - ..\bin\$(Configuration)Linux\ - true - - - - TRACE;SERVER;LINUX;X64;USE_STEAM - x64 - ..\bin\$(Configuration)Linux\ - - - - TRACE;SERVER;LINUX;X64;USE_STEAM - x64 - ..\bin\$(Configuration)Linux\ - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $(IntermediateOutputPath)gitver - $(IntermediateOutputPath)gitbranch - - - - - - - - - - - - - - - - - - @(GitVersion) - - - - - - - - - @(GitBranch) - - - - - - - $(IntermediateOutputPath)CustomAssemblyInfo.cs - - - - - - - - - <_Parameter1>GitRevision - <_Parameter2>$(BuildHash) - - - <_Parameter1>GitBranch - <_Parameter2>$(BuildBranch) - - - <_Parameter1>ProjectDir - <_Parameter2>$(ProjectDir) - - - - - - - + + + + Exe + netcoreapp3.1 + Barotrauma + FakeFish, Undertow Games + Barotrauma Dedicated Server + 0.15.17.0 + Copyright © FakeFish 2018-2020 + AnyCPU;x64 + DedicatedServer + ..\BarotraumaShared\Icon.ico + Debug;Release;Unstable + + + + DEBUG;TRACE;SERVER;LINUX;USE_STEAM + x64 + ..\bin\$(Configuration)Linux\ + + + + TRACE;DEBUG;SERVER;LINUX;X64;USE_STEAM + x64 + ..\bin\$(Configuration)Linux\ + + + + TRACE;SERVER;LINUX;USE_STEAM + x64 + ..\bin\$(Configuration)Linux\ + + + + TRACE;SERVER;LINUX;USE_STEAM + x64 + ..\bin\$(Configuration)Linux\ + true + + + + TRACE;SERVER;LINUX;X64;USE_STEAM + x64 + ..\bin\$(Configuration)Linux\ + + + + TRACE;SERVER;LINUX;X64;USE_STEAM + x64 + ..\bin\$(Configuration)Linux\ + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(IntermediateOutputPath)gitver + $(IntermediateOutputPath)gitbranch + + + + + + + + + + + + + + + + + + @(GitVersion) + + + + + + + + + @(GitBranch) + + + + + + + $(IntermediateOutputPath)CustomAssemblyInfo.cs + + + + + + + + + <_Parameter1>GitRevision + <_Parameter2>$(BuildHash) + + + <_Parameter1>GitBranch + <_Parameter2>$(BuildBranch) + + + <_Parameter1>ProjectDir + <_Parameter2>$(ProjectDir) + + + + + + + diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 1e7744b49..5f1a062bb 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -1,154 +1,157 @@ - - - - Exe - netcoreapp3.1 - Barotrauma - FakeFish, Undertow Games - Barotrauma Dedicated Server - 0.15.15.0 - Copyright © FakeFish 2018-2020 - AnyCPU;x64 - DedicatedServer - ..\BarotraumaShared\Icon.ico - 0.9.0.0 - Debug;Release;Unstable - - - - TRACE;SERVER;OSX;USE_STEAM;DEBUG;NETCOREAPP;NETCOREAPP3_0 - x64 - ..\bin\DebugMac - true - - - - - TRACE;DEBUG;SERVER;OSX;X64;USE_STEAM - x64 - ..\bin\$(Configuration)Mac\ - - - - TRACE;SERVER;OSX;USE_STEAM;RELEASE;NETCOREAPP;NETCOREAPP3_0 - x64 - - ..\bin\ReleaseMac - - - - TRACE;SERVER;OSX;USE_STEAM;RELEASE;NETCOREAPP;NETCOREAPP3_0;UNSTABLE - x64 - - ..\bin\ReleaseMac - true - - - - TRACE;SERVER;OSX;X64;USE_STEAM - x64 - ..\bin\$(Configuration)Mac\ - - - - TRACE;SERVER;OSX;X64;USE_STEAM;UNSTABLE - x64 - ..\bin\$(Configuration)Mac\ - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - libsteam_api64.dylib - PreserveNewest - - - - - - $(IntermediateOutputPath)gitver - $(IntermediateOutputPath)gitbranch - - - - - - - - - - - - - - - - - - @(GitVersion) - - - - - - - - - @(GitBranch) - - - - - - - $(IntermediateOutputPath)CustomAssemblyInfo.cs - - - - - - - - - <_Parameter1>GitRevision - <_Parameter2>$(BuildHash) - - - <_Parameter1>GitBranch - <_Parameter2>$(BuildBranch) - - - <_Parameter1>ProjectDir - <_Parameter2>$(ProjectDir) - - - - - - - + + + + Exe + netcoreapp3.1 + Barotrauma + FakeFish, Undertow Games + Barotrauma Dedicated Server + 0.15.17.0 + Copyright © FakeFish 2018-2020 + AnyCPU;x64 + DedicatedServer + ..\BarotraumaShared\Icon.ico + 0.9.0.0 + Debug;Release;Unstable + + + + TRACE;SERVER;OSX;USE_STEAM;DEBUG;NETCOREAPP;NETCOREAPP3_0 + x64 + ..\bin\DebugMac + true + + + + + TRACE;DEBUG;SERVER;OSX;X64;USE_STEAM + x64 + ..\bin\$(Configuration)Mac\ + + + + TRACE;SERVER;OSX;USE_STEAM;RELEASE;NETCOREAPP;NETCOREAPP3_0 + x64 + + ..\bin\ReleaseMac + + + + TRACE;SERVER;OSX;USE_STEAM;RELEASE;NETCOREAPP;NETCOREAPP3_0;UNSTABLE + x64 + + ..\bin\ReleaseMac + true + + + + TRACE;SERVER;OSX;X64;USE_STEAM + x64 + ..\bin\$(Configuration)Mac\ + + + + TRACE;SERVER;OSX;X64;USE_STEAM;UNSTABLE + x64 + ..\bin\$(Configuration)Mac\ + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + libsteam_api64.dylib + PreserveNewest + + + + + + $(IntermediateOutputPath)gitver + $(IntermediateOutputPath)gitbranch + + + + + + + + + + + + + + + + + + @(GitVersion) + + + + + + + + + @(GitBranch) + + + + + + + $(IntermediateOutputPath)CustomAssemblyInfo.cs + + + + + + + + + <_Parameter1>GitRevision + <_Parameter2>$(BuildHash) + + + <_Parameter1>GitBranch + <_Parameter2>$(BuildBranch) + + + <_Parameter1>ProjectDir + <_Parameter2>$(ProjectDir) + + + + + + + diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 80b8a45b2..a2b85a64c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -269,7 +269,7 @@ namespace Barotrauma { string errorMsg = "Failed to write input to command line (window width: " + Console.WindowWidth + ", window height: " + Console.WindowHeight + ")\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("DebugConsole.RewriteInputToCommandLine", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("DebugConsole.RewriteInputToCommandLine", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } } @@ -1588,10 +1588,6 @@ namespace Barotrauma if (tpCharacter != null) { tpCharacter.TeleportTo(cursorWorldPos); - if (tpCharacter.AIController?.SteeringManager is IndoorsSteeringManager pathSteering) - { - pathSteering.ResetPath(); - } } } ); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index e0f044392..9c08e13ab 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -1,7 +1,6 @@ using Barotrauma.Networking; using Barotrauma.Steam; using FarseerPhysics.Dynamics; -using GameAnalyticsSDK.Net; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -98,8 +97,9 @@ namespace Barotrauma Console.WriteLine("Initializing SteamManager"); SteamManager.Initialize(); - Console.WriteLine("Initializing GameAnalytics"); - if (GameSettings.SendUserStatistics) GameAnalyticsManager.Init(); + //TODO: figure out how consent is supposed to work for servers + //Console.WriteLine("Initializing GameAnalytics"); + //GameAnalyticsManager.InitIfConsented(); Console.WriteLine("Initializing GameScreen"); GameScreen = new GameScreen(); @@ -418,7 +418,7 @@ namespace Barotrauma SaveUtil.CleanUnnecessarySaveFiles(); if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging) { DebugConsole.SaveLogs(); } - if (GameSettings.SendUserStatistics) { GameAnalytics.OnQuit(); } + if (GameAnalyticsManager.SendUserStatistics) { GameAnalyticsManager.ShutDown(); } MainThread = null; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs index 9f3f034b7..8013b965b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs @@ -13,7 +13,7 @@ namespace Barotrauma get { return itemData != null; } } - public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false) + public CharacterCampaignData(Client client) { Name = client.Name; ClientEndPoint = client.Connection.EndPointString; @@ -22,13 +22,6 @@ namespace Barotrauma healthData = new XElement("health"); client.Character?.CharacterHealth?.Save(healthData); - if (giveRespawnPenaltyAffliction) - { - var respawnPenaltyAffliction = RespawnManager.GetRespawnPenaltyAffliction(); - healthData.Add(new XElement("Affliction", - new XAttribute("identifier", respawnPenaltyAffliction.Identifier), - new XAttribute("strength", respawnPenaltyAffliction.Strength.ToString("G", CultureInfo.InvariantCulture)))); - } if (client.Character?.Inventory != null) { itemData = new XElement("inventory"); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 30853fb52..f895b062f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -211,18 +211,6 @@ namespace Barotrauma { c.Character = null; } - - if (c.HasSpawned && c.CharacterInfo != null && c.CharacterInfo.CauseOfDeath != null && c.CharacterInfo.CauseOfDeath.Type != CauseOfDeathType.Disconnected) - { - //the client has opted to spawn this round with Reaper's Tax - if (c.WaitForNextRoundRespawn.HasValue && !c.WaitForNextRoundRespawn.Value) - { - c.CharacterInfo.StartItemsGiven = false; - characterData.RemoveAll(cd => cd.MatchesClient(c)); - characterData.Add(new CharacterCampaignData(c, giveRespawnPenaltyAffliction: true)); - continue; - } - } //use the info of the character the client is currently controlling // or the previously saved info if not (e.g. if the client has been spectating or died) var characterInfo = c.Character?.Info ?? characterData.Find(d => d.MatchesClient(c))?.CharacterInfo; @@ -231,6 +219,7 @@ namespace Barotrauma if (characterInfo.CauseOfDeath != null && characterInfo.CauseOfDeath.Type != CauseOfDeathType.Disconnected) { RespawnManager.ReduceCharacterSkills(characterInfo); + characterInfo.RemoveSavedStatValuesOnDeath(); } c.CharacterInfo = characterInfo; characterData.RemoveAll(cd => cd.MatchesClient(c)); @@ -358,6 +347,7 @@ namespace Barotrauma } } } + UpdateCampaignSubs(); SaveUtil.SaveGame(GameMain.GameSession.SavePath); PendingSubmarineSwitch = null; @@ -400,20 +390,50 @@ namespace Barotrauma partial void InitProjSpecific() { - if (GameMain.Server != null) - { - CargoManager.OnItemsInBuyCrateChanged += () => { LastUpdateID++; }; - CargoManager.OnPurchasedItemsChanged += () => { LastUpdateID++; }; - CargoManager.OnSoldItemsChanged += () => { LastUpdateID++; }; - UpgradeManager.OnUpgradesChanged += () => { LastUpdateID++; }; - Map.OnLocationSelected += (loc, connection) => { LastUpdateID++; }; - Map.OnMissionsSelected += (loc, mission) => { LastUpdateID++; }; - Reputation.OnAnyReputationValueChanged += () => { LastUpdateID++; }; - } + CargoManager.OnItemsInBuyCrateChanged += () => { LastUpdateID++; }; + CargoManager.OnPurchasedItemsChanged += () => { LastUpdateID++; }; + CargoManager.OnSoldItemsChanged += () => { LastUpdateID++; }; + UpgradeManager.OnUpgradesChanged += () => { LastUpdateID++; }; + Map.OnLocationSelected += (loc, connection) => { LastUpdateID++; }; + Map.OnMissionsSelected += (loc, mission) => { LastUpdateID++; }; + Reputation.OnAnyReputationValueChanged += () => { LastUpdateID++; }; + + UpdateCampaignSubs(); + //increment save ID so clients know they're lacking the most up-to-date save file LastSaveID++; } + private void UpdateCampaignSubs() + { + bool isSubmarineVisible(SubmarineInfo s) + => !GameMain.Server.ServerSettings.HiddenSubs.Any(h + => s.Name.Equals(h, StringComparison.OrdinalIgnoreCase)); + + List availableSubs = + SubmarineInfo.SavedSubmarines + .Where(s => + s.IsCampaignCompatible + && isSubmarineVisible(s)) + .ToList(); + + if (!availableSubs.Any()) + { + //None of the available subs were marked as campaign-compatible, just include all visible subs + availableSubs.AddRange( + SubmarineInfo.SavedSubmarines + .Where(isSubmarineVisible)); + } + + if (!availableSubs.Any()) + { + //No subs are visible at all! Just make the selected one available + availableSubs.Add(GameMain.NetLobbyScreen.SelectedSub); + } + + GameMain.NetLobbyScreen.CampaignSubmarines = availableSubs; + } + public void DiscardClientCharacterData(Client client) { characterData.RemoveAll(cd => cd.MatchesClient(client)); @@ -1030,14 +1050,6 @@ namespace Barotrauma new XAttribute("points", savedExperiencePoint.ExperiencePoints))); } - // save available submarines - XElement availableSubsElement = new XElement("AvailableSubs"); - for (int i = 0; i < GameMain.NetLobbyScreen.CampaignSubmarines.Count; i++) - { - availableSubsElement.Add(new XElement("Sub", new XAttribute("name", GameMain.NetLobbyScreen.CampaignSubmarines[i].Name))); - } - modeElement.Add(availableSubsElement); - element.Add(modeElement); //save character data to a separate file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs index 129d6e622..446106402 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs @@ -14,6 +14,7 @@ namespace Barotrauma.Items.Components { if (c.Character == null) { return; } var requestedFixAction = (FixActions)msg.ReadRangedInteger(0, 2); + var QTESuccess = msg.ReadBoolean(); if (requestedFixAction != FixActions.None) { if (!c.Character.IsTraitor && requestedFixAction == FixActions.Sabotage) @@ -31,6 +32,11 @@ namespace Barotrauma.Items.Components item.CreateServerEvent(this); } } + else + { + RepairBoost(QTESuccess); + item.CreateServerEvent(this); + } } public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) @@ -42,6 +48,7 @@ namespace Barotrauma.Items.Components msg.Write(tinkeringStrength); msg.Write(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID); msg.WriteRangedInteger((int)currentFixerAction, 0, 2); + msg.Write(repairBoost); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index f936f258b..07117c718 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -41,7 +41,7 @@ namespace Barotrauma } msg.WriteRangedInteger((int)NetEntityEvent.Type.Invalid, 0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1); DebugConsole.Log(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:InvalidData" + Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:InvalidData" + Name, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } @@ -186,7 +186,7 @@ namespace Barotrauma msg.LengthBits = initialWritePos; msg.WriteRangedInteger((int)NetEntityEvent.Type.Invalid, 0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1); DebugConsole.Log(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:" + errorMsg, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:" + errorMsg, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } } @@ -402,7 +402,7 @@ namespace Barotrauma { string errorMsg = "Attempted to create a network event for an item (" + Name + ") that hasn't been fully initialized yet.\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Item.CreateServerEvent:EventForUninitializedItem" + Name + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.CreateServerEvent:EventForUninitializedItem" + Name + ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } @@ -423,7 +423,7 @@ namespace Barotrauma { string errorMsg = "Attempted to create a network event for an item (" + Name + ") that hasn't been fully initialized yet.\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Item.CreateServerEvent:EventForUninitializedItem" + Name + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.CreateServerEvent:EventForUninitializedItem" + Name + ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/BanList.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/BanList.cs index 4a95adecd..ad2438c2d 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/BanList.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/BanList.cs @@ -274,6 +274,8 @@ namespace Barotrauma.Networking public void Save() { GameServer.Log("Saving banlist", ServerLog.MessageType.ServerMessage); + + GameMain.Server?.ServerSettings?.UpdateFlag(ServerSettings.NetFlags.Properties); bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value); @@ -344,7 +346,7 @@ namespace Barotrauma.Networking catch (Exception e) { string errorMsg = "Error while writing banlist. {" + e + "}\n" + e.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("Banlist.ServerAdminWrite", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Banlist.ServerAdminWrite", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); throw; } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs index ea78a0062..e1a4c0d60 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs @@ -278,7 +278,7 @@ namespace Barotrauma.Networking DebugConsole.ThrowError("FileSender threw an exception when trying to send data", e); GameAnalyticsManager.AddErrorEventOnce( "FileSender.Update:Exception", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace()); transfer.Status = FileTransferStatus.Error; return; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index d374ae874..dcf4d19a4 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -600,16 +600,19 @@ namespace Barotrauma.Networking //constantly increase AFK timer if the client is controlling a character (gets reset to zero every time an input is received) if (gameStarted && c.Character != null && !c.Character.IsDead && !c.Character.IsIncapacitated) { - if (c.Connection != OwnerConnection) c.KickAFKTimer += deltaTime; + if (c.Connection != OwnerConnection && c.Permissions != ClientPermissions.All) { c.KickAFKTimer += deltaTime; } } } - IEnumerable kickAFK = connectedClients.FindAll(c => - c.KickAFKTimer >= serverSettings.KickAFKTime && - (OwnerConnection == null || c.Connection != OwnerConnection)); - foreach (Client c in kickAFK) + if (connectedClients.Any(c => c.KickAFKTimer >= serverSettings.KickAFKTime)) { - KickClient(c, "DisconnectMessage.AFK"); + IEnumerable kickAFK = connectedClients.FindAll(c => + c.KickAFKTimer >= serverSettings.KickAFKTime && + (OwnerConnection == null || c.Connection != OwnerConnection)); + foreach (Client c in kickAFK) + { + KickClient(c, "DisconnectMessage.AFK"); + } } serverPeer.Update(deltaTime); @@ -632,7 +635,7 @@ namespace Barotrauma.Networking { DebugConsole.ThrowError("Failed to write a network message for the client \"" + c.Name + "\"!", e); - string errorMsg = "Failed to write a network message for the client \"" + c.Name + "\"! (MidRoundSyncing: " + c.NeedsMidRoundSync + ")\n" + string errorMsg = "Failed to write a network message for a client! (MidRoundSyncing: " + c.NeedsMidRoundSync + ")\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace(); if (e.InnerException != null) { @@ -641,7 +644,7 @@ namespace Barotrauma.Networking GameAnalyticsManager.AddErrorEventOnce( "GameServer.Update:ClientWriteFailed" + e.StackTrace.CleanupStackTrace(), - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } } @@ -791,6 +794,8 @@ namespace Barotrauma.Networking string localSavePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveName); if (connectedClient.HasPermission(ClientPermissions.SelectMode) || connectedClient.HasPermission(ClientPermissions.ManageCampaign)) { + ServerSettings.RadiationEnabled = settings.RadiationEnabled; + ServerSettings.MaxMissionCount = settings.MaxMissionCount; MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed, settings); } } @@ -854,6 +859,7 @@ namespace Barotrauma.Networking private void HandleClientError(IReadMessage inc, Client c) { string errorStr = "Unhandled error report"; + string errorStrNoName = errorStr; ClientNetError error = (ClientNetError)inc.ReadByte(); switch (error) @@ -861,7 +867,7 @@ namespace Barotrauma.Networking case ClientNetError.MISSING_EVENT: UInt16 expectedID = inc.ReadUInt16(); UInt16 receivedID = inc.ReadUInt16(); - errorStr = "Expecting event id " + expectedID.ToString() + ", received " + receivedID.ToString(); + errorStr = errorStrNoName = "Expecting event id " + expectedID.ToString() + ", received " + receivedID.ToString(); break; case ClientNetError.MISSING_ENTITY: UInt16 eventID = inc.ReadUInt16(); @@ -869,25 +875,26 @@ namespace Barotrauma.Networking Entity entity = Entity.FindEntityByID(entityID); if (entity == null) { - errorStr = "Received an update for an entity that doesn't exist (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; + errorStr = errorStrNoName = "Received an update for an entity that doesn't exist (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; } else if (entity is Character character) { errorStr = "Missing character " + character.Name + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; + errorStrNoName = "Missing character " + character.SpeciesName + "(event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; } else if (entity is Item item) { - errorStr = "Missing item " + item.Name + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; + errorStr = errorStrNoName = "Missing item " + item.Name + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; } else { - errorStr = "Missing entity " + entity.ToString() + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; + errorStr = errorStrNoName = "Missing entity " + entity.ToString() + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; } break; } - Log(GameServer.ClientLogName(c) + " has reported an error: " + errorStr, ServerLog.MessageType.Error); - GameAnalyticsManager.AddErrorEventOnce("GameServer.HandleClientError:" + errorStr, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorStr); + Log(ClientLogName(c) + " has reported an error: " + errorStr, ServerLog.MessageType.Error); + GameAnalyticsManager.AddErrorEventOnce("GameServer.HandleClientError:" + errorStrNoName, GameAnalyticsManager.ErrorSeverity.Error, errorStr); try { @@ -1392,49 +1399,23 @@ namespace Barotrauma.Networking } break; case ClientPermissions.SelectSub: - bool isCampaign = inc.ReadBoolean(); - if (!isCampaign) + bool isShuttle = inc.ReadBoolean(); + inc.ReadPadBits(); + UInt16 subIndex = inc.ReadUInt16(); + var subList = GameMain.NetLobbyScreen.GetSubList(); + if (subIndex >= subList.Count) { - bool isShuttle = inc.ReadBoolean(); - inc.ReadPadBits(); - UInt16 subIndex = inc.ReadUInt16(); - var subList = GameMain.NetLobbyScreen.GetSubList(); - if (subIndex >= subList.Count) - { - DebugConsole.NewMessage("Client \"" + GameServer.ClientLogName(sender) + "\" attempted to select a sub, index out of bounds (" + subIndex + ")", Color.Red); - } - else - { - if (isShuttle) - { - GameMain.NetLobbyScreen.SelectedShuttle = subList[subIndex]; - } - else - { - GameMain.NetLobbyScreen.SelectedSub = subList[subIndex]; - } - } + DebugConsole.NewMessage($"Client \"{ClientLogName(sender)}\" attempted to select a sub, index out of bounds ({subIndex})", Color.Red); } else { - int subEqualityCheckVal = inc.ReadInt32(); - bool add = inc.ReadBoolean(); - SubmarineInfo sub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.EqualityCheckVal == subEqualityCheckVal); - - if (sub == null) + if (isShuttle) { - DebugConsole.NewMessage("Client \"" + GameServer.ClientLogName(sender) + "\" attempted to select a sub that does not exist on the server!", Color.Red); + GameMain.NetLobbyScreen.SelectedShuttle = subList[subIndex]; } else { - if (add) - { - GameMain.NetLobbyScreen.AddCampaignSubmarine(sub); - } - else - { - GameMain.NetLobbyScreen.RemoveCampaignSubmarine(sub); - } + GameMain.NetLobbyScreen.SelectedSub = subList[subIndex]; } } break; @@ -1751,7 +1732,7 @@ namespace Barotrauma.Networking " Chat message size: " + chatMessageBytes + " bytes\n" + " Position update size: " + positionUpdateBytes + " bytes\n\n"; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable); @@ -1791,7 +1772,7 @@ namespace Barotrauma.Networking } DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame2:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame2:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable); @@ -1878,7 +1859,7 @@ namespace Barotrauma.Networking List campaignSubIndices = new List(); if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign) { - List subList = GameMain.NetLobbyScreen.GetSubList(); + IReadOnlyList subList = GameMain.NetLobbyScreen.GetSubList(); for (int i = 0; i < subList.Count; i++) { if (GameMain.NetLobbyScreen.CampaignSubmarines.Contains(subList[i])) @@ -1916,9 +1897,6 @@ namespace Barotrauma.Networking { outmsg.Write(autoRestartTimerRunning ? serverSettings.AutoRestartTimer : 0.0f); } - - outmsg.Write(serverSettings.RadiationEnabled); - outmsg.Write((byte)serverSettings.MaxMissionCount); } else { @@ -1958,8 +1936,31 @@ namespace Barotrauma.Networking chatMessageBytes = outmsg.LengthBytes - outmsg.LengthBytes; outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE); - - if (isInitialUpdate) + + bool messageTooLarge = outmsg.LengthBytes > MsgConstants.MTU; + if (messageTooLarge && !isInitialUpdate) + { + string warningMsg = "Maximum packet size exceeded, will send using reliable mode (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n"; + warningMsg += + " Client list size: " + clientListBytes + " bytes\n" + + " Chat message size: " + chatMessageBytes + " bytes\n" + + " Campaign size: " + campaignBytes + " bytes\n" + + " Settings size: " + settingsBytes + " bytes\n"; + if (initialUpdateBytes > 0) + { + warningMsg += + " Initial update size: " + settingsBuf.LengthBytes + " bytes\n"; + } + if (settingsBuf != null) + { + warningMsg += + " Settings buffer size: " + settingsBuf.LengthBytes + " bytes\n"; + } + if (GameSettings.VerboseLogging) { DebugConsole.AddWarning(warningMsg); } + GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:ClientWriteLobby" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg); + } + + if (isInitialUpdate || messageTooLarge) { //the initial update may be very large if the host has a large number //of submarine files, so the message may have to be fragmented @@ -1975,28 +1976,6 @@ namespace Barotrauma.Networking } else { - if (outmsg.LengthBytes > MsgConstants.MTU) - { - string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n"; - errorMsg += - " Client list size: " + clientListBytes + " bytes\n" + - " Chat message size: " + chatMessageBytes + " bytes\n" + - " Campaign size: " + campaignBytes + " bytes\n" + - " Settings size: " + settingsBytes + " bytes\n"; - if (initialUpdateBytes > 0) - { - errorMsg += - " Initial update size: " + settingsBuf.LengthBytes + " bytes\n"; - } - if (settingsBuf != null) - { - errorMsg += - " Settings buffer size: " + settingsBuf.LengthBytes + " bytes\n"; - } - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:ClientWriteLobby" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); - } - serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable); } } @@ -2121,7 +2100,7 @@ namespace Barotrauma.Networking startGameCoroutine = null; string errorMsg = "Starting the round failed. Campaign was still active, but the map has been disposed. Try selecting another game mode."; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("GameServer.StartGame:InvalidCampaignState", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameServer.StartGame:InvalidCampaignState", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); if (OwnerConnection != null) { SendDirectChatMessage(errorMsg, connectedClients.Find(c => c.Connection == OwnerConnection), ChatMessageType.Error); @@ -2163,7 +2142,7 @@ namespace Barotrauma.Networking { string errorMsg = "Failed to start a campaign round (next level not set)."; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("GameServer.StartGame:InvalidCampaignState", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameServer.StartGame:InvalidCampaignState", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); if (OwnerConnection != null) { SendDirectChatMessage(errorMsg, connectedClients.Find(c => c.Connection == OwnerConnection), ChatMessageType.Error); @@ -2392,6 +2371,9 @@ namespace Barotrauma.Networking spawnedCharacter.TeamID = teamID; spawnedCharacter.GiveJobItems(mainSubWaypoints[i]); spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); + spawnedCharacter.Info.InventoryData = new XElement("inventory"); + spawnedCharacter.Info.StartItemsGiven = true; + spawnedCharacter.SaveInventory(); // talents are only avilable for players in online sessions, but modders or someone else might want to have them loaded anyway spawnedCharacter.LoadTalents(); } @@ -2953,7 +2935,7 @@ namespace Barotrauma.Networking { string errorMsg = "Attempted to send a chat message to a null client.\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("GameServer.SendDirectChatMessage:ClientNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameServer.SendDirectChatMessage:ClientNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } @@ -3255,7 +3237,7 @@ namespace Barotrauma.Networking BanClient(c, "ServerMessage.KickedByVoteAutoBan", duration: TimeSpan.FromSeconds(serverSettings.AutoBanTime)); } - GameMain.NetLobbyScreen.LastUpdateID++; + //GameMain.NetLobbyScreen.LastUpdateID++; SendVoteStatus(connectedClients); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs index 1feb6ffc1..d50e6209f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -186,7 +186,7 @@ namespace Barotrauma.Networking DebugConsole.ThrowError(errorMsg, e); } GameAnalyticsManager.AddErrorEventOnce("ServerEntityEventManager.Read:ReadFailed" + entityName, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Failed to read server event for entity \"" + entityName + "\"!\n" + e.StackTrace.CleanupStackTrace()); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs index 8daa8ad3d..629a767cf 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs @@ -125,7 +125,7 @@ namespace Barotrauma.Networking catch (Exception e) { string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("LidgrenServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("LidgrenServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #else @@ -208,15 +208,13 @@ namespace Barotrauma.Networking PendingClient pendingClient = pendingClients.Find(c => (c.Connection is LidgrenConnection l) && l.NetConnection == inc.SenderConnection); - byte incByte = inc.ReadByte(); - bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0; - bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0; + PacketHeader packetHeader = (PacketHeader)inc.ReadByte(); - if (isConnectionInitializationStep && pendingClient != null) + if (packetHeader.IsConnectionInitializationStep() && pendingClient != null) { ReadConnectionInitializationStep(pendingClient, new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false)); } - else if (!isConnectionInitializationStep) + else if (!packetHeader.IsConnectionInitializationStep()) { LidgrenConnection conn = connectedClients.Find(c => (c is LidgrenConnection l) && l.NetConnection == inc.SenderConnection) as LidgrenConnection; if (conn == null) @@ -242,7 +240,7 @@ namespace Barotrauma.Networking //DebugConsole.NewMessage(isCompressed + " " + isConnectionInitializationStep + " " + (int)incByte + " " + length); - IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, conn); + IReadMessage msg = new ReadOnlyMessage(inc.Data, packetHeader.IsCompressed(), inc.PositionInBytes, length, conn); OnMessageReceived?.Invoke(conn, msg); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs index c7b85305c..6af0c5221 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs @@ -102,7 +102,7 @@ namespace Barotrauma.Networking catch (Exception e) { string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("SteamP2PServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("SteamP2PServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #else @@ -125,14 +125,9 @@ namespace Barotrauma.Networking UInt64 senderSteamId = inc.ReadUInt64(); UInt64 ownerSteamId = inc.ReadUInt64(); - byte incByte = inc.ReadByte(); - bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0; - bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0; - bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0; - bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0; - bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0; + PacketHeader packetHeader = (PacketHeader)inc.ReadByte(); - if (isServerMessage) + if (packetHeader.IsServerMessage()) { DebugConsole.ThrowError("Got server message from" + senderSteamId.ToString()); return; @@ -160,7 +155,7 @@ namespace Barotrauma.Networking } return; } - else if (isDisconnectMessage) + else if (packetHeader.IsDisconnectMessage()) { if (pendingClient != null) { @@ -174,12 +169,12 @@ namespace Barotrauma.Networking } return; } - else if (isHeartbeatMessage) + else if (packetHeader.IsHeartbeatMessage()) { //message exists solely as a heartbeat, ignore its contents return; } - else if (isConnectionInitializationStep) + else if (packetHeader.IsConnectionInitializationStep()) { if (pendingClient != null) @@ -203,7 +198,7 @@ namespace Barotrauma.Networking { UInt16 length = inc.ReadUInt16(); - IReadMessage msg = new ReadOnlyMessage(inc.Buffer, isCompressed, inc.BytePosition, length, connectedClient); + IReadMessage msg = new ReadOnlyMessage(inc.Buffer, packetHeader.IsCompressed(), inc.BytePosition, length, connectedClient); OnMessageReceived?.Invoke(connectedClient, msg); } } @@ -211,17 +206,17 @@ namespace Barotrauma.Networking { if (OwnerConnection != null) { (OwnerConnection as SteamP2PConnection).Heartbeat(); } - if (isDisconnectMessage) + if (packetHeader.IsDisconnectMessage()) { DebugConsole.ThrowError("Received disconnect message from owner"); return; } - if (isServerMessage) + if (packetHeader.IsServerMessage()) { DebugConsole.ThrowError("Received server message from owner"); return; } - if (isConnectionInitializationStep) + if (packetHeader.IsConnectionInitializationStep()) { if (OwnerConnection == null) { @@ -236,7 +231,7 @@ namespace Barotrauma.Networking } return; } - if (isHeartbeatMessage) + if (packetHeader.IsHeartbeatMessage()) { return; } @@ -244,7 +239,7 @@ namespace Barotrauma.Networking { UInt16 length = inc.ReadUInt16(); - IReadMessage msg = new ReadOnlyMessage(inc.Buffer, isCompressed, inc.BytePosition, length, OwnerConnection); + IReadMessage msg = new ReadOnlyMessage(inc.Buffer, packetHeader.IsCompressed(), inc.BytePosition, length, OwnerConnection); OnMessageReceived?.Invoke(OwnerConnection, msg); } } @@ -267,7 +262,7 @@ namespace Barotrauma.Networking } IWriteMessage msgToSend = new WriteOnlyMessage(); - byte[] msgData = new byte[msg.LengthBytes]; + byte[] msgData = new byte[16]; msg.PrepareForSending(ref msgData, out bool isCompressed, out int length); msgToSend.Write(conn.SteamID); msgToSend.Write((byte)deliveryMethod); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index 840b4e972..be893a291 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -398,6 +398,7 @@ namespace Barotrauma.Networking else { ReduceCharacterSkills(characterInfos[i]); + characterInfos[i].RemoveSavedStatValuesOnDeath(); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index 7ac6e30e5..64bef50e4 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -14,6 +14,17 @@ namespace Barotrauma.Networking public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml"; public static readonly char SubmarineSeparatorChar = '|'; + public readonly Dictionary LastUpdateIdForFlag = new Dictionary(); + + public void UpdateFlag(NetFlags flag) + => LastUpdateIdForFlag[flag] = (UInt16)(GameMain.NetLobbyScreen.LastUpdateID + 1); + + public NetFlags GetRequiredFlags(Client c) + => LastUpdateIdForFlag.Keys + .Where(k => LastUpdateIdForFlag[k] > c.LastRecvLobbyUpdate) + .Concat(NetFlags.None.ToEnumerable()) //prevents InvalidOperationException in Aggregate + .Aggregate((f1, f2) => f1 | f2); + partial void InitProjSpecific() { LoadSettings(); @@ -37,6 +48,9 @@ namespace Barotrauma.Networking //outMsg.WritePadBits(); //outMsg.Write((UInt16)QueryPort); + NetFlags requiredFlags = GetRequiredFlags(c); + if (!requiredFlags.HasFlag(NetFlags.Properties)) { return; } + WriteNetProperties(outMsg); WriteMonsterEnabled(outMsg); BanList.ServerAdminWrite(outMsg, c); @@ -45,8 +59,18 @@ namespace Barotrauma.Networking public void ServerWrite(IWriteMessage outMsg, Client c) { - outMsg.Write(ServerName); - outMsg.Write(ServerMessageText); + NetFlags requiredFlags = GetRequiredFlags(c); + outMsg.Write((byte)requiredFlags); + if (requiredFlags.HasFlag(NetFlags.Name)) + { + outMsg.Write(ServerName); + } + + if (requiredFlags.HasFlag(NetFlags.Message)) + { + outMsg.Write(ServerMessageText); + } + outMsg.Write((byte)PlayStyle); outMsg.Write((byte)MaxPlayers); outMsg.Write(HasPassword); outMsg.Write(IsPublic); @@ -54,12 +78,13 @@ namespace Barotrauma.Networking outMsg.WritePadBits(); outMsg.WriteRangedInteger(TickRate, 1, 60); - WriteExtraCargo(outMsg); - + if (requiredFlags.HasFlag(NetFlags.Properties)) + { + WriteExtraCargo(outMsg); + } + WriteHiddenSubs(outMsg); - Voting.ServerWrite(outMsg); - if (c.HasPermission(Networking.ClientPermissions.ManageSettings)) { outMsg.Write(true); @@ -85,20 +110,20 @@ namespace Barotrauma.Networking if (flags.HasFlag(NetFlags.Name)) { string serverName = incMsg.ReadString(); - if (ServerName != serverName) changed = true; + if (ServerName != serverName) { changed = true; } ServerName = serverName; } if (flags.HasFlag(NetFlags.Message)) { string serverMessageText = incMsg.ReadString(); - if (ServerMessageText != serverMessageText) changed = true; + if (ServerMessageText != serverMessageText) { changed = true; } ServerMessageText = serverMessageText; } if (flags.HasFlag(NetFlags.Properties)) { - changed |= ReadExtraCargo(incMsg); + bool propertiesChanged = ReadExtraCargo(incMsg); UInt32 count = incMsg.ReadUInt32(); @@ -114,7 +139,7 @@ namespace Barotrauma.Networking { GameServer.Log(GameServer.ClientLogName(c) + " changed " + netProperties[key].Name + " to " + netProperties[key].Value.ToString(), ServerLog.MessageType.ServerMessage); } - changed = true; + propertiesChanged = true; } else { @@ -124,10 +149,13 @@ namespace Barotrauma.Networking } bool changedMonsterSettings = incMsg.ReadBoolean(); incMsg.ReadPadBits(); - changed |= changedMonsterSettings; - if (changedMonsterSettings) ReadMonsterEnabled(incMsg); - changed |= BanList.ServerAdminRead(incMsg, c); - changed |= Whitelist.ServerAdminRead(incMsg, c); + propertiesChanged |= changedMonsterSettings; + if (changedMonsterSettings) { ReadMonsterEnabled(incMsg); } + propertiesChanged |= BanList.ServerAdminRead(incMsg, c); + propertiesChanged |= Whitelist.ServerAdminRead(incMsg, c); + + if (propertiesChanged) { UpdateFlag(NetFlags.Properties); } + changed |= propertiesChanged; } if (flags.HasFlag(NetFlags.HiddenSubs)) @@ -175,12 +203,14 @@ namespace Barotrauma.Networking MaxMissionCount = MathHelper.Clamp(maxMissionCount, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit); changed |= true; + UpdateFlag(NetFlags.Misc); } if (flags.HasFlag(NetFlags.LevelSeed)) { GameMain.NetLobbyScreen.LevelSeed = incMsg.ReadString(); changed |= true; + UpdateFlag(NetFlags.LevelSeed); } if (changed) @@ -271,6 +301,8 @@ namespace Barotrauma.Networking HiddenSubs.UnionWith(doc.Root.GetAttributeStringArray("HiddenSubs", Array.Empty())); + SelectedSubmarine = SelectNonHiddenSubmarine(SelectedSubmarine); + string[] defaultAllowedClientNameChars = new string[] { "32-33", @@ -345,7 +377,6 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.SetBotSpawnMode(BotSpawnMode); GameMain.NetLobbyScreen.SetBotCount(BotCount); - GameMain.NetLobbyScreen.SetMaxMissionCount(MaxMissionCount); List monsterNames = CharacterPrefab.Prefabs.Select(p => p.Identifier).ToList(); MonsterEnabled = new Dictionary(); @@ -355,16 +386,25 @@ namespace Barotrauma.Networking } } - public void SelectNonHiddenSubmarine() + public string SelectNonHiddenSubmarine(string current = null) { - if (HiddenSubs.Contains(GameMain.NetLobbyScreen.SelectedSub.Name)) + current ??= GameMain.NetLobbyScreen.SelectedSub.Name; + if (HiddenSubs.Contains(current)) { - var candidates = GameMain.NetLobbyScreen.GetSubList().Where(s => !HiddenSubs.Contains(s.Name)).ToArray(); + var candidates + = GameMain.NetLobbyScreen.GetSubList().Where(s => !HiddenSubs.Contains(s.Name)).ToArray(); if (candidates.Any()) { GameMain.NetLobbyScreen.SelectedSub = candidates.GetRandom(Rand.RandSync.Unsynced); + return GameMain.NetLobbyScreen.SelectedSub.Name; + } + else + { + HiddenSubs.Remove(current); + return current; } } + return current; } public void LoadClientPermissions() diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/WhiteList.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/WhiteList.cs index aaaa0894f..0e6d43c44 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/WhiteList.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/WhiteList.cs @@ -69,6 +69,8 @@ namespace Barotrauma.Networking { GameServer.Log("Saving whitelist", ServerLog.MessageType.ServerMessage); + GameMain.Server?.ServerSettings?.UpdateFlag(ServerSettings.NetFlags.Properties); + List lines = new List(); if (Enabled) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Program.cs b/Barotrauma/BarotraumaServer/ServerSource/Program.cs index 43583e932..97461c229 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Program.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Program.cs @@ -1,13 +1,13 @@ #region Using Statements using Barotrauma.Steam; -using GameAnalyticsSDK.Net; using System; using Barotrauma.IO; using System.Linq; using System.Text; -using System.Threading; +#if LINUX using System.Runtime.InteropServices; +#endif #endregion @@ -56,7 +56,7 @@ namespace Barotrauma Game = new GameMain(args); Game.Run(); - if (GameSettings.SendUserStatistics) { GameAnalytics.OnQuit(); } + if (GameAnalyticsManager.SendUserStatistics) { GameAnalyticsManager.ShutDown(); } SteamManager.ShutDown(); } @@ -152,6 +152,13 @@ namespace Barotrauma } } + if (GameAnalyticsManager.SendUserStatistics) + { + //send crash report before appending debug console messages (which may contain non-anonymous information) + GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Critical, sb.ToString()); + GameAnalyticsManager.ShutDown(); + } + sb.AppendLine("Last debug messages:"); DebugConsole.Clear(); for (int i = DebugConsole.Messages.Count - 1; i > 0 && i > DebugConsole.Messages.Count - 15; i--) @@ -171,10 +178,8 @@ namespace Barotrauma if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging) { DebugConsole.SaveLogs(); } - if (GameSettings.SendUserStatistics) + if (GameAnalyticsManager.SendUserStatistics) { - GameAnalytics.AddErrorEvent(EGAErrorSeverity.Critical, crashReport); - GameAnalytics.OnQuit(); Console.Write("A crash report (\"servercrashreport.log\") was saved in the root folder of the game and sent to the developers."); } else diff --git a/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs index 77f81cc49..693a77c93 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs @@ -51,42 +51,6 @@ namespace Barotrauma private List campaignSubmarines; - public void AddCampaignSubmarine(SubmarineInfo sub) - { - if (!campaignSubmarines.Contains(sub)) - { - campaignSubmarines.Add(sub); - } - else - { - return; - } - - lastUpdateID++; - if (GameMain.NetworkMember?.ServerSettings != null) - { - GameMain.NetworkMember.ServerSettings.ServerDetailsChanged = true; - } - } - - public void RemoveCampaignSubmarine(SubmarineInfo sub) - { - if (campaignSubmarines.Contains(sub)) - { - campaignSubmarines.Remove(sub); - } - else - { - return; - } - - lastUpdateID++; - if (GameMain.NetworkMember?.ServerSettings != null) - { - GameMain.NetworkMember.ServerSettings.ServerDetailsChanged = true; - } - } - public GameModePreset[] GameModes { get; } private int selectedModeIndex; @@ -212,10 +176,7 @@ namespace Barotrauma } private List subs; - public List GetSubList() - { - return subs; - } + public IReadOnlyList GetSubList() => subs; public string LevelSeed { diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 7b8278d09..a64e5583b 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -1,149 +1,152 @@ - - - - Exe - netcoreapp3.1 - Barotrauma - FakeFish, Undertow Games - Barotrauma Dedicated Server - 0.15.15.0 - Copyright © FakeFish 2018-2020 - AnyCPU;x64 - DedicatedServer - ..\BarotraumaShared\Icon.ico - Debug;Release;Unstable - - - - DEBUG;TRACE;SERVER;WINDOWS;USE_STEAM - x64 - ..\bin\$(Configuration)Windows\ - - - - TRACE;DEBUG;SERVER;WINDOWS;X64;USE_STEAM - x64 - ..\bin\$(Configuration)Windows\ - full - true - - - - TRACE;SERVER;WINDOWS;USE_STEAM - x64 - ..\bin\$(Configuration)Windows\ - - - - TRACE;SERVER;WINDOWS;USE_STEAM - x64 - ..\bin\$(Configuration)Windows\ - true - - - - TRACE;SERVER;WINDOWS;X64;USE_STEAM - x64 - ..\bin\$(Configuration)Windows\ - full - true - - - - TRACE;SERVER;WINDOWS;X64;USE_STEAM - x64 - ..\bin\$(Configuration)Windows\ - full - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $(IntermediateOutputPath)gitver - $(IntermediateOutputPath)gitbranch - - - - - - - - - - - - - - - - - - @(GitVersion) - - - - - - - - - @(GitBranch) - - - - - - - $(IntermediateOutputPath)CustomAssemblyInfo.cs - - - - - - - - - <_Parameter1>GitRevision - <_Parameter2>$(BuildHash) - - - <_Parameter1>GitBranch - <_Parameter2>$(BuildBranch) - - - <_Parameter1>ProjectDir - <_Parameter2>$(ProjectDir) - - - - - - - + + + + Exe + netcoreapp3.1 + Barotrauma + FakeFish, Undertow Games + Barotrauma Dedicated Server + 0.15.17.0 + Copyright © FakeFish 2018-2020 + AnyCPU;x64 + DedicatedServer + ..\BarotraumaShared\Icon.ico + Debug;Release;Unstable + + + + DEBUG;TRACE;SERVER;WINDOWS;USE_STEAM + x64 + ..\bin\$(Configuration)Windows\ + + + + TRACE;DEBUG;SERVER;WINDOWS;X64;USE_STEAM + x64 + ..\bin\$(Configuration)Windows\ + full + true + + + + TRACE;SERVER;WINDOWS;USE_STEAM + x64 + ..\bin\$(Configuration)Windows\ + + + + TRACE;SERVER;WINDOWS;USE_STEAM + x64 + ..\bin\$(Configuration)Windows\ + true + + + + TRACE;SERVER;WINDOWS;X64;USE_STEAM + x64 + ..\bin\$(Configuration)Windows\ + full + true + + + + TRACE;SERVER;WINDOWS;X64;USE_STEAM + x64 + ..\bin\$(Configuration)Windows\ + full + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(IntermediateOutputPath)gitver + $(IntermediateOutputPath)gitbranch + + + + + + + + + + + + + + + + + + @(GitVersion) + + + + + + + + + @(GitBranch) + + + + + + + $(IntermediateOutputPath)CustomAssemblyInfo.cs + + + + + + + + + <_Parameter1>GitRevision + <_Parameter2>$(BuildHash) + + + <_Parameter1>GitBranch + <_Parameter2>$(BuildBranch) + + + <_Parameter1>ProjectDir + <_Parameter2>$(ProjectDir) + + + + + + + diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 2f110d253..4a2e502cc 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -135,6 +135,7 @@ + diff --git a/Barotrauma/BarotraumaShared/DeployGameAnalytics.props b/Barotrauma/BarotraumaShared/DeployGameAnalytics.props new file mode 100644 index 000000000..7c7f3bac4 --- /dev/null +++ b/Barotrauma/BarotraumaShared/DeployGameAnalytics.props @@ -0,0 +1,18 @@ + + + + false + true + + + true + false + + + + + + + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs b/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs index 8189c8fac..77339f0b1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs @@ -1,8 +1,6 @@ using Microsoft.Xna.Framework; -using NLog.Targets; using System; using System.Collections.Generic; -using System.Linq; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index 35ffe424e..9c942ee08 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -1,5 +1,4 @@ -using Barotrauma.Extensions; -using Barotrauma.Items.Components; +using Barotrauma.Items.Components; using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; @@ -125,6 +124,8 @@ namespace Barotrauma minGapSize = ConvertUnits.ToDisplayUnits(Math.Min(colliderWidth, colliderLength)); } + public virtual void OnHealed(Character healer, float healAmount) { } + public virtual void OnAttacked(Character attacker, AttackResult attackResult) { } public virtual void SelectTarget(AITarget target) { } @@ -306,14 +307,15 @@ namespace Barotrauma public void UnequipEmptyItems(Item parentItem, bool avoidDroppingInSea = true) => UnequipEmptyItems(Character, parentItem, avoidDroppingInSea); - public void UnequipContainedItems(Item parentItem, Func predicate = null, bool avoidDroppingInSea = true) => UnequipContainedItems(Character, parentItem, predicate, avoidDroppingInSea); + public void UnequipContainedItems(Item parentItem, Func predicate = null, bool avoidDroppingInSea = true, int? unequipMax = null) => UnequipContainedItems(Character, parentItem, predicate, avoidDroppingInSea, unequipMax); public static void UnequipEmptyItems(Character character, Item parentItem, bool avoidDroppingInSea = true) => UnequipContainedItems(character, parentItem, it => it.Condition <= 0, avoidDroppingInSea); - public static void UnequipContainedItems(Character character, Item parentItem, Func predicate, bool avoidDroppingInSea = true) + public static void UnequipContainedItems(Character character, Item parentItem, Func predicate, bool avoidDroppingInSea = true, int? unequipMax = null) { var inventory = parentItem.OwnInventory; if (inventory == null) { return; } + int removed = 0; if (predicate == null || inventory.AllItems.Any(predicate)) { foreach (Item containedItem in inventory.AllItemsMod) @@ -326,10 +328,12 @@ namespace Barotrauma // If we are not inside a friendly sub (= same team), try to put the item in the inventory instead dropping it. if (character.Inventory.TryPutItem(containedItem, character, CharacterInventory.anySlot)) { + if (unequipMax.HasValue && ++removed >= unequipMax) { return; } continue; } } containedItem.Drop(character); + if (unequipMax.HasValue && ++removed >= unequipMax) { return; } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs index 3522c45dc..a5d3ca142 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs @@ -76,7 +76,7 @@ namespace Barotrauma { string errorMsg = "Invalid AITarget sector direction (" + value + ")\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("AITarget.SectorDir:" + entity?.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("AITarget.SectorDir:" + entity?.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } sectorDir = value; @@ -125,7 +125,7 @@ namespace Barotrauma DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace()); #endif GameAnalyticsManager.AddErrorEventOnce("AITarget.WorldPosition:EntityRemoved", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace()); return Vector2.Zero; } @@ -144,7 +144,7 @@ namespace Barotrauma DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace()); #endif GameAnalyticsManager.AddErrorEventOnce("AITarget.WorldPosition:EntityRemoved", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace()); return Vector2.Zero; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 611ba59f0..0500a03c8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -339,11 +339,15 @@ namespace Barotrauma { targetingTag = "dead"; } + else if (AIParams.TryGetTarget(targetCharacter.CharacterHealth.GetActiveAfflictionTags(), out CharacterParams.TargetParams tp) && tp.Threshold > Character.GetDamageDoneByAttacker(targetCharacter)) + { + targetingTag = tp.Tag; + } else if (PetBehavior != null && aiTarget.Entity == PetBehavior.Owner) { - targetingTag = "owner"; + targetingTag = "owner"; } - else if (AIParams.TryGetTarget(targetCharacter.SpeciesName, out CharacterParams.TargetParams tP)) + else if (AIParams.TryGetTarget(targetCharacter, out CharacterParams.TargetParams tP)) { targetingTag = tP.Tag; } @@ -353,7 +357,7 @@ namespace Barotrauma { targetingTag = "husk"; } - else + else if (!Character.IsFriendly(targetCharacter)) { if (enemy.CombatStrength > CombatStrength) { @@ -386,6 +390,10 @@ namespace Barotrauma { targetingTag = "sonar"; } + if (targetItem.GetComponent() != null) + { + targetingTag = "door"; + } } } else if (aiTarget.Entity is Structure) @@ -511,8 +519,7 @@ namespace Barotrauma } else if (avoidTimer <= 0 || activeTriggers.Any() && returnTimer <= 0) { - CharacterParams.TargetParams targetingParams = null; - UpdateTargets(Character, out targetingParams); + UpdateTargets(out CharacterParams.TargetParams targetingParams); updateTargetsTimer = updateTargetsInterval * Rand.Range(0.75f, 1.25f); if (SelectedAiTarget == null) { @@ -1973,7 +1980,7 @@ namespace Barotrauma ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100); } } - else if (canAttack && attacker.IsHuman && AIParams.TryGetTarget(attacker.SpeciesName, out CharacterParams.TargetParams targetingParams)) + else if (canAttack && attacker.IsHuman && AIParams.TryGetTarget(attacker, out CharacterParams.TargetParams targetingParams)) { if (targetingParams.State == AIState.Aggressive || targetingParams.State == AIState.PassiveAggressive) { @@ -2362,7 +2369,7 @@ namespace Barotrauma //goes through all the AItargets, evaluates how preferable it is to attack the target, //whether the Character can see/hear the target and chooses the most preferable target within //sight/hearing range - public AITarget UpdateTargets(Character character, out CharacterParams.TargetParams targetingParams) + public AITarget UpdateTargets(out CharacterParams.TargetParams targetingParams) { AITarget newTarget = null; targetValue = 0; @@ -2386,70 +2393,35 @@ namespace Barotrauma } Character targetCharacter = aiTarget.Entity as Character; //ignore the aitarget if it is the Character itself - if (targetCharacter == character) { continue; } + if (targetCharacter == Character) { continue; } float valueModifier = 1; - string targetingTag = null; + string targetingTag = GetTargetingTag(aiTarget); if (targetCharacter != null) { // ignore if target is tagged to be explicitly ignored (Feign Death) if (targetCharacter.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { continue; } - - if (targetCharacter.IsDead) + if (AIParams.Targets.None() && Character.IsFriendly(targetCharacter)) { - targetingTag = "dead"; + continue; } - else if (PetBehavior != null && aiTarget.Entity == PetBehavior.Owner) + if (targetCharacter.AIController is EnemyAIController enemy) { - targetingTag = "owner"; - } - else if (AIParams.TryGetTarget(targetCharacter.SpeciesName, out CharacterParams.TargetParams tP)) - { - targetingTag = tP.Tag; - } - else - { - if (Character.IsFriendly(targetCharacter)) + if (targetingTag == "stronger" && (State == AIState.Avoid || State == AIState.Escape || State == AIState.Flee)) { - continue; - } - if (targetCharacter.AIController is EnemyAIController enemy) - { - if (targetCharacter.IsHusk && AIParams.HasTag("husk")) + if (SelectedAiTarget == aiTarget) { - targetingTag = "husk"; + // Freightened -> hold on to the target + valueModifier *= 2; } - else + if (IsBeingChasedBy(targetCharacter)) { - if (enemy.CombatStrength > CombatStrength) - { - targetingTag = "stronger"; - } - else if (enemy.CombatStrength < CombatStrength) - { - targetingTag = "weaker"; - } - else - { - targetingTag = "equal"; - } - if (targetingTag == "stronger" && (State == AIState.Avoid || State == AIState.Escape || State == AIState.Flee)) - { - if (SelectedAiTarget == aiTarget) - { - // Freightened -> hold on to the target - valueModifier *= 2; - } - if (IsBeingChasedBy(targetCharacter)) - { - valueModifier *= 2; - } - if (Character.CurrentHull != null && !VisibleHulls.Contains(targetCharacter.CurrentHull)) - { - // Inside but in a different room - valueModifier /= 2; - } - } + valueModifier *= 2; + } + if (Character.CurrentHull != null && !VisibleHulls.Contains(targetCharacter.CurrentHull)) + { + // Inside but in a different room + valueModifier /= 2; } } } @@ -2469,7 +2441,7 @@ namespace Barotrauma if (aiTarget.Entity is Hull hull) { // Ignore the target if it's a room and the character is already inside a sub - if (character.CurrentHull != null) { continue; } + if (Character.CurrentHull != null) { continue; } // Ignore ruins if (hull.Submarine == null) { continue; } if (hull.Submarine.Info.IsRuin) { continue; } @@ -2479,7 +2451,7 @@ namespace Barotrauma if (aiTarget.Entity is Item item) { door = item.GetComponent(); - bool targetingFromOutsideToInside = item.CurrentHull != null && character.CurrentHull == null; + bool targetingFromOutsideToInside = item.CurrentHull != null && Character.CurrentHull == null; if (targetingFromOutsideToInside) { if (door != null && (!canAttackDoors && !AIParams.CanOpenDoors) || !canAttackWalls) @@ -2488,28 +2460,12 @@ namespace Barotrauma continue; } } - foreach (var prio in AIParams.Targets) + if (door == null && targetingFromOutsideToInside) { - if (item.HasTag(prio.Tag)) + if (item.Submarine?.Info is { IsRuin: true }) { - targetingTag = prio.Tag; - break; - } - } - if (door == null && targetingTag == null) - { - if (item.GetComponent() != null) - { - targetingTag = "sonar"; - } - else if (targetingFromOutsideToInside) - { - targetingTag = "room"; - if (item.Submarine?.Info.IsRuin != null) - { - // Ignore ruin items when the creature is outside. - continue; - } + // Ignore ruin items when the creature is outside. + continue; } } else if (targetingTag == "nasonov") @@ -2521,14 +2477,13 @@ namespace Barotrauma } } // Ignore the target if it's a decoy and the character is already inside a sub - if (character.CurrentHull != null && targetingTag == "decoy") + if (Character.CurrentHull != null && targetingTag == "decoy") { continue; } } else if (aiTarget.Entity is Structure s) { - targetingTag = "wall"; if (!s.HasBody) { // Ignore structures that doesn't have a body (not walls) @@ -2537,7 +2492,7 @@ namespace Barotrauma if (s.IsPlatform) { continue; } if (s.Submarine == null) { continue; } if (s.Submarine.Info.IsRuin) { continue; } - bool isCharacterInside = character.CurrentHull != null; + bool isCharacterInside = Character.CurrentHull != null; bool isInnerWall = s.prefab.Tags.Contains("inner"); if (isInnerWall && !isCharacterInside) { @@ -2624,21 +2579,12 @@ namespace Barotrauma } } } - else - { - targetingTag = "room"; - } if (door != null) { - // If there's not a more specific tag for the door - if (string.IsNullOrEmpty(targetingTag) || targetingTag == "room") - { - targetingTag = "door"; - } if (door.Item.Submarine == null) { continue; } bool isOutdoor = door.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom; // Ignore inner doors when outside - if (character.CurrentHull == null && !isOutdoor) { continue; } + if (Character.CurrentHull == null && !isOutdoor) { continue; } bool isOpen = door.CanBeTraversed; if (!isOpen) { @@ -2651,7 +2597,7 @@ namespace Barotrauma } if (IsAggressiveBoarder) { - if (character.CurrentHull == null) + if (Character.CurrentHull == null) { // Increase the priority if the character is outside and the door is from outside to inside if (door.CanBeTraversed) @@ -2679,14 +2625,14 @@ namespace Barotrauma if (targetingTag == null) { continue; } var targetParams = GetTargetParams(targetingTag); if (targetParams == null) { continue; } - if (targetParams.IgnoreInside && character.CurrentHull != null) { continue; } - if (targetParams.IgnoreOutside && character.CurrentHull == null) { continue; } + if (targetParams.IgnoreInside && Character.CurrentHull != null) { continue; } + if (targetParams.IgnoreOutside && Character.CurrentHull == null) { continue; } if (targetParams.IgnoreIncapacitated && targetCharacter != null && targetCharacter.IsIncapacitated) { continue; } if (targetParams.IgnoreIfNotInSameSub) { if (aiTarget.Entity.Submarine != Character.Submarine) { continue; } var targetHull = targetCharacter != null ? targetCharacter.CurrentHull : aiTarget.Entity is Item it ? it.CurrentHull : null; - if ((targetHull == null) != (character.CurrentHull == null)) { continue; } + if ((targetHull == null) != (Character.CurrentHull == null)) { continue; } } if (targetParams.State == AIState.Observe || targetParams.State == AIState.Eat) { @@ -2706,7 +2652,7 @@ namespace Barotrauma { target = selectedTargetingParams == targetParams ? targetParams.ThresholdMax : targetParams.ThresholdMin; } - if (character.HealthPercentage > target) + if (Character.HealthPercentage > target) { continue; } @@ -2721,7 +2667,7 @@ namespace Barotrauma // Halve the priority for each swarm mate targeting the same target -> reduces stacking foreach (Character otherCharacter in SwarmBehavior.Members) { - if (otherCharacter == character) { continue; } + if (otherCharacter == Character) { continue; } if (otherCharacter.AIController?.SelectedAiTarget != aiTarget) { continue; } valueModifier /= 2; } @@ -2731,15 +2677,15 @@ namespace Barotrauma // The same as above, but using all the friendly characters in the level. foreach (Character otherCharacter in Character.CharacterList) { - if (otherCharacter == character) { continue; } + if (otherCharacter == Character) { continue; } if (otherCharacter.AIController?.SelectedAiTarget != aiTarget) { continue; } - if (!character.IsFriendly(otherCharacter)) { continue; } + if (!Character.IsFriendly(otherCharacter)) { continue; } valueModifier /= 2; } } } if (!aiTarget.IsWithinSector(WorldPosition)) { continue; } - Vector2 toTarget = aiTarget.WorldPosition - character.WorldPosition; + Vector2 toTarget = aiTarget.WorldPosition - Character.WorldPosition; float dist = toTarget.Length(); float nonModifiedDist = dist; //if the target has been within range earlier, the character will notice it more easily @@ -2829,7 +2775,7 @@ namespace Barotrauma Character owner = GetOwner(i); // Don't target items that we own. // This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive) - if (owner == character) { continue; } + if (owner == Character) { continue; } if (owner != null && (Character.IsFriendly(owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget))) { continue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 6e654e1cb..3aa38677b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -85,6 +85,7 @@ namespace Barotrauma /// List of previous attacks done to this character /// private readonly Dictionary previousAttackResults = new Dictionary(); + private readonly Dictionary previousHealAmounts = new Dictionary(); private readonly SteeringManager outsideSteering, insideSteering; @@ -187,6 +188,15 @@ namespace Barotrauma foreach (var previousAttackResult in previousAttackResults) { RespondToAttack(previousAttackResult.Key, previousAttackResult.Value); + if (previousHealAmounts.ContainsKey(previousAttackResult.Key)) + { + //gradually forget past heals + previousHealAmounts[previousAttackResult.Key] = Math.Min(previousHealAmounts[previousAttackResult.Key] - 5.0f, 100.0f); + if (previousHealAmounts[previousAttackResult.Key] <= 0.0f) + { + previousHealAmounts.Remove(previousAttackResult.Key); + } + } } previousAttackResults.Clear(); respondToAttackTimer = RespondToAttackInterval; @@ -237,39 +247,41 @@ namespace Barotrauma if (Character.Submarine == null) { - // When the character is outside, far enough from the target, and the direct route is blocked, - // use the indoor steering with the main and side path waypoints to help avoid getting stuck in level walls - if (SelectedAiTarget?.Entity != null && !IsCloseEnoughToTarget(2000, useTargetSub: false)) + obstacleRaycastTimer -= deltaTime; + if (obstacleRaycastTimer <= 0) { - obstacleRaycastTimer -= deltaTime; - if (obstacleRaycastTimer <= 0) + obstacleRaycastTimer = obstacleRaycastIntervalLong; + if (SelectedAiTarget?.Entity == null || SelectedAiTarget.Entity is ISpatialEntity target && target.Submarine == null || !IsCloseEnoughToTarget(2000, useTargetSub: false)) { - obstacleRaycastTimer = obstacleRaycastIntervalLong; - Vector2 rayEnd = SelectedAiTarget.Entity.SimPosition; - if (SelectedAiTarget.Entity.Submarine != null) + // If the target is behind a level wall, switch to the pathing to get around the obstacles. + ISpatialEntity spatialTarget = SelectedAiTarget?.Entity; + if (spatialTarget == null) { - rayEnd += SelectedAiTarget.Entity.Submarine.SimPosition; + var gotoObjective = ObjectiveManager.GetActiveObjective(); + spatialTarget = gotoObjective?.Target; } - IEnumerable ignoredBodies = null; - if (SelectedAiTarget.Entity is ISpatialEntity spatialTarget) + if (spatialTarget == null) { + UseIndoorSteeringOutside = false; + } + else + { + IEnumerable ignoredBodies = null; + Vector2 rayEnd = spatialTarget.SimPosition; Submarine targetSub = spatialTarget.Submarine; if (targetSub != null) { + rayEnd += targetSub.SimPosition; ignoredBodies = targetSub.PhysicsBody.FarseerBody.ToEnumerable(); } + var obstacle = Submarine.PickBody(SimPosition, rayEnd, ignoredBodies, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall); + UseIndoorSteeringOutside = obstacle != null; } - var obstacle = Submarine.PickBody(SimPosition, rayEnd, ignoredBodies, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall); - UseIndoorSteeringOutside = obstacle != null; } - } - else - { - UseIndoorSteeringOutside = false; - if (hasValidPath) + else { - obstacleRaycastTimer -= deltaTime; - if (obstacleRaycastTimer <= 0) + UseIndoorSteeringOutside = false; + if (hasValidPath) { obstacleRaycastTimer = obstacleRaycastIntervalShort; // Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs). @@ -332,25 +344,10 @@ namespace Barotrauma } } } + + IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => c.Area.Contains(Character.WorldPosition)) is Level.Cave; - // Check whether the character is inside a cave - if (IsInsideCave) - { - // If the character was inside a cave, require them to move a bit further from the area to set the field back to false - // This is to avoid any twitchy behavior with the steering managers - IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => - { - var area = c.Area; - area.Inflate(new Vector2(100)); - return area.Contains(Character.WorldPosition); - }) is Level.Cave; - } - else - { - IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => c.Area.Contains(Character.WorldPosition)) is Level.Cave; - } - - if (UseIndoorSteeringOutside || IsInsideCave || Character.CurrentHull?.Submarine != null || hasValidPath || IsCloseEnoughToTarget(steeringBuffer)) + if (UseIndoorSteeringOutside || Character.CurrentHull?.Submarine != null || hasValidPath || IsCloseEnoughToTarget(steeringBuffer)) { if (steeringManager != insideSteering) { @@ -601,7 +598,7 @@ namespace Barotrauma takeMaskOff = false; break; } - else if (gotoObjective.mimic) + else if (gotoObjective.Mimic) { if (!removeSuit) { @@ -880,7 +877,8 @@ namespace Barotrauma if (target.CurrentHull != hull || !target.Enabled) { continue; } if (AIObjectiveFightIntruders.IsValidTarget(target, Character)) { - if (AddTargets(Character, target) && newOrder == null) + bool arrested = AIObjectiveFightIntruders.ShouldArrest(target, Character) && target.HasEquippedItem("handlocker"); + if (!arrested && AddTargets(Character, target) && newOrder == null) { var orderPrefab = Order.GetPrefab("reportintruders"); newOrder = new Order(orderPrefab, hull, null, orderGiver: Character); @@ -1033,6 +1031,19 @@ namespace Barotrauma } } + public override void OnHealed(Character healer, float healAmount) + { + if (healer == null || healAmount <= 0.0f) { return; } + if (previousHealAmounts.ContainsKey(healer)) + { + previousHealAmounts[healer] += healAmount; + } + else + { + previousHealAmounts.Add(healer, healAmount); + } + } + public override void OnAttacked(Character attacker, AttackResult attackResult) { // The attack incapacitated/killed the character: respond immediately to trigger nearby characters because the update loop no longer runs @@ -1076,11 +1087,16 @@ namespace Barotrauma } private void RespondToAttack(Character attacker, AttackResult attackResult) - { + { + float healAmount = 0.0f; + if (attacker != null) + { + previousHealAmounts.TryGetValue(attacker, out healAmount); + } // excluding poisons etc - float realDamage = attackResult.Damage; + float realDamage = attackResult.Damage - healAmount; // including poisons etc - float totalDamage = realDamage; + float totalDamage = realDamage - healAmount; if (attackResult.Afflictions != null) { foreach (Affliction affliction in attackResult.Afflictions) @@ -1128,7 +1144,7 @@ namespace Barotrauma // Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate). return; } - float cumulativeDamage = GetDamageDoneByAttacker(attacker); + float cumulativeDamage = Character.GetDamageDoneByAttacker(attacker); bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && Character.CombatAction == null; if (isAccidental) { @@ -1197,7 +1213,7 @@ namespace Barotrauma if (Character.Submarine != null && Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine)) { // Non-friendly - InformOtherNPCs(GetDamageDoneByAttacker(attacker)); + InformOtherNPCs(Character.GetDamageDoneByAttacker(attacker)); } if (Character.IsBot) { @@ -1601,21 +1617,19 @@ namespace Barotrauma } } - public static void ItemTaken(Item item, Character character) + public static void ItemTaken(Item item, Character thief) { - if (item == null || character == null || item.GetComponent() != null) { return; } - Character thief = character; - bool someoneSpoke = false; + if (item == null || thief == null || item.GetComponent() != null) { return; } + bool someoneSpoke = false; bool stolenItemsInside = item.OwnInventory?.FindAllItems(it => it.SpawnedInCurrentOutpost && !it.AllowStealing, recursive: true).Any() ?? false; if ((item.SpawnedInCurrentOutpost && !item.AllowStealing || stolenItemsInside) && thief.TeamID != CharacterTeamType.FriendlyNPC && !item.HasTag("handlocker")) { foreach (Character otherCharacter in Character.CharacterList) { - if (otherCharacter == thief || otherCharacter.TeamID == thief.TeamID || otherCharacter.IsDead || - otherCharacter.Info?.Job == null || - !(otherCharacter.AIController is HumanAIController otherHumanAI) || + if (otherCharacter == thief || otherCharacter.TeamID == thief.TeamID || otherCharacter.IsIncapacitated || otherCharacter.Stun > 0.0f || + otherCharacter.Info?.Job == null || !(otherCharacter.AIController is HumanAIController otherHumanAI) || !otherHumanAI.VisibleHulls.Contains(thief.CurrentHull)) { continue; @@ -1624,13 +1638,13 @@ namespace Barotrauma if (!otherCharacter.CanSeeCharacter(thief)) { continue; } // Don't react if the player is taking an extinguisher and there's any fires on the sub, or diving gear when the sub is flooding // -> allow them to use the emergency items - if (character.Submarine != null) + if (thief.Submarine != null) { - var connectedHulls = character.Submarine.GetHulls(alsoFromConnectedSubs: true); + var connectedHulls = thief.Submarine.GetHulls(alsoFromConnectedSubs: true); if (item.HasTag("fireextinguisher") && connectedHulls.Any(h => h.FireSources.Any())) { continue; } if (item.HasTag("diving") && connectedHulls.Any(h => h.ConnectedGaps.Any(g => AIObjectiveFixLeaks.IsValidTarget(g, thief)))) { continue; } } - if (!someoneSpoke && !character.IsIncapacitated && character.Stun <= 0.0f) + if (!someoneSpoke) { if (!item.StolenDuringRound && GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null) { @@ -1663,7 +1677,7 @@ namespace Barotrauma } else if (item.OwnInventory?.FindItem(it => it.SpawnedInCurrentOutpost && !item.AllowStealing, true) is { } foundItem) { - ItemTaken(foundItem, character); + ItemTaken(foundItem, thief); } bool TriggerSecurity(HumanAIController humanAI) @@ -1791,17 +1805,6 @@ namespace Barotrauma humanAI.ObjectiveManager.GetObjective()?.ReportedTargets.Remove(target)); } - public float GetDamageDoneByAttacker(Character otherCharacter) - { - float dmg = 0; - Character.Attacker attacker = Character.LastAttackers.LastOrDefault(a => a.Character == otherCharacter); - if (attacker != null) - { - dmg = attacker.Damage; - } - return dmg; - } - private void StoreHullSafety(Hull hull, HullSafety safety) { if (knownHulls.ContainsKey(hull)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index 418b72362..fd2d75b5e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -26,10 +26,9 @@ namespace Barotrauma private float findPathTimer; + private float checkDoorsTimer; private float buttonPressCooldown; - const float ButtonPressInterval = 0.25f; - public SteeringPath CurrentPath { get { return currentPath; } @@ -97,9 +96,10 @@ namespace Barotrauma public override void Update(float speed) { base.Update(speed); - - buttonPressCooldown -= 1.0f / 60.0f; - findPathTimer -= 1.0f / 60.0f; + float step = 1.0f / 60.0f; + checkDoorsTimer -= step; + buttonPressCooldown -= step; + findPathTimer -= step; } public void SetPath(SteeringPath path) @@ -204,24 +204,23 @@ namespace Barotrauma pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && character.PressureProtection <= 0; var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility); bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0; - if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable) + if (!useNewPath && currentPath?.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable) { // Check if the new path is the same as the old, in which case we just ignore it and continue using the old path (or the progress would reset). if (IsIdenticalPath()) { useNewPath = false; } - else + else if (!character.IsClimbing) { // Use the new path if it has significantly lower cost (don't change the path if it has marginally smaller cost. This reduces navigating backwards due to new path that is calculated from the node just behind us). float t = (float)currentPath.CurrentIndex / (currentPath.Nodes.Count - 1); useNewPath = newPath.Cost < currentPath.Cost * MathHelper.Lerp(0.95f, 0, t); - if (!useNewPath && character.Submarine != null && !character.IsClimbing) + if (!useNewPath && character.Submarine != null) { // It's possible that the current path was calculated from a start point that is no longer valid. // Therefore, let's accept also paths with a greater cost than the current, if the current node is much farther than the new start node. // This is a special case for cases e.g. where the character falls and thus needs a new path. - // Don't do this outside or when climbing ladders, because both cause issues. useNewPath = Vector2.DistanceSquared(character.WorldPosition, currentPath.CurrentNode.WorldPosition) > Math.Pow(Vector2.Distance(character.WorldPosition, newPath.Nodes.First().WorldPosition) * 3, 2); } } @@ -315,7 +314,8 @@ namespace Barotrauma return currentTarget - pos2; } bool doorsChecked = false; - if (!character.LockHands && buttonPressCooldown <= 0.0f) + checkDoorsTimer = Math.Min(checkDoorsTimer, GetDoorCheckTime()); + if (!character.LockHands && checkDoorsTimer <= 0.0f) { CheckDoorsInPath(); doorsChecked = true; @@ -336,7 +336,7 @@ namespace Barotrauma { if (character.CanInteractWith(ladders.Item)) { - ladders.Item.TryInteract(character, false, true); + ladders.Item.TryInteract(character, forceSelectKey: true); } else { @@ -347,7 +347,7 @@ namespace Barotrauma if (previousLadders != null && previousLadders != ladders && character.SelectedConstruction != previousLadders.Item && character.CanInteractWith(previousLadders.Item) && Math.Abs(previousLadders.Item.WorldPosition.X - ladders.Item.WorldPosition.X) < 5) { - previousLadders.Item.TryInteract(character, false, true); + previousLadders.Item.TryInteract(character, forceSelectKey: true); } } } @@ -387,7 +387,7 @@ namespace Barotrauma // Try to change the ladder (hatches between two submarines) if (character.SelectedConstruction != nextLadder.Item && nextLadder.Item.IsInsideTrigger(character.WorldPosition)) { - nextLadder.Item.TryInteract(character, false, true); + nextLadder.Item.TryInteract(character, forceSelectKey: true); } } if (isAboveFloor || nextLadderSameAsCurrent) @@ -487,7 +487,14 @@ namespace Barotrauma else { // We'll want this to run each time, because the delegate is used to find a valid button component. - bool canAccessButtons = door.Item.GetConnectedComponents(true).Any(b => b.HasAccess(character) && (buttonFilter == null || buttonFilter(b))); + bool canAccessButtons = false; + foreach (var button in door.Item.GetConnectedComponents(true)) + { + if (button.HasAccess(character) && (buttonFilter == null || buttonFilter(button))) + { + canAccessButtons = true; + } + } return canAccessButtons || door.IsOpen || ShouldBreakDoor(door); } } @@ -500,8 +507,22 @@ namespace Barotrauma return ConvertUnits.ToDisplayUnits(Math.Max(colliderSize.X, colliderSize.Y)); } + private (Door door, bool state) lastDoor; + private float GetDoorCheckTime() + { + if (steering.LengthSquared() > 0) + { + return character.AnimController.IsMovingFast ? 0.1f : 0.3f; + } + else + { + return float.PositiveInfinity; + } + } + private void CheckDoorsInPath() { + checkDoorsTimer = GetDoorCheckTime(); if (!canOpenDoors) { return; } for (int i = 0; i < 5; i++) { @@ -518,8 +539,7 @@ namespace Barotrauma } else { - bool closeDoors = character.IsBot && character.IsInFriendlySub || character.Params.AI != null && character.Params.AI.KeepDoorsClosed; - if (i == 0 || !closeDoors) + if (i == 0) { currentWaypoint = currentPath.CurrentNode; nextWaypoint = currentPath.NextNode; @@ -540,7 +560,7 @@ namespace Barotrauma if (currentWaypoint.ConnectedDoor.LinkedGap != null) { // Keep the airlock doors closed, but not in ruins/wrecks - if (currentWaypoint.ConnectedDoor.LinkedGap.IsRoomToRoom || currentWaypoint.Submarine?.Info.IsRuin != null || currentWaypoint.Submarine?.Info.IsWreck != null) + if (currentWaypoint.ConnectedDoor.LinkedGap.IsRoomToRoom && currentWaypoint.CurrentHull is { IsWetRoom: false } || currentWaypoint.Submarine == null || currentWaypoint.Submarine.Info.IsRuin || currentWaypoint.Submarine.Info.IsWreck) { shouldBeOpen = true; door = currentWaypoint.ConnectedDoor; @@ -566,28 +586,49 @@ namespace Barotrauma } if (door == null) { return; } + + if (door.BotsShouldKeepOpen) { shouldBeOpen = true; } - //toggle the door if it's the previous node and open, or if it's current node and closed if ((door.IsOpen || door.IsBroken) != shouldBeOpen) { + if (!shouldBeOpen) + { + if (character.AIController is HumanAIController humanAI) + { + bool keepDoorsClosed = character.IsBot && door.Item.Submarine?.TeamID == character.TeamID || character.Params.AI != null && character.Params.AI.KeepDoorsClosed; + if (!keepDoorsClosed) { return; } + bool isInAirlock = door.Item.CurrentHull is { IsWetRoom: true } || character.CurrentHull is { IsWetRoom: true }; + if (!isInAirlock) + { + // Don't slam the door at anyones face + if (Character.CharacterList.Any(c => c != character && humanAI.IsFriendly(c) && humanAI.VisibleHulls.Contains(c.CurrentHull) && !c.IsUnconscious)) + { + return; + } + } + } + } Controller closestButton = null; float closestDist = 0; bool canAccess = CanAccessDoor(door, button => { if (currentWaypoint == null) { return true; } - // Check that the button is on the right side of the door. - if (door.LinkedGap.IsHorizontal) + // Check that the button is on the right side of the door. If the door is open, doesn't matter + if (!door.IsOpen) { - int dir = Math.Sign((nextWaypoint ?? currentWaypoint).WorldPosition.X - door.Item.WorldPosition.X); - if (button.Item.WorldPosition.X * dir > door.Item.WorldPosition.X * dir) { return false; } - } - else - { - int dir = Math.Sign((nextWaypoint ?? currentWaypoint).WorldPosition.Y - door.Item.WorldPosition.Y); - if (button.Item.WorldPosition.Y * dir > door.Item.WorldPosition.Y * dir) { return false; } + if (door.LinkedGap.IsHorizontal) + { + int dir = Math.Sign((nextWaypoint ?? currentWaypoint).WorldPosition.X - door.Item.WorldPosition.X); + if (button.Item.WorldPosition.X * dir > door.Item.WorldPosition.X * dir) { return false; } + } + else + { + int dir = Math.Sign((nextWaypoint ?? currentWaypoint).WorldPosition.Y - door.Item.WorldPosition.Y); + if (button.Item.WorldPosition.Y * dir > door.Item.WorldPosition.Y * dir) { return false; } + } } float distance = Vector2.DistanceSquared(button.Item.WorldPosition, character.WorldPosition); - if (closestButton == null || distance < closestDist) + if (closestButton == null || distance < closestDist && character.CanSeeTarget(button.Item)) { closestButton = button; closestDist = distance; @@ -596,18 +637,39 @@ namespace Barotrauma }); if (canAccess) { + bool pressButton = buttonPressCooldown <= 0 || lastDoor.door != door || lastDoor.state != shouldBeOpen; if (door.HasIntegratedButtons) { - door.Item.TryInteract(character, false, true); - buttonPressCooldown = ButtonPressInterval; + if (pressButton && character.CanSeeTarget(door.Item)) + { + if (door.Item.TryInteract(character, forceSelectKey: true)) + { + lastDoor = (door, shouldBeOpen); + buttonPressCooldown = 3; + } + else + { + buttonPressCooldown = 0; + } + } break; } else if (closestButton != null) { - if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance + GetColliderLength(), 2)) + if (closestDist < MathUtils.Pow2(closestButton.Item.InteractDistance + GetColliderLength())) { - closestButton.Item.TryInteract(character, false, true); - buttonPressCooldown = ButtonPressInterval; + if (pressButton) + { + if (closestButton.Item.TryInteract(character, forceSelectKey: true)) + { + lastDoor = (door, shouldBeOpen); + buttonPressCooldown = 3; + } + else + { + buttonPressCooldown = 0; + } + } break; } else @@ -627,6 +689,7 @@ namespace Barotrauma // The button is on the wrong side of the door or a wall currentPath.Unreachable = true; } + lastDoor = (null, false); return; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs index 88d3df439..a98cef44d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs @@ -207,7 +207,8 @@ namespace Barotrauma var cells = Level.Loaded.GetCells(character.WorldPosition, 1); if (cells.Count > 0) { - float closestDist = float.PositiveInfinity; + //ignore walls more than 200 meters away + float closestDist = 200.0f * 200.0f; foreach (Voronoi2.VoronoiCell cell in cells) { foreach (Voronoi2.GraphEdge edge in cell.Edges) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs index 6986c8940..905cffa20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs @@ -283,9 +283,9 @@ namespace Barotrauma /// public float CalculatePriority() { + ForceWalk = false; Priority = GetPriority(); ForceHighestPriority = false; - ForceWalk = false; return Priority; } @@ -501,5 +501,34 @@ namespace Barotrauma } } } + + protected static bool CanEquip(Character character, Item item) + { + bool canEquip = item != null; + if (canEquip && !item.AllowedSlots.Contains(InvSlotType.Any)) + { + canEquip = false; + var inv = character.Inventory; + foreach (var allowedSlot in item.AllowedSlots) + { + foreach (var slotType in inv.SlotTypes) + { + if (!allowedSlot.HasFlag(slotType)) { continue; } + for (int i = 0; i < inv.Capacity; i++) + { + canEquip = true; + if (allowedSlot.HasFlag(inv.SlotTypes[i]) && inv.GetItemAt(i) != null) + { + canEquip = false; + break; + } + } + } + } + } + return canEquip; + } + + protected bool CanEquip(Item item) => CanEquip(character, item); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs index dab9b3d1f..f3197b975 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs @@ -68,15 +68,12 @@ namespace Barotrauma protected override void OnObjectiveCompleted(AIObjective objective, Item target) => HumanAIController.RemoveTargets(character, target); - private static bool IsItemInsideValidSubmarine(Item item, Character character) + public static bool IsItemInsideValidSubmarine(Item item, Character character) { if (item.CurrentHull == null) { return false; } if (item.Submarine == null) { return false; } if (item.Submarine.TeamID != character.TeamID) { return false; } - if (character.Submarine != null) - { - if (!character.Submarine.IsConnectedTo(item.Submarine)) { return false; } - } + if (character.Submarine != null && !character.Submarine.IsConnectedTo(item.Submarine)) { return false; } return true; } @@ -129,29 +126,7 @@ namespace Barotrauma { return true; } - bool canEquip = true; - if (!item.AllowedSlots.Contains(InvSlotType.Any)) - { - canEquip = false; - var inv = character.Inventory; - foreach (var allowedSlot in item.AllowedSlots) - { - foreach (var slotType in inv.SlotTypes) - { - if (!allowedSlot.HasFlag(slotType)) { continue; } - for (int i = 0; i < inv.Capacity; i++) - { - canEquip = true; - if (allowedSlot.HasFlag(inv.SlotTypes[i]) && inv.GetItemAt(i) != null) - { - canEquip = false; - break; - } - } - } - } - } - return canEquip; + return CanEquip(character, item); } public override void OnDeselected() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 857c56da9..b1b366526 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -111,7 +111,7 @@ namespace Barotrauma public CombatMode Mode { get; private set; } private bool IsOffensiveOrArrest => initialMode == CombatMode.Offensive || initialMode == CombatMode.Arrest; - private bool TargetEliminated => IsEnemyDisabled || (Enemy.IsUnconscious && Enemy.Params.Health.ConstantHealthRegeneration <= 0.0f); + private bool TargetEliminated => IsEnemyDisabled || Enemy.IsUnconscious && Enemy.Params.Health.ConstantHealthRegeneration <= 0.0f || Enemy.IsHuman && Enemy.HasEquippedItem("handlocker") && !character.IsInstigator; private bool IsEnemyDisabled => Enemy == null || Enemy.Removed || Enemy.IsDead; private float AimSpeed => HumanAIController.AimSpeed; @@ -158,7 +158,7 @@ namespace Barotrauma return Priority; } } - float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, HumanAIController.GetDamageDoneByAttacker(Enemy) / 100.0f); + float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, character.GetDamageDoneByAttacker(Enemy) / 100.0f); Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100); return Priority; } @@ -177,11 +177,12 @@ namespace Barotrauma ignoredWeapons.Clear(); ignoreWeaponTimer = ignoredWeaponsClearTime; } - if (findSafety != null) + bool isCurrentObjective = objectiveManager.IsCurrentObjective(); + if (findSafety != null && isCurrentObjective) { findSafety.Priority = 0; } - if (!AllowCoolDown && !character.IsOnPlayerTeam && !objectiveManager.IsCurrentObjective()) + if (!AllowCoolDown && !character.IsOnPlayerTeam && !isCurrentObjective) { distanceTimer -= deltaTime; if (distanceTimer < 0) @@ -204,13 +205,18 @@ namespace Barotrauma protected override void Act(float deltaTime) { + if (IsEnemyDisabled) + { + IsCompleted = true; + return; + } if (AllowCoolDown) { coolDownTimer -= deltaTime; } if (seekAmmunitionObjective == null && seekWeaponObjective == null) { - if (Mode != CombatMode.Retreat && TryArm() && !IsEnemyDisabled) + if (Mode != CombatMode.Retreat && TryArm()) { OperateWeapon(deltaTime); } @@ -283,10 +289,12 @@ namespace Barotrauma } else { + AskHelp(); Retreat(deltaTime); } break; case CombatMode.Retreat: + AskHelp(); Retreat(deltaTime); break; default: @@ -363,6 +371,7 @@ namespace Barotrauma { if (WeaponComponent == null) { + SpeakNoWeapons(); Mode = CombatMode.Retreat; } } @@ -409,6 +418,7 @@ namespace Barotrauma onCompleted: () => RemoveSubObjective(ref seekWeaponObjective), onAbandon: () => { + SpeakNoWeapons(); RemoveSubObjective(ref seekWeaponObjective); Mode = CombatMode.Retreat; }); @@ -680,6 +690,7 @@ namespace Barotrauma } else { + SpeakNoWeapons(); Weapon = null; Mode = CombatMode.Retreat; return false; @@ -892,7 +903,7 @@ namespace Barotrauma TryAddSubObjective(ref seekAmmunitionObjective, constructor: () => new AIObjectiveContainItem(character, ammunitionIdentifiers, Weapon.GetComponent(), objectiveManager) { - targetItemCount = Weapon.GetComponent().Capacity, + ItemCount = Weapon.GetComponent().Capacity, checkInventory = false }, onCompleted: () => RemoveSubObjective(ref seekAmmunitionObjective), @@ -1099,7 +1110,7 @@ namespace Barotrauma if (WeaponComponent is RangedWeapon rangedWeapon) { // If the weapon is just equipped, we can't shoot just yet. - if (rangedWeapon.ReloadTimer <= 0) + if (rangedWeapon.ReloadTimer <= 0 && !rangedWeapon.HoldTrigger) { reloadTime = rangedWeapon.Reload; } @@ -1155,6 +1166,21 @@ namespace Barotrauma retreatTarget = null; } + private void SpeakNoWeapons() => Speak("dialogcombatnoweapons", delay: 0, minDuration: 30); + private void AskHelp() => Speak("dialogcombatretreating", delay: Rand.Range(0, 1), minDuration: 20); + + private void Speak(string textIdentifier, float delay, float minDuration) + { + if (character.IsOnPlayerTeam && !character.IsInFriendlySub) + { + string msg = TextManager.Get(textIdentifier, true); + if (msg != null) + { + character.Speak(msg, identifier: textIdentifier, delay: delay, minDurationBetweenSimilar: minDuration); + } + } + } + //private float CalculateEnemyStrength() //{ // float enemyStrength = 0; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs index 249a7818d..e973fc74d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -11,12 +11,11 @@ namespace Barotrauma public Func GetItemPriority; - public int targetItemCount = 1; public string[] ignoredContainerIdentifiers; public bool checkInventory = true; - //if the item can't be found, spawn it in the character's inventory (used by outpost NPCs) - private bool spawnItemIfNotFound = false; + //if the item can't be found, spawn it in the character's inventory (used by outpost NPCs and in some cases also enemy NPCs, like pirates) + private readonly bool spawnItemIfNotFound; //can either be a tag or an identifier public readonly string[] itemIdentifiers; @@ -35,9 +34,24 @@ namespace Barotrauma public bool Equip { get; set; } public bool RemoveEmpty { get; set; } = true; public bool RemoveExisting { get; set; } + /// + /// Only remove existing items when the contain target can't be put in the inventory + /// + public bool RemoveExistingWhenNecessary { get; set; } + public Func RemoveExistingPredicate { get; set; } + public int? RemoveMax { get; set; } public bool MoveWholeStack { get; set; } + private int _itemCount = 1; + public int ItemCount + { + get { return _itemCount; } + set + { + _itemCount = Math.Max(value, 1); + } + } public AIObjectiveContainItem(Character character, Item item, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) @@ -83,7 +97,7 @@ namespace Barotrauma containedItemCount++; } } - return containedItemCount >= targetItemCount; + return containedItemCount >= ItemCount; } } @@ -106,9 +120,9 @@ namespace Barotrauma } if (character.CanInteractWith(container.Item, checkLinked: false)) { - if (RemoveExisting) + if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(item))) { - HumanAIController.UnequipContainedItems(container.Item); + HumanAIController.UnequipContainedItems(container.Item, predicate: RemoveExistingPredicate, unequipMax: RemoveMax); } else if (RemoveEmpty) { @@ -127,7 +141,6 @@ namespace Barotrauma container.Inventory.TryPutItem(item, null); } } - IsCompleted = true; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs index 559aad746..1c8b16955 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs @@ -36,6 +36,11 @@ namespace Barotrauma /// public bool DropIfFails { get; set; } = true; + public bool RemoveExistingWhenNecessary { get; set; } + public Func RemoveExistingPredicate { get; set; } + public int? RemoveExistingMax { get; set; } + public string AbandonGetItemDialogueIdentifier { get; set; } + public AIObjectiveDecontainItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, ItemContainer sourceContainer = null, ItemContainer targetContainer = null, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { @@ -86,6 +91,7 @@ namespace Barotrauma } if (itemToDecontain.Container != sourceContainer.Item) { + itemToDecontain.Drop(character); IsCompleted = true; return; } @@ -98,7 +104,12 @@ namespace Barotrauma if (getItemObjective == null && !itemToDecontain.IsOwnedBy(character)) { TryAddSubObjective(ref getItemObjective, - constructor: () => new AIObjectiveGetItem(character, targetItem, objectiveManager, Equip) { TakeWholeStack = this.TakeWholeStack }, + constructor: () => new AIObjectiveGetItem(character, targetItem, objectiveManager, Equip) + { + CannotFindDialogueIdentifierOverride = AbandonGetItemDialogueIdentifier, + SpeakIfFails = AbandonGetItemDialogueIdentifier != null, + TakeWholeStack = this.TakeWholeStack + }, onAbandon: () => Abandon = true); return; } @@ -110,6 +121,9 @@ namespace Barotrauma MoveWholeStack = TakeWholeStack, Equip = Equip, RemoveEmpty = false, + RemoveExistingWhenNecessary = RemoveExistingWhenNecessary, + RemoveExistingPredicate = RemoveExistingPredicate, + RemoveMax = RemoveExistingMax, GetItemPriority = GetItemPriority, ignoredContainerIdentifiers = sourceContainer != null ? new string[] { sourceContainer.Item.Prefab.Identifier } : null }, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs index 6e13c261d..5bd0f0ac7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs @@ -31,7 +31,7 @@ namespace Barotrauma protected override AIObjective ObjectiveConstructor(Character target) { - AIObjectiveCombat.CombatMode combatMode = target.IsEscorted && character.TeamID == CharacterTeamType.Team1 ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Offensive; + AIObjectiveCombat.CombatMode combatMode = ShouldArrest(target, character) ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Offensive; var combatObjective = new AIObjectiveCombat(character, target, combatMode, objectiveManager, PriorityModifier); if (character.TeamID == CharacterTeamType.FriendlyNPC && target.TeamID == CharacterTeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign) { @@ -41,7 +41,7 @@ namespace Barotrauma combatObjective.holdFireCondition = () => { //hold fire while the enemy is in the airlock (except if they've attacked us) - if (HumanAIController.GetDamageDoneByAttacker(target) > 0.0f) { return false; } + if (character.GetDamageDoneByAttacker(target) > 0.0f) { return false; } return target.CurrentHull == null || target.CurrentHull.OutpostModuleTags.Any(t => t.Equals("airlock", System.StringComparison.OrdinalIgnoreCase)); }; character.Speak(TextManager.Get("dialogenteroutpostwarning"), null, Rand.Range(0.5f, 1.0f), "leaveoutpostwarning", 30.0f); @@ -65,7 +65,13 @@ namespace Barotrauma if (HumanAIController.IsFriendly(character, target)) { return false; } if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; } if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; } + if (ShouldArrest(target, character) && target.HasEquippedItem("handlocker")) { return false; } return true; } + + public static bool ShouldArrest(Character target, Character character) + { + return target != null && target.IsEscorted && character.TeamID == CharacterTeamType.Team1; + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 7dc9de1d2..3f8ca0520 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -52,9 +52,11 @@ namespace Barotrauma } else { - Priority = (objectiveManager.IsCurrentOrder() || - objectiveManager.IsCurrentOrder() || - objectiveManager.Objectives.Any(o => o.Priority > 0 && o is AIObjectiveCombat)) + Priority = ( + objectiveManager.HasOrder(o => o.Priority > 0) || + objectiveManager.HasOrder(o => o.Priority > 0) || + objectiveManager.HasActiveObjective() || + objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0)) && HumanAIController.HasDivingSuit(character) ? 0 : 100; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs index f42c4a725..6613d85c0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -21,7 +21,7 @@ namespace Barotrauma public float TargetCondition { get; set; } = 1; public bool AllowDangerousPressure { get; set; } - private readonly ImmutableArray identifiersOrTags; + public readonly ImmutableArray IdentifiersOrTags; //if the item can't be found, spawn it in the character's inventory (used by outpost NPCs) private bool spawnItemIfNotFound = false; @@ -58,6 +58,17 @@ namespace Barotrauma public bool EvaluateCombatPriority { get; set; } public bool CheckPathForEachItem { get; set; } public bool SpeakIfFails { get; set; } + public string CannotFindDialogueIdentifierOverride { get; set; } + + private int _itemCount = 1; + public int ItemCount + { + get { return _itemCount; } + set + { + _itemCount = Math.Max(value, 1); + } + } public InvSlotType? EquipSlotType { get; set; } @@ -81,7 +92,7 @@ namespace Barotrauma Equip = equip; this.spawnItemIfNotFound = spawnItemIfNotFound; this.checkInventory = checkInventory; - this.identifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableArray(); + IdentifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableArray(); ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToArray(); } @@ -113,7 +124,7 @@ namespace Barotrauma private bool CheckInventory() { - if (identifiersOrTags == null) { return false; } + if (IdentifiersOrTags == null) { return false; } var item = character.Inventory.FindItem(i => CheckItem(i), recursive: true); if (item != null) { @@ -123,6 +134,19 @@ namespace Barotrauma return item != null; } + private bool CountItems() + { + int itemCount = 0; + foreach (Item it in character.Inventory.AllItems) + { + if (CheckItem(it)) + { + itemCount++; + } + } + return itemCount >= ItemCount; + } + protected override void Act(float deltaTime) { if (character.LockHands) @@ -135,7 +159,7 @@ namespace Barotrauma Abandon = true; return; } - if (identifiersOrTags != null && !isDoneSeeking) + if (IdentifiersOrTags != null && !isDoneSeeking) { if (checkInventory) { @@ -152,7 +176,7 @@ namespace Barotrauma if (dangerousPressure) { #if DEBUG - string itemName = targetItem != null ? targetItem.Name : identifiersOrTags.FirstOrDefault(); + string itemName = targetItem != null ? targetItem.Name : IdentifiersOrTags.FirstOrDefault(); DebugConsole.NewMessage($"{character.Name}: Seeking item ({itemName}) aborted, because the pressure is dangerous.", Color.Yellow); #endif Abandon = true; @@ -245,7 +269,18 @@ namespace Barotrauma } } } - IsCompleted = true; + if (IdentifiersOrTags == null) + { + IsCompleted = true; + } + else + { + IsCompleted = CountItems(); + if (!IsCompleted) + { + ResetInternal(); + } + } } else { @@ -297,7 +332,7 @@ namespace Barotrauma private void FindTargetItem() { - if (identifiersOrTags == null) + if (IdentifiersOrTags == null) { if (targetItem == null) { @@ -316,7 +351,7 @@ namespace Barotrauma // Otherwise it will take some time for us to find a valid item when there are multiple items that we can't reach and some that we can. // This is relatively expensive, so let's do this only when it significantly improves the behavior. // Only allow one path find call per frame. - CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.isFollowOrderObjective); + CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.IsFollowOrderObjective); } bool checkPath = CheckPathForEachItem; bool hasCalledPathFinder = false; @@ -430,10 +465,10 @@ namespace Barotrauma { if (spawnItemIfNotFound) { - if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && identifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab)) + if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && IdentifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab)) { #if DEBUG - DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow); + DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow); #endif Abandon = true; } @@ -452,7 +487,7 @@ namespace Barotrauma else { #if DEBUG - DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow); + DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}", Color.Yellow); #endif Abandon = true; } @@ -468,13 +503,20 @@ namespace Barotrauma // Not yet ready return false; } - if (Equip && EquipSlotType.HasValue) + if (IdentifiersOrTags != null && ItemCount > 1) { - return character.HasEquippedItem(targetItem, EquipSlotType.Value); + return CountItems(); } else { - return character.HasItem(targetItem, Equip); + if (Equip && EquipSlotType.HasValue) + { + return character.HasEquippedItem(targetItem, EquipSlotType.Value); + } + else + { + return character.HasItem(targetItem, Equip); + } } } @@ -487,7 +529,7 @@ namespace Barotrauma if (item.Condition < TargetCondition) { return false; } if (ItemFilter != null && !ItemFilter(item)) { return false; } if (RequireLoaded && item.Components.Any(i => !i.IsLoaded(character))) { return false; } - return identifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id) || (AllowVariants && item.Prefab.VariantOf?.Identifier == id)); + return IdentifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id) || (AllowVariants && item.Prefab.VariantOf?.Identifier == id)); } public override void Reset() @@ -528,7 +570,7 @@ namespace Barotrauma { if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective) { - string msg = TextManager.Get("dialogcannotfinditem", true); + string msg = TextManager.Get(CannotFindDialogueIdentifierOverride, returnNull: true) ?? TextManager.Get("dialogcannotfinditem", returnNull: true); if (msg != null) { character.Speak(msg, identifier: "dialogcannotfinditem", minDurationBetweenSimilar: 20.0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItems.cs index ee84492d9..9cb439547 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItems.cs @@ -11,6 +11,7 @@ namespace Barotrauma public override string Identifier { get; set; } = "get items"; public override string DebugTag => $"{Identifier}"; public override bool KeepDivingGearOn => true; + public override bool AllowMultipleInstances => true; public bool AllowStealing { get; set; } public bool TakeWholeStack { get; set; } @@ -21,6 +22,7 @@ namespace Barotrauma public bool EvaluateCombatPriority { get; set; } public bool CheckPathForEachItem { get; set; } public bool RequireLoaded { get; set; } + public bool RequireAllItems { get; set; } private readonly ImmutableArray gearTags; private readonly string[] ignoredTags; @@ -47,9 +49,11 @@ namespace Barotrauma { foreach (string tag in gearTags) { + if (subObjectives.Any(so => so is AIObjectiveGetItem getItem && getItem.IdentifiersOrTags.Contains(tag))) { continue; } + int count = gearTags.Count(t => t == tag); AIObjectiveGetItem? getItem = null; - TryAddSubObjective(ref getItem, () => - new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory) + TryAddSubObjective(ref getItem, () => + new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory && count <= 1) { AllowVariants = AllowVariants, Wear = Wear, @@ -57,7 +61,9 @@ namespace Barotrauma AllowStealing = AllowStealing, ignoredIdentifiersOrTags = ignoredTags, CheckPathForEachItem = CheckPathForEachItem, - RequireLoaded = RequireLoaded + RequireLoaded = RequireLoaded, + ItemCount = count, + SpeakIfFails = RequireAllItems }, onCompleted: () => { @@ -75,6 +81,10 @@ namespace Barotrauma achievedItems.Remove(item); } RemoveSubObjective(ref getItem); + if (RequireAllItems) + { + Abandon = true; + } }); } subObjectivesCreated = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index 17930516a..72dcbaf98 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -22,16 +22,23 @@ namespace Barotrauma public Func requiredCondition; public Func endNodeFilter; - public Func priorityGetter; + public Func PriorityGetter; + + public bool IsFollowOrderObjective; + public bool Mimic; - public bool isFollowOrderObjective; - public bool mimic; public bool SpeakIfFails { get; set; } = true; public bool DebugLogWhenFails { get; set; } = true; public bool UsePathingOutside { get; set; } = true; - public float extraDistanceWhileSwimming; - public float extraDistanceOutsideSub; + public float ExtraDistanceWhileSwimming; + public float ExtraDistanceOutsideSub; + private float _closeEnoughMultiplier = 1; + public float CloseEnoughMultiplier + { + get { return _closeEnoughMultiplier; } + set { _closeEnoughMultiplier = Math.Max(value, 1); } + } private float _closeEnough = 50; private readonly float minDistance = 50; private readonly float seekGapsInterval = 1; @@ -45,14 +52,15 @@ namespace Barotrauma { get { - float dist = _closeEnough; + float dist = _closeEnough * CloseEnoughMultiplier; + float extraMultiplier = Math.Clamp(CloseEnoughMultiplier * 0.6f, 1, 3); if (character.AnimController.InWater) { - dist += extraDistanceWhileSwimming; + dist += ExtraDistanceWhileSwimming * extraMultiplier; } if (character.CurrentHull == null) { - dist += extraDistanceOutsideSub; + dist += ExtraDistanceOutsideSub * extraMultiplier; } return dist; } @@ -109,9 +117,9 @@ namespace Barotrauma } else { - if (priorityGetter != null) + if (PriorityGetter != null) { - Priority = priorityGetter(); + Priority = PriorityGetter(); } else if (OverridePriority.HasValue) { @@ -177,7 +185,7 @@ namespace Barotrauma Abandon = true; return; } - if (cannotFollow || Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy)) + if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy)) { // Wait character.AIController.SteeringManager.Reset(); @@ -200,7 +208,7 @@ namespace Barotrauma } } Hull targetHull = GetTargetHull(); - if (!isFollowOrderObjective) + if (!IsFollowOrderObjective) { // Abandon if going through unsafe paths. Note ignores unsafe nodes when following an order or when the objective is set to ignore unsafe hulls. bool containsUnsafeNodes = character.IsDismissed && !HumanAIController.ObjectiveManager.CurrentObjective.IgnoreUnsafeHulls @@ -249,7 +257,7 @@ namespace Barotrauma Character followTarget = Target as Character; bool needsDivingSuit = (!isInside || hasOutdoorNodes) && character.NeedsAir && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure); bool needsDivingGear = (needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit)) && character.NeedsAir; - if (mimic) + if (Mimic) { if (HumanAIController.HasDivingSuit(followTarget) && character.NeedsAir) { @@ -277,7 +285,7 @@ namespace Barotrauma if (findDivingGear != null && !findDivingGear.CanBeCompleted) { TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager), - onAbandon: () => Abort(), + onAbandon: () => Abandon = true, onCompleted: () => { cannotFollow = false; @@ -287,7 +295,7 @@ namespace Barotrauma else { TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager), - onAbandon: () => Abort(), + onAbandon: () => Abandon = true, onCompleted: () => { cannotFollow = false; @@ -320,7 +328,7 @@ namespace Barotrauma if (character.AnimController.InWater) { if (character.CurrentHull == null || - isFollowOrderObjective && + IsFollowOrderObjective && targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) && Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance) { @@ -363,7 +371,7 @@ namespace Barotrauma } if (TargetGap != null) { - if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, isFollowOrderObjective ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime)) + if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, IsFollowOrderObjective ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime)) { SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1); return; @@ -382,7 +390,7 @@ namespace Barotrauma Item scooter = null; float closeEnough = 250; float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition); - bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic || + bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!Mimic || (targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2)); if (HumanAIController.HasItem(character, scooterTag, out IEnumerable equippedScooters, recursive: false, requireEquipped: true)) { @@ -391,24 +399,32 @@ namespace Barotrauma } else if (shouldUseScooter) { - bool hasBattery = false; - if (HumanAIController.HasItem(character, scooterTag, out IEnumerable nonEquippedScooters, containedTag: batteryTag, conditionPercentage: 1, requireEquipped: false)) + var leftHandItem = character.GetEquippedItem(slotType: InvSlotType.LeftHand); + var rightHandItem = character.GetEquippedItem(slotType: InvSlotType.RightHand); + bool handsFull = + (leftHandItem != null && character.Inventory.CheckIfAnySlotAvailable(leftHandItem, inWrongSlot: false) == -1) || + (rightHandItem != null && character.Inventory.CheckIfAnySlotAvailable(rightHandItem, inWrongSlot: false) == -1); + if (!handsFull) { - // Non-equipped scooter with a battery - scooter = nonEquippedScooters.FirstOrDefault(); - hasBattery = true; - } - else if (HumanAIController.HasItem(character, scooterTag, out IEnumerable _nonEquippedScooters, requireEquipped: false)) - { - // Non-equipped scooter without a battery - scooter = _nonEquippedScooters.FirstOrDefault(); - // Non-recursive so that the bots won't take batteries from other items. Also means that they can't find batteries inside containers. Not sure how to solve this. - hasBattery = HumanAIController.HasItem(character, batteryTag, out _, requireEquipped: false, conditionPercentage: 1, recursive: false); - } - if (scooter != null && hasBattery) - { - // Equip only if we have a battery available - HumanAIController.TakeItem(scooter, character.Inventory, equip: true, dropOtherIfCannotMove: false, allowSwapping: true, storeUnequipped: false); + bool hasBattery = false; + if (HumanAIController.HasItem(character, scooterTag, out IEnumerable nonEquippedScooters, containedTag: batteryTag, conditionPercentage: 1, requireEquipped: false)) + { + // Non-equipped scooter with a battery + scooter = nonEquippedScooters.FirstOrDefault(); + hasBattery = true; + } + else if (HumanAIController.HasItem(character, scooterTag, out IEnumerable _nonEquippedScooters, requireEquipped: false)) + { + // Non-equipped scooter without a battery + scooter = _nonEquippedScooters.FirstOrDefault(); + // Non-recursive so that the bots won't take batteries from other items. Also means that they can't find batteries inside containers. Not sure how to solve this. + hasBattery = HumanAIController.HasItem(character, batteryTag, out _, requireEquipped: false, conditionPercentage: 1, recursive: false); + } + if (scooter != null && hasBattery) + { + // Equip only if we have a battery available + HumanAIController.TakeItem(scooter, character.Inventory, equip: true, dropOtherIfCannotMove: false, allowSwapping: true, storeUnequipped: false); + } } } bool isScooterEquipped = scooter != null && character.HasEquippedItem(scooter); @@ -597,7 +613,7 @@ namespace Barotrauma { if (gap.Open < 1) { continue; } if (gap.Submarine == null) { continue; } - if (!isFollowOrderObjective) + if (!IsFollowOrderObjective) { if (gap.FlowTargetHull == null) { continue; } if (gap.Submarine != Target.Submarine) { continue; } @@ -678,18 +694,6 @@ namespace Barotrauma return IsCompleted; } - private void Abort() - { - if (!objectiveManager.IsOrder(this)) - { - Abandon = true; - } - else - { - cannotFollow = true; - } - } - protected override void OnAbandon() { StopMovement(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItem.cs new file mode 100644 index 000000000..8a6b8cea7 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItem.cs @@ -0,0 +1,235 @@ +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Barotrauma +{ + class AIObjectiveLoadItem : AIObjective + { + public override string Identifier { get; set; } = "load item"; + public override bool IsLoop + { + get => true; + set => throw new Exception("Trying to set the value for AIObjectiveLoadItem.IsLoop from: " + Environment.StackTrace.CleanupStackTrace()); + } + + private AIObjectiveLoadItems.ItemCondition TargetItemCondition { get; } + private Item Container { get; } + private ItemContainer ItemContainer { get; } + private ImmutableArray TargetContainerTags { get; } + + private int itemIndex = 0; + private AIObjectiveDecontainItem decontainObjective; + private readonly HashSet ignoredItems = new HashSet(); + private Item targetItem; + private readonly string abandonGetItemDialogueIdentifier = "dialogcannotfindloadable"; + + public AIObjectiveLoadItem(Item container, ImmutableArray targetTags, AIObjectiveLoadItems.ItemCondition targetCondition, string option, Character character, AIObjectiveManager objectiveManager, float priorityModifier) + : base(character, objectiveManager, priorityModifier) + { + Container = container; + ItemContainer = container?.GetComponent(); + if (ItemContainer?.Inventory == null) + { + Abandon = true; + return; + } + TargetContainerTags = targetTags; + TargetItemCondition = targetCondition; + if (!string.IsNullOrEmpty(option)) + { + string optionSpecificDialogueIdentifier = $"{abandonGetItemDialogueIdentifier}.{option}"; + if (TextManager.ContainsTag(optionSpecificDialogueIdentifier)) + { + abandonGetItemDialogueIdentifier = optionSpecificDialogueIdentifier; + } + } + } + + protected override float GetPriority() + { + if (!IsAllowed) + { + Priority = 0; + Abandon = true; + return Priority; + } + else if (!AIObjectiveLoadItems.IsValidTarget(Container, character, targetCondition: TargetItemCondition)) + { + // Reduce priority to 0 if the this isn't a valid container right now + Priority = 0; + } + else if (targetItem == null) + { + Priority = 0; + } + else + { + float dist = 0.0f; + if (character.CurrentHull != targetItem.CurrentHull) + { + AddDistance(character.WorldPosition, targetItem.WorldPosition); + } + if (targetItem.CurrentHull != Container.CurrentHull) + { + AddDistance(targetItem.WorldPosition, Container.WorldPosition); + } + void AddDistance(Vector2 startPos, Vector2 targetPos) + { + float yDist = Math.Abs(startPos.Y - targetPos.Y); + // If we're on the same level with the target, we'll disregard the vertical distance + if (yDist > 100) { dist += yDist * 5; } + dist += Math.Abs(character.WorldPosition.X - targetPos.X); + } + float distanceFactor = dist > 0.0f ? MathHelper.Lerp(0.9f, 0, MathUtils.InverseLerp(0, 5000, dist)) : 0.9f; + bool hasContainable = character.HasItem(targetItem); + float devotion = (CumulatedDevotion + (hasContainable ? 100 - MaxDevotion : 0)) / 100; + float max = AIObjectiveManager.LowestOrderPriority - (hasContainable ? 1 : 2); + Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (distanceFactor * PriorityModifier), 0, 1)); + if (decontainObjective != null && targetItem.Container != Container) + { + if (!IsValidContainable(targetItem)) + { + // Target is not valid anymore, abandon the objective + decontainObjective.Abandon = true; + } + else if (!ItemContainer.Inventory.CanBePut(targetItem) && ItemContainer.Inventory.AllItems.None(i => AIObjectiveLoadItems.ItemMatchesTargetCondition(i, TargetItemCondition))) + { + // The container is full and there's no item that should be removed, abandon the objective + decontainObjective.Abandon = true; + } + } + if (ItemContainer.Inventory.IsFull()) + { + // Prioritize containers that still have empty space by lowering the priority of objectives with a full target container + Priority /= 4; + } + } + return Priority; + } + + public override void Update(float deltaTime) + { + base.Update(deltaTime); + if (targetItem == null) + { + if (character.FindItem(ref itemIndex, out Item item, identifiers: ItemContainer.ContainableItemIdentifiers, ignoreBroken: false, customPredicate: IsValidContainable, customPriorityFunction: GetConditionBasedPriority)) + { + if (item == null) + { + // No possible containables found, abandon the objective + Abandon = true; + } + targetItem = item; + } + // Prefer items closer to full condition when target condition is Empty, and vice versa + float GetConditionBasedPriority(Item item) + { + try + { + return TargetItemCondition switch + { + AIObjectiveLoadItems.ItemCondition.Full => MathUtils.InverseLerp(100.0f, 0.0f, item.ConditionPercentage), + AIObjectiveLoadItems.ItemCondition.Empty => MathUtils.InverseLerp(0.0f, 100.0f, item.ConditionPercentage), + _ => throw new NotImplementedException() + }; + } + catch (NotImplementedException) + { +#if DEBUG + DebugConsole.ShowError($"Unexpected target condition \"{TargetItemCondition}\" in local function GetConditionBasedProperty"); +#endif + return 0.0f; + } + } + } + } + + protected override void Act(float deltaTime) + { + if (targetItem != null) + { + if(decontainObjective == null && !IsValidContainable(targetItem)) + { + IgnoreTargetItem(); + Reset(); + return; + } + TryAddSubObjective(ref decontainObjective, + constructor: () => new AIObjectiveDecontainItem(character, targetItem, objectiveManager, targetContainer: ItemContainer, priorityModifier: PriorityModifier) + { + AbandonGetItemDialogueIdentifier = abandonGetItemDialogueIdentifier, + Equip = true, + RemoveExistingWhenNecessary = true, + RemoveExistingPredicate = (i) => AIObjectiveLoadItems.ItemMatchesTargetCondition(i, TargetItemCondition), + RemoveExistingMax = 1 + }, + onCompleted: () => + { + IsCompleted = true; + RemoveSubObjective(ref decontainObjective); + }, + onAbandon: () => + { + // Try again + IgnoreTargetItem(); + Reset(); + }); + } + else + { + objectiveManager.GetObjective().Wander(deltaTime); + } + } + + private bool IsValidContainable(Item item) + { + if (item == null) { return false; } + if (item.Removed) { return false; } + if (ignoredItems.Contains(item)) { return false; } + if ((item.SpawnedInCurrentOutpost && !item.AllowStealing) == character.IsOnPlayerTeam) { return false; } + var rootInventoryOwner = item.GetRootInventoryOwner(); + if (rootInventoryOwner is Character owner && owner != character) { return false; } + if (rootInventoryOwner is Item parentItem) + { + if (parentItem.HasTag("donttakeitems")) { return false; } + if (!(parentItem.GetComponent()?.HasAccess(character) ?? true)) { return false; } + } + if (item.IsThisOrAnyContainerIgnoredByAI(character)) { return false; } + if (!character.HasItem(item) && !CanEquip(item)) { return false; } + if (!ItemContainer.HasAccess(character)) { return false; } + if (!ItemContainer.CanBeContained(item)) { return false; } + if (AIObjectiveLoadItems.ItemMatchesTargetCondition(item, TargetItemCondition)) { return false; } + if (TargetItemCondition == AIObjectiveLoadItems.ItemCondition.Full) + { + // Ignore items that have had their condition increase recently + if (TargetItemCondition == AIObjectiveLoadItems.ItemCondition.Full && item.ConditionIncreasedRecently) { return false; } + // Ignore items inside their (condition-restricted) primary containers + if (item.ParentInventory is ItemInventory itemInventory && item.IsContainerPreferred(itemInventory.Container, out bool _, out bool isSecondary, requireConditionRestriction: true) && !isSecondary) { return false; } + } + // Ignore items inside another valid container + if (AIObjectiveLoadItems.IsValidTarget(item.Container, character, TargetContainerTags)) { return false; } + return true; + } + + protected override bool CheckObjectiveSpecific() => IsCompleted; + + public override void Reset() + { + base.Reset(); + // Don't reset the target item when resetting the objective because it affects priority calculations + decontainObjective = null; + itemIndex = 0; + } + + private void IgnoreTargetItem() + { + if(targetItem == null) { return; } + ignoredItems.Add(targetItem); + targetItem = null; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItems.cs new file mode 100644 index 000000000..5d6eb0753 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItems.cs @@ -0,0 +1,115 @@ +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace Barotrauma +{ + class AIObjectiveLoadItems : AIObjectiveLoop + { + public override string Identifier { get; set; } = "load items"; + protected override float IgnoreListClearInterval => 20.0f; + protected override bool ResetWhenClearingIgnoreList => false; + + private ImmutableArray TargetContainerTags { get; } + private List TargetContainers { get; } = new List(); + private ItemCondition TargetCondition { get; } + + public enum ItemCondition + { + Empty, + Full + } + + public AIObjectiveLoadItems(Character character, AIObjectiveManager objectiveManager, string option, ImmutableArray containerTags, Item targetContainer = null, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier, option) + { + if ((containerTags == null || containerTags.None()) && targetContainer == null) + { + Abandon = true; + return; + } + else + { + TargetContainerTags = containerTags.ToImmutableArray(); + } + if (targetContainer != null) + { + TargetContainers.Add(targetContainer); + } + TargetCondition = option == "turretammo" ? ItemCondition.Empty : ItemCondition.Full; + } + + protected override bool Filter(Item target) + { + if (!IsValidTarget(target, character, TargetContainerTags, TargetCondition)) { return false; } + if (target.CurrentHull == null || target.CurrentHull.FireSources.Count > 0) { return false; } + if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; } + return true; + } + + public static bool IsValidTarget(Item item, Character character, ImmutableArray? targetContainerTags = null, ItemCondition? targetCondition = null) + { + if (item == null) { return false; } + if (item.Removed) { return false; } + if (targetContainerTags.HasValue && !Order.TargetItemsMatchItem(targetContainerTags.Value, item)) { return false; } + if (!(item.GetComponent() is ItemContainer container)) { return false; } + if (container.Inventory == null) { return false; } + if (targetCondition.HasValue && container.Inventory.IsFull() && container.Inventory.AllItems.None(i => ItemMatchesTargetCondition(i, targetCondition.Value))) { return false; } + if (!AIObjectiveCleanupItems.IsItemInsideValidSubmarine(item, character)) { return false; } + if (item.GetRootInventoryOwner() is Character owner && owner != character) { return false; } + if (!item.IsInteractable(character)) { return false; } + if (item.IsThisOrAnyContainerIgnoredByAI(character)) { return false; } + if (!container.HasAccess(character)) { return false; } + // Ignore items that require power but don't have it + if (item.GetComponent() is Powered powered && powered.PowerConsumption > 0 && powered.Voltage < powered.MinVoltage) { return false; } + return true; + } + + public static bool ItemMatchesTargetCondition(Item item, ItemCondition targetCondition) + { + if(item == null) { return false; } + try + { + return targetCondition switch + { + ItemCondition.Empty => item.Condition <= 0.1f, + ItemCondition.Full => item.IsFullCondition, + _ => throw new NotImplementedException(), + }; + } + catch (NotImplementedException) + { +#if DEBUG + DebugConsole.ShowError($"Unexpected target condition \"{targetCondition}\" in AIObjectiveLoadItems.ItemMatchesTargetCondition"); +#endif + return false; + } + } + + protected override IEnumerable GetList() => TargetContainers.Any() ? TargetContainers : Item.ItemList; + + protected override AIObjective ObjectiveConstructor(Item target) + => new AIObjectiveLoadItem(target, TargetContainerTags, TargetCondition, Option, character, objectiveManager, PriorityModifier); + + protected override void OnObjectiveCompleted(AIObjective objective, Item target) + => HumanAIController.RemoveTargets(character, target); + + protected override float TargetEvaluation() + { + if (Targets.None()) { return 0; } + if (objectiveManager.IsOrder(this)) + { + float prio = objectiveManager.GetOrderPriority(this); + if (subObjectives.All(so => so.SubObjectives.None() || so.Priority <= 0)) + { + ForceWalk = true; + } + return prio; + } + return AIObjectiveManager.RunPriority - 0.5f; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs index 338227596..21720599b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs @@ -45,6 +45,7 @@ namespace Barotrauma public override bool AbandonWhenCannotCompleteSubjectives => false; public override bool AllowSubObjectiveSorting => true; public virtual bool InverseTargetEvaluation => false; + protected virtual bool ResetWhenClearingIgnoreList => true; public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace.CleanupStackTrace()); } @@ -55,7 +56,15 @@ namespace Barotrauma { if (ignoreListTimer > IgnoreListClearInterval) { - Reset(); + if (ResetWhenClearingIgnoreList) + { + Reset(); + } + else + { + ignoreList.Clear(); + ignoreListTimer = 0; + } } else { @@ -113,7 +122,7 @@ namespace Barotrauma Priority = 0; return Priority; } - if (character.LockHands || character.Submarine == null) + if (character.LockHands) { Priority = 0; } @@ -131,7 +140,7 @@ namespace Barotrauma // If the priority is higher than the target value, let's just use it. // The priority calculation is more precise, but it takes into account things like distances, // so it's better not to use it if it's lower than the rougher targetValue. - targetValue = Priority; + targetValue = currentSubObjective.Priority; } // If the target value is less than 1% of the max value, let's just treat it as zero. if (targetValue < 1) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 443d8b340..1a24cc332 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs @@ -128,7 +128,7 @@ namespace Barotrauma Item item = null; if (orderPrefab.MustSetTarget) { - item = orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID, interactableFor: character)?.GetRandom(); + item = orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID, interactableFor: character, orderOption: autonomousObjective.option)?.GetRandom(); } var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character); if (order == null) { continue; } @@ -279,6 +279,7 @@ namespace Barotrauma float highestPriority = 0; for (int i = CurrentOrders.Count - 1; i >= 0; i--) { + if (CurrentOrders.Count <= i) { break; } var orderObjective = CurrentOrders[i].Objective; if (orderObjective == null) { continue; } orderObjective.CalculatePriority(); @@ -354,6 +355,7 @@ namespace Barotrauma // Make sure the order priorities reflect those set by the player for (int i = CurrentOrders.Count - 1; i >= 0; i--) { + if (CurrentOrders.Count <= i) { break; } var currentOrder = CurrentOrders[i]; if (currentOrder.Objective == null || currentOrder.MatchesOrder(order, option)) { @@ -406,13 +408,14 @@ namespace Barotrauma if (orderGiver == null) { return null; } newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier) { - CloseEnough = Rand.Range(90, 100) + Rand.Range(50, 70) * Math.Min(HumanAIController.CountCrew(c => c.ObjectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.Target == orderGiver, onlyBots: true), 4), - extraDistanceOutsideSub = 100, - extraDistanceWhileSwimming = 100, + CloseEnough = Rand.Range(80, 100), + CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountCrew(c => c.ObjectiveManager.HasOrder(o => o.Target == orderGiver), onlyBots: true) * Rand.Range(0.8f, 1f), 4), + ExtraDistanceOutsideSub = 100, + ExtraDistanceWhileSwimming = 100, AllowGoingOutside = true, IgnoreIfTargetDead = true, - isFollowOrderObjective = true, - mimic = true, + IsFollowOrderObjective = true, + Mimic = character.IsOnPlayerTeam, DialogueIdentifier = "dialogcannotreachplace" }; break; @@ -523,7 +526,7 @@ namespace Barotrauma newObjective = new AIObjectiveEscapeHandcuffs(character, this, priorityModifier: priorityModifier); break; case "prepareforexpedition": - newObjective = new AIObjectivePrepare(character, this, order.TargetItems) + newObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(option), order.RequireItems) { KeepActiveWhenReady = true, CheckInventory = true, @@ -532,16 +535,29 @@ namespace Barotrauma }; break; case "findweapon": - newObjective = new AIObjectivePrepare(character, this, order.TargetItems) + AIObjectivePrepare prepareObjective; + if (order.TargetEntity is Item tItem) { - KeepActiveWhenReady = false, - CheckInventory = false, - Equip = true, - EvaluateCombatPriority = true, - FindAllItems = false - }; + prepareObjective = new AIObjectivePrepare(character, this, targetItem: tItem); + } + else + { + prepareObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(option), order.RequireItems) + { + KeepActiveWhenReady = false, + CheckInventory = false, + EvaluateCombatPriority = true, + FindAllItems = false + }; + } + prepareObjective.KeepActiveWhenReady = false; + prepareObjective.Equip = true; + newObjective = prepareObjective; newObjective.Completed += () => DismissSelf(order, option); break; + case "loaditems": + newObjective = new AIObjectiveLoadItems(character, this, option, order.GetTargetItems(option), order.TargetEntity as Item, priorityModifier); + break; default: if (order.TargetItemComponent == null) { return null; } if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; } @@ -573,17 +589,20 @@ namespace Barotrauma #endif return; } + Order dismissOrder = Order.GetPrefab("dismissed"); + var orderOption = Order.GetDismissOrderOption(currentOrder); + int priority = currentOrder.ManualPriority; #if CLIENT if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.IsSinglePlayer) { - GameMain.GameSession?.CrewManager?.SetCharacterOrder(character, Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character); + GameMain.GameSession.CrewManager.SetCharacterOrder(character, dismissOrder, orderOption, priority, character); } #else - GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, currentOrder.Order?.TargetSpatialEntity, character, character)); + GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(dismissOrder, orderOption, priority, currentOrder.Order.TargetSpatialEntity, character, character)); + SetOrder(dismissOrder, orderOption, priority, character, speak: false); #endif } - private bool IsAllowedToWait() { if (!character.IsOnPlayerTeam) { return false; } @@ -632,10 +651,9 @@ namespace Barotrauma return ForcedOrder != null || CurrentOrders.Any(); } - public bool HasOrder() where T : AIObjective - { - return ForcedOrder is T || CurrentOrders.Any(o => o.Objective is T); - } + public bool HasOrder(Func predicate = null) where T : AIObjective => + ForcedOrder is T forcedOrder && (predicate == null || predicate(forcedOrder)) || + CurrentOrders.Any(o => o.Objective is T order && (predicate == null || predicate(order))); public float GetOrderPriority(AIObjective objective) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 82d572d2a..5c7055788 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -202,7 +202,7 @@ namespace Barotrauma HumanAIController.FaceTarget(target.Item); if (character.SelectedConstruction != target.Item) { - target.Item.TryInteract(character, false, true); + target.Item.TryInteract(character, forceSelectKey: true); } if (component.AIOperate(deltaTime, character, this)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePrepare.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePrepare.cs index a7edab712..c4b2e1d1c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePrepare.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePrepare.cs @@ -1,5 +1,5 @@ -#nullable enable -using Barotrauma.Extensions; +using Barotrauma.Extensions; +using System; using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; @@ -11,11 +11,15 @@ namespace Barotrauma public override string Identifier { get; set; } = "prepare"; public override string DebugTag => $"{Identifier}"; public override bool KeepDivingGearOn => true; + public override bool PrioritizeIfSubObjectivesActive => true; - private AIObjectiveGetItem? getSingleItemObjective; - private AIObjectiveGetItems? getMultipleItemsObjective; + private AIObjectiveGetItem getSingleItemObjective; + private AIObjectiveGetItems getAllItemsObjective; + private AIObjectiveGetItems getMultipleItemsObjective; private bool subObjectivesCreated; - private readonly ImmutableArray gearTags; + private readonly Item targetItem; + private readonly ImmutableArray requiredItems; + private readonly ImmutableArray optionalItems; private readonly HashSet items = new HashSet(); public bool KeepActiveWhenReady { get; set; } public bool CheckInventory { get; set; } @@ -23,12 +27,29 @@ namespace Barotrauma public bool Equip { get; set; } public bool EvaluateCombatPriority { get; set; } - private AIObjective? GetSubObjective() => getSingleItemObjective ?? getMultipleItemsObjective as AIObjective; - - public AIObjectivePrepare(Character character, AIObjectiveManager objectiveManager, IEnumerable items, float priorityModifier = 1) + private AIObjective GetSubObjective() + { + if (getSingleItemObjective != null) { return getSingleItemObjective; } + if (getAllItemsObjective == null || getAllItemsObjective.IsCompleted) + { + return getMultipleItemsObjective; + } + return getAllItemsObjective; + } + public AIObjectivePrepare(Character character, AIObjectiveManager objectiveManager, Item targetItem, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { - gearTags = items.ToImmutableArray(); + this.targetItem = targetItem; + } + + public AIObjectivePrepare(Character character, AIObjectiveManager objectiveManager, IEnumerable optionalItems, IEnumerable requiredItems = null, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier) + { + this.optionalItems = optionalItems.ToImmutableArray(); + if (requiredItems != null) + { + this.requiredItems = requiredItems.ToImmutableArray(); + } } protected override bool CheckObjectiveSpecific() => IsCompleted; @@ -69,48 +90,76 @@ namespace Barotrauma } if (!subObjectivesCreated) { - if (FindAllItems) + if (FindAllItems && targetItem == null) { - if (!TryAddSubObjective(ref getMultipleItemsObjective, () => new AIObjectiveGetItems(character, objectiveManager, gearTags) + getMultipleItemsObjective = CreateObjectives(optionalItems, requireAll: false); + if (requiredItems != null && requiredItems.Any()) { - CheckInventory = CheckInventory, - Equip = Equip, - EvaluateCombatPriority = EvaluateCombatPriority, - RequireLoaded = true - }, - onCompleted: () => + getAllItemsObjective = CreateObjectives(requiredItems, requireAll: true); + } + AIObjectiveGetItems CreateObjectives(IEnumerable itemTags, bool requireAll) { - if (KeepActiveWhenReady) + AIObjectiveGetItems objectiveReference = null; + if (!TryAddSubObjective(ref objectiveReference, () => new AIObjectiveGetItems(character, objectiveManager, itemTags) { - if (getMultipleItemsObjective != null) + CheckInventory = CheckInventory, + Equip = Equip, + EvaluateCombatPriority = EvaluateCombatPriority, + RequireLoaded = true, + RequireAllItems = requireAll + }, + onCompleted: () => + { + if (KeepActiveWhenReady) { - foreach (var item in getMultipleItemsObjective.achievedItems) + if (objectiveReference != null) { - if (item?.IsOwnedBy(character) != null) + foreach (var item in objectiveReference.achievedItems) { - items.Add(item); + if (item?.IsOwnedBy(character) != null) + { + items.Add(item); + } } } } - } - else + else + { + IsCompleted = true; + } + }, + onAbandon: () => Abandon = true)) { - IsCompleted = true; + Abandon = true; } - }, - onAbandon: () => Abandon = true)) - { - Abandon = true; + return objectiveReference; } } else { - if (!TryAddSubObjective(ref getSingleItemObjective, () => new AIObjectiveGetItem(character, gearTags, objectiveManager, equip: Equip, checkInventory: CheckInventory) + Func getItemConstructor; + if (targetItem != null) { - EvaluateCombatPriority = EvaluateCombatPriority, - SpeakIfFails = true, - RequireLoaded = true - }, + getItemConstructor = () => new AIObjectiveGetItem(character, targetItem, objectiveManager, equip: Equip) + { + SpeakIfFails = true + }; + } + else + { + IEnumerable allItems = optionalItems; + if (requiredItems != null && requiredItems.Any()) + { + allItems = requiredItems; + } + getItemConstructor = () => new AIObjectiveGetItem(character, allItems, objectiveManager, equip: Equip, checkInventory: CheckInventory) + { + EvaluateCombatPriority = EvaluateCombatPriority, + SpeakIfFails = true, + RequireLoaded = true + }; + } + if (!TryAddSubObjective(ref getSingleItemObjective, getItemConstructor, onCompleted: () => { if (KeepActiveWhenReady) @@ -143,8 +192,9 @@ namespace Barotrauma base.Reset(); items.Clear(); subObjectivesCreated = false; - RemoveSubObjective(ref getMultipleItemsObjective); - RemoveSubObjective(ref getSingleItemObjective); + getMultipleItemsObjective = null; + getSingleItemObjective = null; + getAllItemsObjective = null; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs index cd6b24016..eb499efbd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs @@ -20,6 +20,9 @@ namespace Barotrauma private float previousCondition = -1; private RepairTool repairTool; + private const float WaitTimeBeforeRepair = 0.5f; + private float waitTimer; + private bool IsRepairing() => IsRepairing(character, Item); private readonly bool isPriority; @@ -160,6 +163,9 @@ namespace Barotrauma } if (!character.IsClimbing && character.CanInteractWith(Item, out _, checkLinked: false)) { + waitTimer += deltaTime; + if (waitTimer < WaitTimeBeforeRepair) { return; } + HumanAIController.FaceTarget(Item); if (repairTool != null) { @@ -177,7 +183,7 @@ namespace Barotrauma if (character.SelectedConstruction != Item) { if (!Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) && - !Item.TryInteract(character, ignoreRequiredItems: true, forceActionKey: true)) + !Item.TryInteract(character, ignoreRequiredItems: true, forceUseKey: true)) { Abandon = true; } @@ -209,6 +215,7 @@ namespace Barotrauma } else { + waitTimer = 0.0f; RemoveSubObjective(ref refuelObjective); // If cannot reach the item, approach it. TryAddSubObjective(ref goToObjective, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs index 0da0eecd3..1c7786d6b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -36,9 +36,9 @@ namespace Barotrauma { if (targetCharacter == null) { - string errorMsg = $"{character.Name}: Attempted to create a Rescue objective with no target!\n" + Environment.StackTrace.CleanupStackTrace(); - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("AIObjectiveRescue:ctor:targetnull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + string errorMsg = $"Attempted to create a Rescue objective with no target!\n" + Environment.StackTrace.CleanupStackTrace(); + DebugConsole.ThrowError(character.Name + ": " + errorMsg); + GameAnalyticsManager.AddErrorEventOnce("AIObjectiveRescue:ctor:targetnull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); Abandon = true; return; } @@ -59,7 +59,7 @@ namespace Barotrauma protected override void Act(float deltaTime) { - if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead) + if (character.LockHands || targetCharacter == null || targetCharacter.Removed || targetCharacter.IsDead) { Abandon = true; return; @@ -137,66 +137,69 @@ namespace Barotrauma recursive: true); } } - if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD) + if (character.Submarine != null) { - // Incapacitated target is not in a safe place -> Move to a safe place first - if (character.SelectedCharacter != targetCharacter) + if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD) { - if (targetCharacter.CurrentHull != null && HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null) + // Incapacitated target is not in a safe place -> Move to a safe place first + if (character.SelectedCharacter != targetCharacter) { - character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget", new string[2] { "[targetname]", "[roomname]" }, - new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }), - null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f); - } - // Go to the target and select it - if (!character.CanInteractWith(targetCharacter)) - { - RemoveSubObjective(ref replaceOxygenObjective); - RemoveSubObjective(ref goToObjective); - TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager) + if (targetCharacter.CurrentHull != null && HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null) { - CloseEnough = CloseEnoughToTreat, - DialogueIdentifier = "dialogcannotreachpatient", - TargetName = targetCharacter.DisplayName - }, - onCompleted: () => RemoveSubObjective(ref goToObjective), - onAbandon: () => + character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget", new string[2] { "[targetname]", "[roomname]" }, + new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }), + null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f); + } + // Go to the target and select it + if (!character.CanInteractWith(targetCharacter)) { + RemoveSubObjective(ref replaceOxygenObjective); RemoveSubObjective(ref goToObjective); - Abandon = true; - }); - } - else - { - character.SelectCharacter(targetCharacter); - } - } - else - { - // Drag the character into safety - if (safeHull == null) - { - if (findHullTimer > 0) - { - findHullTimer -= deltaTime; - } - else - { - safeHull = objectiveManager.GetObjective().FindBestHull(HumanAIController.VisibleHulls); - findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f); - } - } - if (safeHull != null && character.CurrentHull != safeHull) - { - RemoveSubObjective(ref replaceOxygenObjective); - RemoveSubObjective(ref goToObjective); - TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager), + TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager) + { + CloseEnough = CloseEnoughToTreat, + DialogueIdentifier = "dialogcannotreachpatient", + TargetName = targetCharacter.DisplayName + }, onCompleted: () => RemoveSubObjective(ref goToObjective), onAbandon: () => { RemoveSubObjective(ref goToObjective); - safeHull = character.CurrentHull; + Abandon = true; }); + } + else + { + character.SelectCharacter(targetCharacter); + } + } + else + { + // Drag the character into safety + if (safeHull == null) + { + if (findHullTimer > 0) + { + findHullTimer -= deltaTime; + } + else + { + safeHull = objectiveManager.GetObjective().FindBestHull(HumanAIController.VisibleHulls); + findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f); + } + } + if (safeHull != null && character.CurrentHull != safeHull) + { + RemoveSubObjective(ref replaceOxygenObjective); + RemoveSubObjective(ref goToObjective); + TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager), + onCompleted: () => RemoveSubObjective(ref goToObjective), + onAbandon: () => + { + RemoveSubObjective(ref goToObjective); + safeHull = character.CurrentHull; + }); + } } } } @@ -228,7 +231,7 @@ namespace Barotrauma // We can start applying treatment if (character != targetCharacter && character.SelectedCharacter != targetCharacter) { - if (targetCharacter.CurrentHull.DisplayName != null) + if (targetCharacter.CurrentHull?.DisplayName != null) { character.Speak(TextManager.GetWithVariables("DialogFoundWoundedTarget", new string[2] { "[targetname]", "[roomname]" }, new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }), @@ -252,7 +255,7 @@ namespace Barotrauma return; } - SteeringManager?.Reset(); + SteeringManager.Reset(); if (!targetCharacter.IsPlayer) { @@ -269,26 +272,36 @@ namespace Barotrauma float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f; //find which treatments are the most suitable to treat the character's current condition - targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false); + targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false, predictFutureDuration: 10.0f); //check if we already have a suitable treatment for any of the afflictions foreach (Affliction affliction in GetSortedAfflictions(targetCharacter)) { if (affliction == null) { throw new Exception("Affliction was null"); } if (affliction.Prefab == null) { throw new Exception("Affliction prefab was null"); } + float bestSuitability = 0.0f; + Item bestItem = null; foreach (KeyValuePair treatmentSuitability in affliction.Prefab.TreatmentSuitability) { - if (currentTreatmentSuitabilities.ContainsKey(treatmentSuitability.Key) && currentTreatmentSuitabilities[treatmentSuitability.Key] > 0.0f) + if (currentTreatmentSuitabilities.ContainsKey(treatmentSuitability.Key) && + currentTreatmentSuitabilities[treatmentSuitability.Key] > bestSuitability) { Item matchingItem = character.Inventory.FindItemByIdentifier(treatmentSuitability.Key, true); - if (matchingItem == null) { continue; } - if (targetCharacter != character) { character.SelectCharacter(targetCharacter); } - ApplyTreatment(affliction, matchingItem); - //wait a bit longer after applying a treatment to wait for potential side-effects to manifest - treatmentTimer = TreatmentDelay * 4; - return; + if (matchingItem != null) + { + bestItem = matchingItem; + bestSuitability = currentTreatmentSuitabilities[treatmentSuitability.Key]; + } } } + if (bestItem != null) + { + if (targetCharacter != character) { character.SelectCharacter(targetCharacter); } + ApplyTreatment(affliction, bestItem); + //wait a bit longer after applying a treatment to wait for potential side-effects to manifest + treatmentTimer = TreatmentDelay * 4; + return; + } } // Find treatments outside of own inventory only if inside the own sub. if (character.Submarine != null && character.Submarine.TeamID == character.TeamID) @@ -418,21 +431,7 @@ namespace Barotrauma protected override bool CheckObjectiveSpecific() { - if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead) - { - Abandon = true; - return false; - } - // Don't go into rooms that have enemies - if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) - { - Abandon = true; - return false; - } - bool isCompleted = - AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) || - targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Prefab.IsBuff || a.Strength <= a.Prefab.TreatmentThreshold); - + bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter); if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam) { character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name), @@ -449,16 +448,36 @@ namespace Barotrauma Abandon = true; return Priority; } - if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead) + if (character.CurrentHull == null) + { + if (!objectiveManager.HasOrder()) + { + Priority = 0; + Abandon = true; + return Priority; + } + } + else if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) + { + // Don't go into rooms that have enemies + Priority = 0; + Abandon = true; + return Priority; + } + if (targetCharacter == null) { Priority = 0; Abandon = true; } else { - // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) - float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f; - float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist)); + float horizontalDistance = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X); + float verticalDistance = Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y); + if (character.Submarine?.Info is { IsRuin: false }) + { + verticalDistance *= 2; + } + float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, horizontalDistance + verticalDistance)); if (targetCharacter.CurrentHull == character.CurrentHull) { distanceFactor = 1; @@ -472,6 +491,16 @@ namespace Barotrauma public static IEnumerable GetSortedAfflictions(Character character) => CharacterHealth.SortAfflictionsBySeverity(character.CharacterHealth.GetAllAfflictions()); + public static IEnumerable GetTreatableAfflictions(Character character) + { + foreach (Affliction affliction in character.CharacterHealth.GetAllAfflictions()) + { + if (affliction.Prefab.IsBuff || affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; } + if (!affliction.Prefab.TreatmentSuitability.Any(kvp => kvp.Value > 0)) { continue; } + yield return affliction; + } + } + public override void Reset() { base.Reset(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs index d27ef7b37..ed1684d2c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs @@ -64,8 +64,15 @@ namespace Barotrauma public static float GetVitalityFactor(Character character) { - float vitality = character.HealthPercentage - (character.Bleeding * 2) - character.Bloodloss + Math.Min(character.Oxygen, 0); + float vitality = 100; + vitality -= character.Bleeding * 2; + vitality += Math.Min(character.Oxygen, 0); vitality -= character.CharacterHealth.GetAfflictionStrength("paralysis"); + foreach (Affliction affliction in AIObjectiveRescue.GetTreatableAfflictions(character)) + { + float strength = character.CharacterHealth.GetPredictedStrength(affliction, predictFutureDuration: 10.0f); + vitality -= affliction.GetVitalityDecrease(character.CharacterHealth, strength) / character.MaxVitality * 100; + } return Math.Clamp(vitality, 0, 100); } @@ -83,8 +90,7 @@ namespace Barotrauma if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; } if (character.AIController is HumanAIController humanAI) { - if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target) || - target.CharacterHealth.GetAllAfflictions().All(a => a.Prefab.IsBuff || a.Strength <= a.Prefab.TreatmentThreshold)) + if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; } @@ -106,10 +112,13 @@ namespace Barotrauma { if (GetVitalityFactor(target) >= vitalityThreshold) { return false; } } - if (target.Submarine == null || character.Submarine == null) { return false; } - // Don't allow going into another sub, unless it's connected and of the same team and type. - if (!character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, includingConnectedSubs: true)) { return false; } - if (target != character &&!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI) + if (target.Submarine != character.Submarine) { return false; } + if (character.Submarine != null) + { + // Don't allow going into another sub, unless it's connected and of the same team and type. + if (!character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, includingConnectedSubs: true)) { return false; } + } + if (target != character && target.IsBot && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI) { // Ignore all concious targets that are currently fighting, fleeing, fixing, or treating characters if (targetAI.ObjectiveManager.HasActiveObjective() || @@ -120,9 +129,12 @@ namespace Barotrauma return false; } } - // Don't go into rooms that have enemies - if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) { return false; } - return true; + if (target.CurrentHull != null) + { + // Don't go into rooms that have enemies + if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) { return false; } + } + return character.GetDamageDoneByAttacker(target) <= 0; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs index 694b78aa2..a460c39c0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Xml.Linq; using System.Linq; +using System.Collections.Immutable; namespace Barotrauma { @@ -19,11 +20,11 @@ namespace Barotrauma struct OrderInfo { - public Order Order { get; } - public string OrderOption { get; } - public int ManualPriority { get; } - public OrderType Type { get; } - public AIObjective Objective { get; } + public readonly Order Order; + public readonly string OrderOption; + public readonly int ManualPriority; + public readonly OrderType Type; + public readonly AIObjective Objective; public bool IsCurrentOrder => Type == OrderType.Current; public enum OrderType @@ -41,6 +42,8 @@ namespace Barotrauma Objective = objective; } + public OrderInfo(Order order, string orderOption) : this(order, orderOption, CharacterInfo.HighestManualOrderPriority, null) { } + public OrderInfo(Order order, string orderOption, int manualPriority) : this(order, orderOption, manualPriority, OrderType.Current, null) { } public OrderInfo(Order order, string orderOption, int manualPriority, AIObjective objective) : this(order, orderOption, manualPriority, OrderType.Current, objective) { } @@ -103,7 +106,10 @@ namespace Barotrauma public readonly Type ItemComponentType; public readonly bool CanTypeBeSubclass; - public readonly string[] TargetItems; + public readonly ImmutableArray TargetItems; + public readonly ImmutableArray RequireItems; + private readonly Dictionary> OptionTargetItems; + public bool HasOptionSpecificTargetItems => OptionTargetItems != null && OptionTargetItems.Any(); public readonly string Identifier; @@ -209,6 +215,12 @@ namespace Barotrauma /// public bool DrawIconWhenContained { get; } + /// + /// Affects how high on the order list the order will be placed (i.e. the manual priority order when it's given) when it's first given. + /// Manually rearranging orders will override this priority. + /// + public int AssignmentPriority { get; } + public static void Init() { Prefabs = new Dictionary(); @@ -306,8 +318,6 @@ namespace Barotrauma } } CanTypeBeSubclass = orderElement.GetAttributeBool("cantypebesubclass", false); - - TargetItems = orderElement.GetAttributeStringArray("targetitems", new string[0], trim: true, convertToLowerInvariant: true); color = orderElement.GetAttributeColor("color"); FadeOutTime = orderElement.GetAttributeFloat("fadeouttime", 0.0f); UseController = orderElement.GetAttributeBool("usecontroller", false); @@ -317,6 +327,36 @@ namespace Barotrauma Options = orderElement.GetAttributeStringArray("options", new string[0]); HiddenOptions = orderElement.GetAttributeStringArray("hiddenoptions", new string[0]); AllOptions = Options.Concat(HiddenOptions).ToArray(); + + OptionTargetItems = new Dictionary>(); + if (orderElement.GetAttributeString("targetitems", "") is string targetItems && targetItems.Contains(';')) + { + string[] splitTargetItems = targetItems.Split(';'); +#if DEBUG + if (splitTargetItems.Length != AllOptions.Length) + { + DebugConsole.ThrowError($"Order \"{Identifier}\" has option-specific target items, but the option count doesn't match the target item count"); + } +#endif + var allTargetItems = new List(); + for (int i = 0; i < AllOptions.Length; i++) + { + string[] optionTargetItems = i < splitTargetItems.Length ? splitTargetItems[i].Split(',', ',') : new string[0]; + for (int j = 0; j < optionTargetItems.Length; j++) + { + optionTargetItems[j] = optionTargetItems[j].ToLowerInvariant().Trim(); + allTargetItems.Add(optionTargetItems[j]); + } + OptionTargetItems.Add(AllOptions[i], optionTargetItems.ToImmutableArray()); + } + TargetItems = allTargetItems.ToImmutableArray(); + } + else + { + TargetItems = orderElement.GetAttributeStringArray("targetitems", new string[0], trim: true, convertToLowerInvariant: true).ToImmutableArray(); + } + RequireItems = orderElement.GetAttributeStringArray("requireitems", new string[0], trim: true, convertToLowerInvariant: true).ToImmutableArray(); + var category = orderElement.GetAttributeString("category", null); if (!string.IsNullOrWhiteSpace(category)) { this.Category = (OrderCategory)Enum.Parse(typeof(OrderCategory), category, true); } MustSetTarget = orderElement.GetAttributeBool("mustsettarget", false); @@ -365,6 +405,7 @@ namespace Barotrauma IsIgnoreOrder = Identifier == "ignorethis" || Identifier == "unignorethis"; DrawIconWhenContained = orderElement.GetAttributeBool("displayiconwhencontained", false); AutoDismiss = orderElement.GetAttributeBool("autodismiss", Category == OrderCategory.Movement); + AssignmentPriority = Math.Clamp(orderElement.GetAttributeInt("assignmentpriority", 100), 0, 100); } /// @@ -380,6 +421,8 @@ namespace Barotrauma ItemComponentType = prefab.ItemComponentType; CanTypeBeSubclass = prefab.CanTypeBeSubclass; TargetItems = prefab.TargetItems; + OptionTargetItems = prefab.OptionTargetItems; + RequireItems = prefab.RequireItems; Options = prefab.Options; SymbolSprite = prefab.SymbolSprite; Color = prefab.Color; @@ -397,6 +440,7 @@ namespace Barotrauma DrawIconWhenContained = prefab.DrawIconWhenContained; Hidden = prefab.Hidden; IgnoreAtOutpost = prefab.IgnoreAtOutpost; + AssignmentPriority = prefab.AssignmentPriority; OrderGiver = orderGiver; TargetEntity = targetEntity; @@ -512,16 +556,17 @@ namespace Barotrauma } /// Only returns items which are interactable for this character - public List GetMatchingItems(Submarine submarine, bool mustBelongToPlayerSub, CharacterTeamType? requiredTeam = null, Character interactableFor = null) + public List GetMatchingItems(Submarine submarine, bool mustBelongToPlayerSub, CharacterTeamType? requiredTeam = null, Character interactableFor = null, string orderOption = null) { List matchingItems = new List(); if (submarine == null) { return matchingItems; } - if (ItemComponentType != null || TargetItems.Length > 0) + if (ItemComponentType != null || TargetItems.Any() || RequireItems.Any()) { foreach (var item in Item.ItemList) { - if (TargetItems.Length > 0 && !TargetItems.Contains(item.Prefab.Identifier) && !item.HasTag(TargetItems)) { continue; } - if (TargetItems.Length == 0 && !TryGetTargetItemComponent(item, out _)) { continue; } + if (RequireItems.Any() && !TargetItemsMatchItem(RequireItems, item)) { continue; } + if (TargetItems.Any() && !TargetItemsMatchItem(item, orderOption)) { continue; } + if (RequireItems.None() && TargetItems.None() && !TryGetTargetItemComponent(item, out _)) { continue; } if (mustBelongToPlayerSub && item.Submarine?.Info != null && item.Submarine.Info.Type != SubmarineType.Player) { continue; } if (item.Submarine != submarine && !submarine.DockedTo.Contains(item.Submarine)) { continue; } if (requiredTeam.HasValue && (item.Submarine == null || item.Submarine.TeamID != requiredTeam.Value)) { continue; } @@ -536,14 +581,13 @@ namespace Barotrauma return matchingItems; } - /// Only returns items which are interactable for this character - public List GetMatchingItems(bool mustBelongToPlayerSub, Character interactableFor = null) + public List GetMatchingItems(bool mustBelongToPlayerSub, Character interactableFor = null, string orderOption = null) { Submarine submarine = Character.Controlled != null && Character.Controlled.TeamID == CharacterTeamType.Team2 && Submarine.MainSubs.Length > 1 ? Submarine.MainSubs[1] : Submarine.MainSub; - return GetMatchingItems(submarine, mustBelongToPlayerSub, interactableFor: interactableFor); + return GetMatchingItems(submarine, mustBelongToPlayerSub, interactableFor: interactableFor, orderOption: orderOption); } public string GetOptionName(string id) @@ -582,5 +626,34 @@ namespace Barotrauma } return ""; } + + public override string ToString() + { + return $"Order ({Name})"; + } + + public ImmutableArray GetTargetItems(string option = null) + { + if (string.IsNullOrEmpty(option) || !OptionTargetItems.TryGetValue(option, out ImmutableArray optionTargetItems)) + { + return TargetItems; + } + else + { + return optionTargetItems; + } + } + + public bool TargetItemsMatchItem(Item item, string option = null) + { + if (item == null) { return false; } + ImmutableArray targetItems = GetTargetItems(option); + return TargetItemsMatchItem(targetItems, item); + } + + public static bool TargetItemsMatchItem(ImmutableArray targetItems, Item item) + { + return item != null && targetItems != null && targetItems.Length > 0 && (targetItems.Contains(item.Prefab.Identifier) || item.HasTag(targetItems)); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index e46839e24..540e4135d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -84,18 +84,17 @@ namespace Barotrauma public bool IsBlocked() { if (blocked.HasValue) { return blocked.Value; } - blocked = false; - if (Waypoint.Submarine != null) { return blocked.Value; } if (Waypoint.Tunnel?.Type != Level.TunnelType.Cave) { return blocked.Value; } foreach (var w in Level.Loaded.ExtraWalls) { - if (!(w is DestructibleLevelWall d)) { return blocked.Value; } - if (d.Destroyed) { return blocked.Value; } - if (!d.IsPointInside(Waypoint.Position)) { return blocked.Value; } - blocked = true; - break; + if (!w.IsPointInside(Waypoint.Position)) { continue; } + if (w is DestructibleLevelWall d) + { + blocked = !d.Destroyed; + } + if (blocked.Value) { break; } } return blocked.Value; } @@ -125,6 +124,7 @@ namespace Barotrauma { wp.OnLinksChanged += WaypointLinksChanged; } + sortedNodes = new List(nodes.Count); this.isCharacter = isCharacter; } @@ -166,7 +166,7 @@ namespace Barotrauma } } - private readonly List sortedNodes = new List(); + private readonly List sortedNodes; public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, float minGapSize = 0, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisibility = true) { @@ -175,8 +175,7 @@ namespace Barotrauma node.ResetBlocked(); } - //sort nodes roughly according to distance - sortedNodes.Clear(); + // First calculate the temp positions for all nodes. foreach (PathNode node in nodes) { node.TempPosition = node.Position; @@ -194,8 +193,15 @@ namespace Barotrauma else if (hostSub == null && wpSub != null) { // Outside and targeting inside - node.TempPosition += wpSub.SimPosition; + node.TempPosition += wpSub.SimPosition; } + } + + //sort nodes roughly according to distance + sortedNodes.Clear(); + PathNode startNode = null; + foreach (PathNode node in nodes) + { float xDiff = Math.Abs(start.X - node.TempPosition.X); float yDiff = Math.Abs(start.Y - node.TempPosition.Y); if (InsideSubmarine && !(node.Waypoint.Submarine?.Info?.IsRuin ?? false)) @@ -215,13 +221,20 @@ namespace Barotrauma //much higher cost to waypoints that are outside if (node.Waypoint.CurrentHull == null && ApplyPenaltyToOutsideNodes) { node.TempDistance *= 10.0f; } - //optimization: - //node extremely far, don't try to use it as a start node - if (node.TempDistance > 800.0f) + //optimization: node extremely far, don't try to use it as a start node + if (node.TempDistance > (InsideSubmarine ? 100.0f : 800.0f)) { continue; } - + //optimization: node extremely close (< 1 m). If it's valid, choose it as the start node and skip the more exhaustive search for the closest one + if (node.TempDistance < 1.0f) + { + if (IsValidStartNode(node)) + { + startNode = node; + break; + } + } //prefer nodes that are closer to the end position node.TempDistance += (Math.Abs(end.X - node.TempPosition.X) + Math.Abs(end.Y - node.TempPosition.Y)) / 100.0f; @@ -233,6 +246,88 @@ namespace Barotrauma sortedNodes.Insert(i, node); } + //find the most suitable start node, starting from the ones that are the closest + if (startNode == null) + { + foreach (PathNode node in sortedNodes) + { + if (IsValidStartNode(node)) + { + startNode = node; + break; + } + } + } + + if (startNode == null) + { +#if DEBUG + DebugConsole.NewMessage("Pathfinding error, couldn't find a start node. "+ errorMsgStr, Color.DarkRed); +#endif + return new SteeringPath(true); + } + + //sort nodes again, now based on distance from the end position + sortedNodes.Clear(); + PathNode endNode = null; + foreach (PathNode node in nodes) + { + node.TempDistance = Vector2.DistanceSquared(end, node.TempPosition); + if (InsideSubmarine) + { + if (ApplyPenaltyToOutsideNodes) + { + //much higher cost to waypoints that are outside + if (node.Waypoint.CurrentHull == null) { node.TempDistance *= 10.0f; } + } + //avoid stopping at a doorway + if (node.Waypoint.ConnectedDoor != null) { node.TempDistance *= 10.0f; } + //avoid stopping at a ladder + if (node.Waypoint.Ladders != null) { node.TempDistance *= 10.0f; } + } + //optimization: node extremely far (> 100m / 800 m) from the end position, don't try to use it as an end node + if (node.TempDistance > (InsideSubmarine ? 100.0f * 100.0f : 800.0f * 800.0f)) + { + continue; + } + //optimization: node extremely close (< 1 m). If it's valid, choose it as the end node and skip the more exhaustive search for the closest one + if (node.TempDistance < 1.0f) + { + if (IsValidEndNode(node)) + { + endNode = node; + break; + } + } + int i = 0; + while (i < sortedNodes.Count && sortedNodes[i].TempDistance < node.TempDistance) + { + i++; + } + sortedNodes.Insert(i, node); + } + if (endNode == null) + { + //find the most suitable end node, starting from the ones closest to the end position + foreach (PathNode node in sortedNodes) + { + if (IsValidEndNode(node)) + { + endNode = node; + break; + } + } + } + if (endNode == null) + { +#if DEBUG + DebugConsole.NewMessage("Pathfinding error, couldn't find an end node. " + errorMsgStr, Color.DarkRed); +#endif + return new SteeringPath(true); + } + var path = FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize); + return path; + bool IsWaypointVisible(PathNode node, Vector2 rayStart, bool checkVisibility = true) { //if searching for a path inside the sub, make sure the waypoint is visible @@ -251,85 +346,33 @@ namespace Barotrauma return true; } - //find the most suitable start node, starting from the ones that are the closest - PathNode startNode = null; - foreach (PathNode node in sortedNodes) + bool IsValidStartNode(PathNode node) { - if (nodeFilter != null && !nodeFilter(node)) { continue; } - if (startNodeFilter != null && !startNodeFilter(node)) { continue; } + if (nodeFilter != null && !nodeFilter(node)) { return false; } + if (startNodeFilter != null && !startNodeFilter(node)) { return false; } // Always check the visibility for the start node - if (!IsWaypointVisible(node, start)) { continue; } - if (node.IsBlocked()) { continue; } + if (!IsWaypointVisible(node, start)) { return false; } + if (node.IsBlocked()) { return false; } if (node.Waypoint.ConnectedGap != null) { - if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } + if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { return false; } } - startNode = node; - break; + return true; } - if (startNode == null) + bool IsValidEndNode(PathNode node) { -#if DEBUG - DebugConsole.NewMessage("Pathfinding error, couldn't find a start node. "+ errorMsgStr, Color.DarkRed); -#endif - return new SteeringPath(true); - } - - //sort nodes again, now based on distance from the end position - sortedNodes.Clear(); - foreach (PathNode node in nodes) - { - node.TempDistance = Vector2.DistanceSquared(end, node.TempPosition); - if (InsideSubmarine) - { - if (ApplyPenaltyToOutsideNodes) - { - //much higher cost to waypoints that are outside - if (node.Waypoint.CurrentHull == null) { node.TempDistance *= 10.0f; } - } - //avoid stopping at a doorway - if (node.Waypoint.ConnectedDoor != null) { node.TempDistance *= 10.0f; } - //avoid stopping at a ladder - if (node.Waypoint.Ladders != null) { node.TempDistance *= 10.0f; } - } - - int i = 0; - while (i < sortedNodes.Count && sortedNodes[i].TempDistance < node.TempDistance) - { - i++; - } - sortedNodes.Insert(i, node); - } - - //find the most suitable end node, starting from the ones closest to the end position - PathNode endNode = null; - foreach (PathNode node in sortedNodes) - { - if (nodeFilter != null && !nodeFilter(node)) { continue; } - if (endNodeFilter != null && !endNodeFilter(node)) { continue; } + if (nodeFilter != null && !nodeFilter(node)) { return false; } + if (endNodeFilter != null && !endNodeFilter(node)) { return false; } // Only check the visibility for the end node when allowed (fix leaks) - if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { continue; } - if (node.IsBlocked()) { continue; } + if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { return false; } + if (node.IsBlocked()) { return false; } if (node.Waypoint.ConnectedGap != null) { - if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } + if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { return false; } } - endNode = node; - break; + return true; } - - if (endNode == null) - { -#if DEBUG - DebugConsole.NewMessage("Pathfinding error, couldn't find an end node. " + errorMsgStr, Color.DarkRed); -#endif - return new SteeringPath(true); - } - - var path = FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize); - - return path; } private SteeringPath FindPath(PathNode start, PathNode end, Func filter = null, string errorMsgStr = "", float minGapSize = 0) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorker.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorker.cs index 8e4a6d4e4..cb99e69b9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorker.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorker.cs @@ -1,4 +1,5 @@ using Barotrauma.Items.Components; +using Barotrauma.Extensions; using Microsoft.Xna.Framework; namespace Barotrauma @@ -47,14 +48,16 @@ namespace Barotrauma public void SetOrder(Character orderedCharacter) { OrderedCharacter = orderedCharacter; - if (orderedCharacter != CommandingCharacter) + if (OrderedCharacter.AIController is HumanAIController humanAI && humanAI.ObjectiveManager.CurrentOrders.None(o => o.MatchesOrder(SuggestedOrderPrefab, Option))) { - CommandingCharacter.Speak(SuggestedOrderPrefab.GetChatMessage(OrderedCharacter.Name, "", false)); + if (orderedCharacter != CommandingCharacter) + { + CommandingCharacter.Speak(SuggestedOrderPrefab.GetChatMessage(OrderedCharacter.Name, "", false), minDurationBetweenSimilar: 5); + } + CurrentOrder = new Order(SuggestedOrderPrefab, TargetItem, TargetItemComponent, CommandingCharacter); + OrderedCharacter.SetOrder(CurrentOrder, Option, priority: 3, CommandingCharacter, CommandingCharacter != OrderedCharacter); + OrderedCharacter.Speak(TextManager.Get("DialogAffirmative"), delay: 1.0f, minDurationBetweenSimilar: 5); } - - // not sure if new orders are supposed to be created each time. TODO m61: check later - CurrentOrder = new Order(SuggestedOrderPrefab, TargetItem, TargetItemComponent, CommandingCharacter); - OrderedCharacter.SetOrder(CurrentOrder, Option, priority: 3, CommandingCharacter, CommandingCharacter != OrderedCharacter); TimeSinceLastAttempt = 0f; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorkerOperateWeapons.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorkerOperateWeapons.cs index 1d4e55f2e..fb2aa21b9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorkerOperateWeapons.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorkerOperateWeapons.cs @@ -31,10 +31,10 @@ namespace Barotrauma } // there should maybe be additional logic for targeting and destroying spires, because they currently cause some issues with pathing - if (targetingImportances.Any()) + if (targetingImportances.Any(i => i > 0)) { targetingImportances.Sort(); - Importance = targetingImportances.TakeLast(3).Average(); + Importance = targetingImportances.TakeLast(3).Sum(); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommandManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommandManager.cs index 23856db00..f4d335bdf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommandManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommandManager.cs @@ -313,7 +313,7 @@ namespace Barotrauma ShipCommandLog("Dismissing " + shipIssueWorker + " for character " + shipIssueWorker.OrderedCharacter); #endif Order orderPrefab = Order.GetPrefab("dismissed"); - character.Speak(orderPrefab.GetChatMessage(shipIssueWorker.OrderedCharacter.Name, "", givingOrderToSelf: false)); + //character.Speak(orderPrefab.GetChatMessage(shipIssueWorker.OrderedCharacter.Name, "", givingOrderToSelf: false)); shipIssueWorker.OrderedCharacter.SetOrder(Order.GetPrefab("dismissed"), orderOption: null, priority: 3, character); shipIssueWorker.RemoveOrder(); break; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs index bbaba7ba8..f9a0b6c2e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs @@ -462,7 +462,7 @@ namespace Barotrauma DebugConsole.Log(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "HumanoidAnimController.HoldItem:InvalidPos:" + character.Name + item.Name, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs index c37d0d8b1..e829475c3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs @@ -951,7 +951,7 @@ namespace Barotrauma { string errorMsg = "Creature death animation error: invalid limb mass on character \"" + character.SpeciesName + "\" (type: " + limb.type + ", mass: " + limb.Mass + ")"; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidMass" + character.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidMass" + character.ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); deathAnimTimer = deathAnimDuration; return; } @@ -961,7 +961,7 @@ namespace Barotrauma { string errorMsg = "Creature death animation error: invalid diff (center of mass: " + centerOfMass + ", limb position: " + limb.SimPosition + ")"; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidDiff" + character.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidDiff" + character.ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); deathAnimTimer = deathAnimDuration; return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 7012a205f..109cec337 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -811,35 +811,41 @@ namespace Barotrauma if (currentHull != null) { float surfacePos = currentHull.Surface; + float surfaceThreshold = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 1.0f); //if the hull is almost full of water, check if there's a water-filled hull above it //and use its water surface instead of the current hull's if (currentHull.Rect.Y - currentHull.Surface < 5.0f) { - foreach (Gap gap in currentHull.ConnectedGaps) + GetSurfacePos(CurrentHull, ref surfacePos); + void GetSurfacePos(Hull hull, ref float prevSurfacePos) { - if (gap.IsHorizontal || gap.Open <= 0.0f) { continue; } - if (Collider.SimPosition.X < ConvertUnits.ToSimUnits(gap.Rect.X) || Collider.SimPosition.X > ConvertUnits.ToSimUnits(gap.Rect.Right)) { continue; } - - //if the gap is above us and leads outside, there's no surface to limit the movement - if (!gap.IsRoomToRoom && gap.Position.Y > currentHull.Position.Y) + if (prevSurfacePos > surfaceThreshold) { return; } + foreach (Gap gap in hull.ConnectedGaps) { - surfacePos += 100000.0f; - continue; - } + if (gap.IsHorizontal || gap.Open <= 0.0f || gap.WorldPosition.Y < hull.WorldPosition.Y) { continue; } + if (Collider.SimPosition.X < ConvertUnits.ToSimUnits(gap.Rect.X) || Collider.SimPosition.X > ConvertUnits.ToSimUnits(gap.Rect.Right)) { continue; } - foreach (var linkedTo in gap.linkedTo) - { - if (linkedTo is Hull hull && hull != currentHull) + //if the gap is above us and leads outside, there's no surface to limit the movement + if (!gap.IsRoomToRoom && gap.Position.Y > hull.Position.Y) { - surfacePos = Math.Max(surfacePos, hull.Surface); - break; + prevSurfacePos += 100000.0f; + return; + } + + foreach (var linkedTo in gap.linkedTo) + { + if (linkedTo is Hull otherHull && otherHull != hull) + { + prevSurfacePos = Math.Max(surfacePos, otherHull.Surface); + GetSurfacePos(otherHull, ref prevSurfacePos); + break; + } } } } } - surfaceLimiter = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 1.0f) - surfacePos; - surfaceLimiter = Math.Max(1.0f, surfaceLimiter); + surfaceLimiter = Math.Max(1.0f, surfaceThreshold - surfacePos); if (surfaceLimiter > 50.0f) { return; } } @@ -1058,10 +1064,12 @@ namespace Barotrauma onGround = false; IgnorePlatforms = true; - Vector2 tempTargetMovement = TargetMovement; - tempTargetMovement.Y = Math.Min(tempTargetMovement.Y, 1.0f); - + bool climbFast = targetMovement.Y > 3.0f; bool slide = targetMovement.Y < -1.1f; + Vector2 tempTargetMovement = TargetMovement; + tempTargetMovement.Y = climbFast ? + Math.Min(tempTargetMovement.Y, 2.0f) : + Math.Min(tempTargetMovement.Y, 1.0f); movement = MathUtils.SmoothStep(movement, tempTargetMovement, 0.3f); @@ -1089,6 +1097,7 @@ namespace Barotrauma } float stepHeight = ConvertUnits.ToSimUnits(30.0f); + if (climbFast) { stepHeight *= 2; } if (currentHull == null && ladder.Item.Submarine != null) { @@ -1104,58 +1113,69 @@ namespace Barotrauma } float bottomPos = Collider.SimPosition.Y - ColliderHeightFromFloor - Collider.radius - Collider.height / 2.0f; - float headPos = HeadPosition ?? 0; float torsoPos = TorsoPosition ?? 0; - MoveLimb(head, new Vector2(ladderSimPos.X - 0.2f * Dir, bottomPos + headPos), 10.5f); MoveLimb(torso, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + torsoPos), 10.5f); + float headPos = HeadPosition ?? 0; + MoveLimb(head, new Vector2(ladderSimPos.X - 0.2f * Dir, bottomPos + headPos), 10.5f); Collider.MoveToPos(new Vector2(ladderSimPos.X - 0.1f * Dir, Collider.SimPosition.Y), 10.5f); Vector2 handPos = new Vector2( ladderSimPos.X, bottomPos + torsoPos + movement.Y * 0.1f - ladderSimPos.Y); + if (climbFast) { handPos.Y -= stepHeight; } + bool aiming = this.aiming || aimingMelee; //prevent the hands from going above the top of the ladders handPos.Y = Math.Min(-0.5f, handPos.Y); - - if (!character.IsKeyDown(InputType.Aim) || Math.Abs(movement.Y) > 0.01f) + if (!aiming || !(character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand)?.GetComponent()?.ControlPose ?? false) || Math.Abs(movement.Y) > 0.01f) + { + MoveLimb(rightHand, + new Vector2(slide ? handPos.X + ladderSimSize.X * 0.5f : handPos.X, + (slide ? handPos.Y : MathUtils.Round(handPos.Y, stepHeight * 2.0f)) + ladderSimPos.Y), + 5.2f); + rightHand.body.ApplyTorque(Dir * 2.0f); + } + if (!aiming || !(character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand)?.GetComponent()?.ControlPose ?? false) || Math.Abs(movement.Y) > 0.01f) { MoveLimb(leftHand, new Vector2(handPos.X - ladderSimSize.X * 0.5f, (slide ? handPos.Y : MathUtils.Round(handPos.Y - stepHeight, stepHeight * 2.0f) + stepHeight) + ladderSimPos.Y), 5.2f); ; - - MoveLimb(rightHand, - new Vector2(slide ? handPos.X + ladderSimSize.X * 0.5f : handPos.X, - (slide ? handPos.Y : MathUtils.Round(handPos.Y, stepHeight * 2.0f)) + ladderSimPos.Y), - 5.2f); - leftHand.body.ApplyTorque(Dir * 2.0f); - rightHand.body.ApplyTorque(Dir * 2.0f); } Vector2 footPos = new Vector2( handPos.X - Dir * 0.05f, bottomPos + ColliderHeightFromFloor - stepHeight * 2.7f - ladderSimPos.Y); + if (climbFast) { footPos.Y += stepHeight; } + + //apply torque to the legs to make the knees bend + Limb leftLeg = GetLimb(LimbType.LeftLeg); + Limb rightLeg = GetLimb(LimbType.RightLeg); //only move the feet if they're above the bottom of the ladders //(if not, they'll just dangle in air, and the character holds itself up with it's arms) - if (footPos.Y > -ladderSimSize.Y && leftFoot != null && rightFoot != null) + if (footPos.Y > -ladderSimSize.Y - 0.2f && leftFoot != null && rightFoot != null) { + Limb refLimb = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso); + bool leftLegBackwards = Math.Abs(leftLeg.body.Rotation - refLimb.body.Rotation) > MathHelper.Pi; + bool rightLegBackwards = Math.Abs(rightLeg.body.Rotation - refLimb.body.Rotation) > MathHelper.Pi; + if (slide) { - MoveLimb(leftFoot, new Vector2(footPos.X - ladderSimSize.X * 0.5f, footPos.Y + ladderSimPos.Y), 15.5f, true); - MoveLimb(rightFoot, new Vector2(footPos.X, footPos.Y + ladderSimPos.Y), 15.5f, true); + if (!leftLegBackwards) { MoveLimb(leftFoot, new Vector2(footPos.X - ladderSimSize.X * 0.5f, footPos.Y + ladderSimPos.Y), 15.5f, true); } + if (!rightLegBackwards) { MoveLimb(rightFoot, new Vector2(footPos.X, footPos.Y + ladderSimPos.Y), 15.5f, true); } } else { float leftFootPos = MathUtils.Round(footPos.Y + stepHeight, stepHeight * 2.0f) - stepHeight; float prevLeftFootPos = MathUtils.Round(prevFootPos + stepHeight, stepHeight * 2.0f) - stepHeight; - MoveLimb(leftFoot, new Vector2(footPos.X, leftFootPos + ladderSimPos.Y), 15.5f, true); + if (!leftLegBackwards) { MoveLimb(leftFoot, new Vector2(footPos.X, leftFootPos + ladderSimPos.Y), 15.5f, true); } float rightFootPos = MathUtils.Round(footPos.Y, stepHeight * 2.0f); float prevRightFootPos = MathUtils.Round(prevFootPos, stepHeight * 2.0f); - MoveLimb(rightFoot, new Vector2(footPos.X, rightFootPos + ladderSimPos.Y), 15.5f, true); + if (!rightLegBackwards) { MoveLimb(rightFoot, new Vector2(footPos.X, rightFootPos + ladderSimPos.Y), 15.5f, true); } #if CLIENT if (Math.Abs(leftFootPos - prevLeftFootPos) > stepHeight && leftFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval) { @@ -1171,12 +1191,8 @@ namespace Barotrauma prevFootPos = footPos.Y; } - //apply torque to the legs to make the knees bend - Limb leftLeg = GetLimb(LimbType.LeftLeg); - Limb rightLeg = GetLimb(LimbType.RightLeg); - - leftLeg.body.ApplyTorque(Dir * -8.0f); - rightLeg.body.ApplyTorque(Dir * -8.0f); + if (!leftLegBackwards) { leftLeg.body.ApplyTorque(Dir * -8.0f); } + if (!rightLegBackwards) { rightLeg.body.ApplyTorque(Dir * -8.0f); } } float movementFactor = (handPos.Y / stepHeight) * (float)Math.PI; @@ -1185,8 +1201,11 @@ namespace Barotrauma Vector2 subSpeed = currentHull != null || ladder.Item.Submarine == null ? Vector2.Zero : ladder.Item.Submarine.Velocity; - Vector2 climbForce = new Vector2(0.0f, movement.Y + 0.3f) * movementFactor; //reached the top of the ladders -> can't go further up + Vector2 climbForce = new Vector2(0.0f, movement.Y) * movementFactor; + + if (!InWater) { climbForce.Y += 0.3f * movementFactor; } + if (character.SimPosition.Y > ladderSimPos.Y) { climbForce.Y = Math.Min(0.0f, climbForce.Y); } //reached the bottom -> can't go further down float minHeightFromFloor = ColliderHeightFromFloor / 2 + Collider.height; @@ -1200,8 +1219,11 @@ namespace Barotrauma //apply forces to the collider to move the Character up/down Collider.ApplyForce((climbForce * 20.0f + subSpeed * 50.0f) * Collider.Mass); - float movementMultiplier = targetMovement.Y < 0 ? 0 : 1; - head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, WalkParams.HeadTorque); + if (!aiming) + { + float movementMultiplier = targetMovement.Y < 0 ? 0 : 1; + head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, WalkParams.HeadTorque); + } if (!ladder.Item.Prefab.Triggers.Any()) { @@ -1688,7 +1710,7 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError(errorMsg); #endif - GameAnalyticsManager.AddErrorEventOnce("FootIK:InvalidPos", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("FootIK:InvalidPos", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } @@ -1722,7 +1744,7 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError(errorMsg); #endif - GameAnalyticsManager.AddErrorEventOnce("FootIK:InvalidAngle", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("FootIK:InvalidAngle", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index 9dafa66bc..31d8938b8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -64,8 +64,8 @@ namespace Barotrauma DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "Ragdoll.Limbs:AccessRemoved", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this) + "\n" + Environment.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.ErrorSeverity.Error, + "Attempted to access a potentially removed ragdoll. Character: " + character.SpeciesName + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this) + "\n" + Environment.StackTrace.CleanupStackTrace()); accessRemovedCharacterErrorShown = true; } return new Limb[0]; @@ -885,7 +885,7 @@ namespace Barotrauma string errorMsg = "Ragdoll.GetCenterOfMass returned an invalid value (" + centerOfMass + "). Limb positions: {" + string.Join(", ", limbs.Select(l => l.SimPosition)) + "}, total mass: " + totalMass + "."; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Ragdoll.GetCenterOfMass", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Ragdoll.GetCenterOfMass", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return Collider.SimPosition; } @@ -923,7 +923,7 @@ namespace Barotrauma { GameAnalyticsManager.AddErrorEventOnce( "Ragdoll.FindHull:InvalidPosition", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Attempted to find a hull at an invalid position (" + findPos + ")\n" + Environment.StackTrace.CleanupStackTrace()); return; } @@ -1354,19 +1354,19 @@ namespace Barotrauma string errorMsg = null; if (!MathUtils.IsValid(body.SimPosition) || Math.Abs(body.SimPosition.X) > 1e10f || Math.Abs(body.SimPosition.Y) > 1e10f) { - errorMsg = GetBodyName() + " position invalid (" + body.SimPosition + ", character: " + character.Name + ")."; + errorMsg = GetBodyName() + " position invalid (" + body.SimPosition + ", character: [name])."; } else if (!MathUtils.IsValid(body.LinearVelocity) || Math.Abs(body.LinearVelocity.X) > 1000f || Math.Abs(body.LinearVelocity.Y) > 1000f) { - errorMsg = GetBodyName() + " velocity invalid (" + body.LinearVelocity + ", character: " + character.Name + ")."; + errorMsg = GetBodyName() + " velocity invalid (" + body.LinearVelocity + ", character: [name])."; } else if (!MathUtils.IsValid(body.Rotation)) { - errorMsg = GetBodyName() + " rotation invalid (" + body.Rotation + ", character: " + character.Name + ")."; + errorMsg = GetBodyName() + " rotation invalid (" + body.Rotation + ", character: [name])."; } else if (!MathUtils.IsValid(body.AngularVelocity) || Math.Abs(body.AngularVelocity) > 1000f) { - errorMsg = GetBodyName() + " angular velocity invalid (" + body.AngularVelocity + ", character: " + character.Name + ")."; + errorMsg = GetBodyName() + " angular velocity invalid (" + body.AngularVelocity + ", character: [name])."; } if (errorMsg != null) { @@ -1384,11 +1384,11 @@ namespace Barotrauma } #if DEBUG - DebugConsole.ThrowError(errorMsg); + DebugConsole.ThrowError(errorMsg.Replace("[name]", Character.Name)); #else - DebugConsole.NewMessage(errorMsg, Color.Red); + DebugConsole.NewMessage(errorMsg.Replace("[name]", Character.Name), Color.Red); #endif - GameAnalyticsManager.AddErrorEventOnce("Ragdoll.CheckValidity:" + character.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Ragdoll.CheckValidity:" + character.ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace("[name]", Character.SpeciesName)); if (!MathUtils.IsValid(Collider.SimPosition) || Math.Abs(Collider.SimPosition.X) > 1e10f || Math.Abs(Collider.SimPosition.Y) > 1e10f) { @@ -1627,8 +1627,8 @@ namespace Barotrauma DebugConsole.ThrowError("Attempted to move a ragdoll (" + character.Name + ") to an invalid position (" + simPosition + "). " + Environment.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce( "Ragdoll.SetPosition:InvalidPosition", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Attempted to move a ragdoll (" + character.Name + ") to an invalid position (" + simPosition + "). " + Environment.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.ErrorSeverity.Error, + "Attempted to move a ragdoll (" + character.SpeciesName + ") to an invalid position (" + simPosition + "). " + Environment.StackTrace.CleanupStackTrace()); return; } if (MainLimb == null) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CauseOfDeath.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CauseOfDeath.cs index 987bf64af..e1cd01576 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CauseOfDeath.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CauseOfDeath.cs @@ -20,7 +20,7 @@ namespace Barotrauma { string errorMsg = "Invalid cause of death (the type of the cause of death was Affliction, but affliction was not specified).\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("InvalidCauseOfDeath", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("InvalidCauseOfDeath", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); type = CauseOfDeathType.Unknown; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index d6cbaf017..ffe3596fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -612,8 +612,7 @@ namespace Barotrauma get { if (IsUnconscious) { return true; } - if (IsDead) { return true; } - return CharacterHealth.Afflictions.Any(a => a.Prefab.AfflictionType == "paralysis" && a.Strength >= a.Prefab.MaxStrength); + return CharacterHealth.GetAllAfflictions().Any(a => a.Prefab.AfflictionType == "paralysis" && a.Strength >= a.Prefab.MaxStrength); } } @@ -643,6 +642,11 @@ namespace Barotrauma set { oxygenAvailable = MathHelper.Clamp(value, 0.0f, 100.0f); } } + public float HullOxygenPercentage + { + get { return CurrentHull?.OxygenPercentage ?? 0.0f; } + } + public bool UseHullOxygen { get; set; } = true; public float Stun @@ -691,12 +695,12 @@ namespace Barotrauma { get { - if (!CanSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f; + if (!CanSpeak || IsUnconscious || Stun > 0.0f || IsDead) { return 100.0f; } return speechImpediment; } set { - if (value < speechImpediment) return; + if (value < speechImpediment) { return; } speechImpedimentSet = true; speechImpediment = MathHelper.Clamp(value, 0.0f, 100.0f); } @@ -808,7 +812,7 @@ namespace Barotrauma { if (!canBeDragged) { return false; } if (Removed || !AnimController.Draggable) { return false; } - return IsKnockedDown || LockHands || IsPet; + return IsKnockedDown || LockHands || IsPet || CanInventoryBeAccessed; } set { canBeDragged = value; } } @@ -826,7 +830,7 @@ namespace Barotrauma } else { - return IsKnockedDown || LockHands; + return IsKnockedDown || LockHands || IsBot && TeamID != CharacterTeamType.FriendlyNPC; } } set { canInventoryBeAccessed = value; } @@ -855,7 +859,7 @@ namespace Barotrauma { if (!accessRemovedCharacterErrorShown) { - string errorMsg = "Attempted to access a potentially removed character. Character: " + Name + ", id: " + ID + ", removed: " + Removed + "."; + string errorMsg = "Attempted to access a potentially removed character. Character: [name], id: " + ID + ", removed: " + Removed + "."; if (AnimController == null) { errorMsg += " AnimController == null"; @@ -865,11 +869,11 @@ namespace Barotrauma errorMsg += " AnimController.Collider == null"; } errorMsg += '\n' + Environment.StackTrace.CleanupStackTrace(); - DebugConsole.NewMessage(errorMsg, Color.Red); + DebugConsole.NewMessage(errorMsg.Replace("[name]", Name), Color.Red); GameAnalyticsManager.AddErrorEventOnce( "Character.SimPosition:AccessRemoved", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.ErrorSeverity.Error, + errorMsg.Replace("[name]", SpeciesName) + "\n" + Environment.StackTrace.CleanupStackTrace()); accessRemovedCharacterErrorShown = true; } return Vector2.Zero; @@ -1359,7 +1363,11 @@ namespace Barotrauma public override string ToString() { +#if DEBUG return (info != null && !string.IsNullOrWhiteSpace(info.Name)) ? info.Name : SpeciesName; +#else + return SpeciesName; +#endif } public void GiveJobItems(WayPoint spawnPoint = null) @@ -1985,7 +1993,7 @@ namespace Barotrauma public bool CanSeeTarget(ISpatialEntity target, ISpatialEntity seeingEntity = null) { - seeingEntity ??= AnimController.SimplePhysicsEnabled ? this as ISpatialEntity : GetSeeingLimb() as ISpatialEntity; + seeingEntity ??= AnimController.SimplePhysicsEnabled ? this : GetSeeingLimb() as ISpatialEntity; if (seeingEntity == null) { return false; } ISpatialEntity sourceEntity = seeingEntity ; // TODO: Could we just use the method below? If not, let's refactor it so that we can. @@ -2020,16 +2028,11 @@ namespace Barotrauma { return wall != target; } - else if (body.UserData is Item item && item != target) + else if (body.UserData is Item item) { - // TODO: The door collider should be disabled, so this check is probably unnecessary. - var door = item.GetComponent(); - if (door != null) - { - return !door.CanBeTraversed; - } + return item != target; } - return false; + return true; } } @@ -2038,7 +2041,7 @@ namespace Barotrauma /// public bool IsFacing(Vector2 targetWorldPos) => AnimController.Dir > 0 && targetWorldPos.X > WorldPosition.X || AnimController.Dir < 0 && targetWorldPos.X < WorldPosition.X; - public bool HasItem(Item item, bool requireEquipped = false, InvSlotType? slotType = null) => requireEquipped ? HasEquippedItem(item) : item.IsOwnedBy(this); + public bool HasItem(Item item, bool requireEquipped = false, InvSlotType? slotType = null) => requireEquipped ? HasEquippedItem(item, slotType) : item.IsOwnedBy(this); public bool HasEquippedItem(Item item, InvSlotType? slotType = null) { @@ -2236,6 +2239,10 @@ namespace Barotrauma { return wire.Connections[0] == null; } + if (SelectedConstruction?.GetComponent()?.DisconnectedWires.Contains(wire) ?? false) + { + return wire.Connections[0] == null && wire.Connections[1] == null; + } } if (checkLinked && item.DisplaySideBySideWhenLinked) @@ -2469,7 +2476,10 @@ namespace Barotrauma { minDist = dist; nearbyLadder = ladder; - if (isControlled) ladder.Item.IsHighlighted = true; + if (isControlled) + { + ladder.Item.IsHighlighted = true; + } break; } } @@ -2477,7 +2487,10 @@ namespace Barotrauma if (nearbyLadder != null && climbInput) { - if (nearbyLadder.Select(this)) SelectedConstruction = nearbyLadder.Item; + if (nearbyLadder.Select(this)) + { + SelectedConstruction = nearbyLadder.Item; + } } } @@ -2499,14 +2512,20 @@ namespace Barotrauma { DeselectCharacter(); #if CLIENT - if (Controlled == this) CharacterHealth.OpenHealthWindow = null; + if (Controlled == this) + { + CharacterHealth.OpenHealthWindow = null; + } #endif } else { SelectCharacter(FocusedCharacter); #if CLIENT - if (Controlled == this) CharacterHealth.OpenHealthWindow = FocusedCharacter.CharacterHealth; + if (Controlled == this) + { + CharacterHealth.OpenHealthWindow = FocusedCharacter.CharacterHealth; + } #endif } } @@ -2873,6 +2892,18 @@ namespace Barotrauma } } + public float GetDamageDoneByAttacker(Character otherCharacter) + { + if (otherCharacter == null) { return 0; } + float dmg = 0; + Attacker attacker = LastAttackers.LastOrDefault(a => a.Character == otherCharacter); + if (attacker != null) + { + dmg = attacker.Damage; + } + return dmg; + } + private void UpdateAttackers(float deltaTime) { //slowly forget about damage done by attackers @@ -3329,6 +3360,8 @@ namespace Barotrauma if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } if (string.IsNullOrEmpty(message)) { return; } + if (SpeechImpediment >= 100.0f) { return; } + if (prevAiChatMessages.ContainsKey(identifier) && prevAiChatMessages[identifier] < Timing.TotalTime - minDurationBetweenSimilar) { @@ -3346,13 +3379,13 @@ namespace Barotrauma private void UpdateAIChatMessages(float deltaTime) { - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return; + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } List sentMessages = new List(); foreach (AIChatMessage message in aiChatMessageQueue) { message.SendDelay -= deltaTime; - if (message.SendDelay > 0.0f) continue; + if (message.SendDelay > 0.0f) { continue; } if (message.MessageType == null) { @@ -3429,9 +3462,9 @@ namespace Barotrauma { if (Removed) { - string errorMsg = "Tried to apply an attack to a removed character (" + Name + ").\n" + Environment.StackTrace.CleanupStackTrace(); - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Character.ApplyAttack:RemovedCharacter", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + string errorMsg = "Tried to apply an attack to a removed character ([name]).\n" + Environment.StackTrace.CleanupStackTrace(); + DebugConsole.ThrowError(errorMsg.Replace("[name]", Name)); + GameAnalyticsManager.AddErrorEventOnce("Character.ApplyAttack:RemovedCharacter", GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace("[name]", SpeciesName)); return new AttackResult(); } @@ -3620,7 +3653,7 @@ namespace Barotrauma // { // string errorMsg = $"Character {Name} received damage from outside the sub while inside (attacker: {attacker.Name})"; // GameAnalyticsManager.AddErrorEventOnce("Character.DamageLimb:DamageFromOutside" + Name + attacker.Name, -// GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, +// GameAnalyticsManager.ErrorSeverity.Warning, // errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); //#if DEBUG // DebugConsole.ThrowError(errorMsg); @@ -3850,7 +3883,7 @@ namespace Barotrauma { string errorMsg = "Attempted to apply an invalid impulse to a limb in Character.BreakJoints (" + diff + "). Limb position: " + limb.SimPosition + ", center of mass: " + centerOfMass + "."; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Ragdoll.GetCenterOfMass", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Ragdoll.GetCenterOfMass", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } @@ -3895,7 +3928,7 @@ namespace Barotrauma AnimController.Frozen = false; - if (GameSettings.SendUserStatistics) + if (GameAnalyticsManager.SendUserStatistics) { string characterType = "Unknown"; @@ -4078,6 +4111,10 @@ namespace Barotrauma Submarine = null; AnimController.SetPosition(ConvertUnits.ToSimUnits(worldPos), lerp: false); AnimController.FindHull(worldPos, setSubmarine: true); + if (AIController is HumanAIController humanAI) + { + humanAI.PathSteering?.ResetPath(); + } } public static void SaveInventory(Inventory inventory, XElement parentElement) @@ -4448,6 +4485,10 @@ namespace Barotrauma public static IEnumerable GetFriendlyCrew(Character character) { + if (character is null) + { + return Enumerable.Empty(); + } return CharacterList.Where(c => HumanAIController.IsFriendly(character, c, onlySameTeam: true) && !c.IsDead); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 00212a48b..34543f566 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -365,6 +365,30 @@ namespace Barotrauma public const int MaxCurrentOrders = 3; public static int HighestManualOrderPriority => MaxCurrentOrders; + public int GetManualOrderPriority(Order order) + { + if (order != null && order.AssignmentPriority < 100 && CurrentOrders.Any()) + { + int orderPriority = HighestManualOrderPriority; + for (int i = 0; i < CurrentOrders.Count; i++) + { + if (CurrentOrders[i].Order is Order currentOrder && order.AssignmentPriority >= currentOrder.AssignmentPriority) + { + break; + } + else + { + orderPriority--; + } + } + return Math.Max(orderPriority, 1); + } + else + { + return HighestManualOrderPriority; + } + } + public List CurrentOrders { get; } = new List(); //unique ID given to character infos in MP @@ -1387,7 +1411,6 @@ namespace Barotrauma foreach (var savedStat in statValuePair.Value) { if (savedStat.StatValue == 0f) { continue; } - if (savedStat.RemoveAfterRound) { continue; } savedStatElement.Add(new XElement("savedstatvalue", new XAttribute("stattype", statValuePair.Key.ToString()), @@ -1745,6 +1768,20 @@ namespace Barotrauma OnPermanentStatChanged(statType); } + public void RemoveSavedStatValuesOnDeath() + { + foreach (StatTypes statType in SavedStatValues.Keys) + { + foreach (SavedStatValue savedStatValue in SavedStatValues[statType]) + { + if (!savedStatValue.RemoveOnDeath) { continue; } + if (MathUtils.NearlyEqual(savedStatValue.StatValue, 0.0f)) { continue; } + savedStatValue.StatValue = 0.0f; + // no need to make a network update, as this is only done after the character has died + } + } + } + public void ResetSavedStatValue(string statIdentifier) { foreach (StatTypes statType in SavedStatValues.Keys) @@ -1784,7 +1821,7 @@ namespace Barotrauma } } - public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue, bool setValue = false) + public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, float maxValue = float.MaxValue, bool setValue = false) { if (!SavedStatValues.ContainsKey(statType)) { @@ -1800,7 +1837,7 @@ namespace Barotrauma } else { - SavedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound)); + SavedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath)); changed = true; } if (changed) { OnPermanentStatChanged(statType); } @@ -1812,14 +1849,12 @@ namespace Barotrauma public string StatIdentifier { get; set; } public float StatValue { get; set; } public bool RemoveOnDeath { get; set; } - public bool RemoveAfterRound { get; set; } - public SavedStatValue(string statIdentifier, float value, bool removeOnDeath, bool retainAfterRound) + public SavedStatValue(string statIdentifier, float value, bool removeOnDeath) { StatValue = value; RemoveOnDeath = removeOnDeath; StatIdentifier = statIdentifier; - RemoveAfterRound = retainAfterRound; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs index 347b52096..3407a90ed 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs @@ -99,15 +99,21 @@ namespace Barotrauma public float GetVitalityDecrease(CharacterHealth characterHealth) { - if (Strength < Prefab.ActivationThreshold) { return 0.0f; } - AfflictionPrefab.Effect currentEffect = GetActiveEffect(); + return GetVitalityDecrease(characterHealth, Strength); + } + + public float GetVitalityDecrease(CharacterHealth characterHealth, float strength) + { + if (strength < Prefab.ActivationThreshold) { return 0.0f; } + strength = MathHelper.Clamp(strength, 0.0f, Prefab.MaxStrength); + AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(strength); if (currentEffect == null) { return 0.0f; } if (currentEffect.MaxStrength - currentEffect.MinStrength <= 0.0f) { return 0.0f; } float currVitalityDecrease = MathHelper.Lerp( - currentEffect.MinVitalityDecrease, - currentEffect.MaxVitalityDecrease, - (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); + currentEffect.MinVitalityDecrease, + currentEffect.MaxVitalityDecrease, + (strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); if (currentEffect.MultiplyByMaxVitality) { @@ -116,7 +122,8 @@ namespace Barotrauma return currVitalityDecrease; } - + + public float GetScreenGrainStrength() { if (Strength < Prefab.ActivationThreshold) { return 0.0f; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index 165ed8bdd..3a7b01df7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -59,17 +59,25 @@ namespace Barotrauma } } - private float DormantThreshold => (Prefab as AfflictionPrefabHusk)?.DormantThreshold ?? Prefab.MaxStrength * 0.5f; - private float ActiveThreshold => (Prefab as AfflictionPrefabHusk)?.ActiveThreshold ?? Prefab.MaxStrength * 0.75f; + private readonly AfflictionPrefabHusk HuskPrefab; - private float TransitionThreshold => (Prefab as AfflictionPrefabHusk)?.TransitionThreshold ?? Prefab.MaxStrength * 0.75f; + private float DormantThreshold => HuskPrefab.DormantThreshold; + private float ActiveThreshold => HuskPrefab.ActiveThreshold; + private float TransitionThreshold => HuskPrefab.TransitionThreshold; + private float TransformThresholdOnDeath => HuskPrefab.TransformThresholdOnDeath; - private float TransformThresholdOnDeath => (Prefab as AfflictionPrefabHusk)?.TransformThresholdOnDeath ?? ActiveThreshold; - - public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength) { } + public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength) + { + HuskPrefab = prefab as AfflictionPrefabHusk; + if (HuskPrefab == null) + { + DebugConsole.ThrowError("Error in husk affliction definition: the prefab is of wrong type!"); + } + } public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime) { + if (HuskPrefab == null) { return; } base.Update(characterHealth, targetLimb, deltaTime); character = characterHealth.Character; if (character == null) { return; } @@ -272,11 +280,13 @@ namespace Barotrauma if ((Prefab as AfflictionPrefabHusk)?.TransferBuffs ?? false) { - foreach (Affliction affliction in character.CharacterHealth.Afflictions) + foreach (Affliction affliction in character.CharacterHealth.GetAllAfflictions()) { if (affliction.Prefab.IsBuff) { - husk.CharacterHealth.ApplyAffliction(null, affliction.Prefab.Instantiate(affliction.Strength)); + husk.CharacterHealth.ApplyAffliction( + character.CharacterHealth.GetAfflictionLimb(affliction), + affliction.Prefab.Instantiate(affliction.Strength)); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index e20ac25cf..983c91471 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -222,6 +222,9 @@ namespace Barotrauma [Serialize("", false)] public string DialogFlag { get; private set; } + [Serialize("", false)] + public string Tag { get; private set; } + [Serialize("0,0,0,0", false)] public Color MinFaceTint { get; private set; } @@ -272,6 +275,9 @@ namespace Barotrauma var flagType = CharacterAbilityGroup.ParseFlagType(subElement.GetAttributeString("flagtype", ""), parentDebugName); AfflictionAbilityFlags.Add(flagType); break; + case "affliction": + DebugConsole.AddWarning($"Error in affliction \"{parentDebugName}\" - additional afflictions caused by the affliction should be configured inside status effects."); + break; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index bb24a39c9..d942a2c72 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -21,18 +21,11 @@ namespace Barotrauma public readonly string Name; - public readonly List Afflictions = new List(); + //public readonly List Afflictions = new List(); public readonly Dictionary VitalityMultipliers = new Dictionary(); public readonly Dictionary VitalityTypeMultipliers = new Dictionary(); - private readonly CharacterHealth characterHealth; - - public float TotalDamage - { - get { return Afflictions.Sum(a => a.GetVitalityDecrease(characterHealth)); } - } - public LimbHealth() { } public LimbHealth(XElement element, CharacterHealth characterHealth) @@ -42,7 +35,6 @@ namespace Barotrauma { Name = TextManager.Get("HealthLimbName." + limbName); } - this.characterHealth = characterHealth; foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -76,22 +68,16 @@ namespace Barotrauma } } } - - public List GetActiveAfflictions(AfflictionPrefab prefab) - { - return Afflictions.FindAll(a => a.Prefab == prefab); - } - public List GetActiveAfflictions(string afflictionType) - { - return Afflictions.FindAll(a => a.Prefab.AfflictionType == afflictionType); - } } public const float InsufficientOxygenThreshold = 30.0f; public const float LowOxygenThreshold = 50.0f; protected float minVitality; - protected float maxVitality + /// + /// Maximum vitality without talent- or job-based modifiers + /// + protected float UnmodifiedMaxVitality { get => Character.Params.Health.Vitality; set => Character.Params.Health.Vitality = value; @@ -118,13 +104,8 @@ namespace Barotrauma } private readonly List limbHealths = new List(); - //non-limb-specific afflictions - private readonly List afflictions = new List(); - /// - /// Note: returns only the non-limb-secific afflictions. Use GetAllAfflictions or some other method for getting also the limb-specific afflictions. - /// - public IEnumerable Afflictions => afflictions; + private readonly Dictionary afflictions = new Dictionary(); private readonly HashSet irremovableAfflictions = new HashSet(); private Affliction bloodlossAffliction; private Affliction oxygenLowAffliction; @@ -146,7 +127,7 @@ namespace Barotrauma { get { - float max = maxVitality; + float max = UnmodifiedMaxVitality; if (Character?.Info?.Job?.Prefab != null) { max += Character.Info.Job.Prefab.VitalityModifier; @@ -242,7 +223,7 @@ namespace Barotrauma this.Character = character; InitIrremovableAfflictions(); - Vitality = maxVitality; + Vitality = UnmodifiedMaxVitality; minVitality = character.IsHuman ? -100.0f : 0.0f; @@ -269,57 +250,37 @@ namespace Barotrauma irremovableAfflictions.Add(oxygenLowAffliction = new Affliction(AfflictionPrefab.OxygenLow, 0.0f)); foreach (Affliction affliction in irremovableAfflictions) { - afflictions.Add(affliction); + afflictions.Add(affliction, null); } } partial void InitProjSpecific(XElement element, Character character); - public IEnumerable GetAllAfflictions(Func limbHealthFilter = null) + public IReadOnlyCollection GetAllAfflictions() { - return limbHealthFilter == null - ? afflictions.Union(limbHealths.SelectMany(lh => lh.Afflictions)) - : afflictions.Where(limbHealthFilter).Union(limbHealths.SelectMany(lh => lh.Afflictions.Where(limbHealthFilter))); + return afflictions.Keys; + } + + public IEnumerable GetAllAfflictions(Func limbHealthFilter) + { + return afflictions.Keys.Where(limbHealthFilter); + } + + private float GetTotalDamage(LimbHealth limbHealth) + { + float totalDamage = 0.0f; + foreach (KeyValuePair kvp in afflictions) + { + if (kvp.Value != limbHealth) { continue; } + var affliction = kvp.Key; + totalDamage += affliction.GetVitalityDecrease(this); + } + return totalDamage; } private LimbHealth GetMatchingLimbHealth(Limb limb) => limb == null ? null : limbHealths[limb.HealthIndex]; private LimbHealth GetMatchingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb, excludeSevered: false)); - /// - /// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb. - /// - private IEnumerable GetMatchingAfflictions(LimbHealth limb) - => limb.Afflictions.Union(afflictions.Where(a => GetMatchingLimbHealth(a) == limb)); - - /// - /// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb. - /// - private IEnumerable GetMatchingAfflictions(LimbHealth limb, Func predicate) - => limb.Afflictions.Where(predicate).Union(afflictions.Where(a => predicate(a) && GetMatchingLimbHealth(a) == limb)); - - public IEnumerable GetAfflictionsByType(string afflictionType, bool allowLimbAfflictions = true) - { - if (allowLimbAfflictions) - { - return GetAllAfflictions(a => a.Prefab.AfflictionType == afflictionType); - } - else - { - return afflictions.Where(a => a.Prefab.AfflictionType == afflictionType); - } - } - - public IEnumerable GetAfflictionsByType(string afflictionType, Limb limb) - { - if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) - { - DebugConsole.ThrowError("Limb health index out of bounds. Character\"" + Character.Name + - "\" only has health configured for" + limbHealths.Count + " limbs but the limb " + limb.type + " is targeting index " + limb.HealthIndex); - return null; - } - return limbHealths[limb.HealthIndex].Afflictions.Where(a => a.Prefab.AfflictionType == afflictionType); - } - public Affliction GetAffliction(string identifier, bool allowLimbAfflictions = true) => GetAffliction(a => a.Prefab.Identifier == identifier, allowLimbAfflictions); @@ -328,20 +289,10 @@ namespace Barotrauma private Affliction GetAffliction(Func predicate, bool allowLimbAfflictions = true) { - foreach (Affliction affliction in afflictions) + foreach (KeyValuePair kvp in afflictions) { - if (predicate(affliction)) { return affliction; } - } - if (!allowLimbAfflictions) - { - return null; - } - foreach (LimbHealth limbHealth in limbHealths) - { - foreach (Affliction affliction in limbHealth.Afflictions) - { - if (predicate(affliction)) { return affliction; } - } + if (!allowLimbAfflictions && kvp.Value != null) { continue; } + if (predicate(kvp.Key)) { return kvp.Key; } } return null; } @@ -359,19 +310,22 @@ namespace Barotrauma "\" only has health configured for" + limbHealths.Count + " limbs but the limb " + limb.type + " is targeting index " + limb.HealthIndex); return null; } - foreach (Affliction affliction in limbHealths[limb.HealthIndex].Afflictions) + foreach (KeyValuePair kvp in afflictions) { - if (affliction.Prefab.Identifier == identifier) return affliction; + if (limbHealths[limb.HealthIndex] == kvp.Value && kvp.Key.Prefab.Identifier == identifier) { return kvp.Key; } } return null; } public Limb GetAfflictionLimb(Affliction affliction) { - for (int i = 0; i < limbHealths.Count; i++) + foreach (KeyValuePair kvp in afflictions) { - if (!limbHealths[i].Afflictions.Contains(affliction)) continue; - return Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == i); + if (kvp.Key == affliction) + { + int limbHealthIndex = limbHealths.IndexOf(kvp.Value); + return Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealthIndex); + } } return null; } @@ -388,12 +342,17 @@ namespace Barotrauma if (requireLimbSpecific && limbHealths.Count == 1) { return 0.0f; } float strength = 0.0f; - foreach (Affliction affliction in limbHealths[limb.HealthIndex].Afflictions) + LimbHealth limbHealth = limbHealths[limb.HealthIndex]; + foreach (KeyValuePair kvp in afflictions) { - if (affliction.Strength < affliction.Prefab.ActivationThreshold) { continue; } - if (affliction.Prefab.AfflictionType == afflictionType) + if (kvp.Value == limbHealth) { - strength += affliction.Strength; + Affliction affliction = kvp.Key; + if (affliction.Strength < affliction.Prefab.ActivationThreshold) { continue; } + if (affliction.Prefab.AfflictionType == afflictionType) + { + strength += affliction.Strength; + } } } return strength; @@ -402,28 +361,16 @@ namespace Barotrauma public float GetAfflictionStrength(string afflictionType, bool allowLimbAfflictions = true) { float strength = 0.0f; - foreach (Affliction affliction in afflictions) + foreach (KeyValuePair kvp in afflictions) { + if (!allowLimbAfflictions && kvp.Value != null) { continue; } + var affliction = kvp.Key; if (affliction.Strength < affliction.Prefab.ActivationThreshold) { continue; } if (affliction.Prefab.AfflictionType == afflictionType) { strength += affliction.Strength; } } - if (!allowLimbAfflictions) { return strength; } - - foreach (LimbHealth limbHealth in limbHealths) - { - foreach (Affliction affliction in limbHealth.Afflictions) - { - if (affliction.Strength < affliction.Prefab.ActivationThreshold) { continue; } - if (affliction.Prefab.AfflictionType == afflictionType) - { - strength += affliction.Strength; - } - } - } - return strength; } @@ -451,73 +398,77 @@ namespace Barotrauma } } - public float GetResistance(AfflictionPrefab affliction) + public float GetResistance(AfflictionPrefab afflictionPrefab) { float resistance = 0.0f; - for (int i = 0; i < afflictions.Count; i++) + foreach (KeyValuePair kvp in afflictions) { - resistance += afflictions[i].GetResistance(affliction); + var affliction = kvp.Key; + resistance += affliction.GetResistance(afflictionPrefab); } - return 1 - ((1 - resistance) * Character.GetAbilityResistance(affliction)); + return 1 - ((1 - resistance) * Character.GetAbilityResistance(afflictionPrefab)); } public float GetStatValue(StatTypes statType) { float value = 0f; - for (int i = 0; i < afflictions.Count; i++) + foreach (KeyValuePair kvp in afflictions) { - value += afflictions[i].GetStatValue(statType); + var affliction = kvp.Key; + value += affliction.GetStatValue(statType); } return value; } public bool HasFlag(AbilityFlags flagType) { - for (int i = 0; i < afflictions.Count; i++) + foreach (KeyValuePair kvp in afflictions) { - if (afflictions[i].HasFlag(flagType)) { return true; } + var affliction = kvp.Key; + if (affliction.HasFlag(flagType)) { return true; } } return false; } private readonly List matchingAfflictions = new List(); - public void ReduceAffliction(Limb targetLimb, string affliction, float amount, ActionType? treatmentAction = null) + public void ReduceAffliction(Limb targetLimb, string afflictionIdentifier, float amount, ActionType? treatmentAction = null) { matchingAfflictions.Clear(); - matchingAfflictions.AddRange(afflictions); - if (targetLimb != null) + + if (targetLimb == null) { - matchingAfflictions.AddRange(limbHealths[targetLimb.HealthIndex].Afflictions); + matchingAfflictions.AddRange(afflictions.Keys); } else { - foreach (LimbHealth limbHealth in limbHealths) + foreach (KeyValuePair kvp in afflictions) { - matchingAfflictions.AddRange(limbHealth.Afflictions); + var affliction = kvp.Key; + if (kvp.Value == null) + { + matchingAfflictions.Add(affliction); + } + else if (limbHealths[targetLimb.HealthIndex] == kvp.Value) + { + matchingAfflictions.Add(affliction); + } } } - if (!string.IsNullOrEmpty(affliction)) + if (!string.IsNullOrEmpty(afflictionIdentifier)) { matchingAfflictions.RemoveAll(a => - !a.Prefab.Identifier.Equals(affliction, StringComparison.OrdinalIgnoreCase) && - !a.Prefab.AfflictionType.Equals(affliction, StringComparison.OrdinalIgnoreCase)); + !a.Prefab.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase) && + !a.Prefab.AfflictionType.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase)); } - if (matchingAfflictions.Count == 0) return; + if (matchingAfflictions.Count == 0) { return; } float reduceAmount = amount / matchingAfflictions.Count; for (int i = matchingAfflictions.Count - 1; i >= 0; i--) { var matchingAffliction = matchingAfflictions[i]; - // this logic runs very often, so culling unnecessary object creation and talent checking with this method - if (Character.HasTalents()) - { - var afflictionReduction = new AbilityValueAffliction(reduceAmount, matchingAffliction); - Character.CheckTalents(AbilityEffectType.OnReduceAffliction, afflictionReduction); - } - if (matchingAffliction.Strength < reduceAmount) { float surplus = reduceAmount - matchingAffliction.Strength; @@ -570,20 +521,28 @@ namespace Barotrauma } } } - + + private readonly static List afflictionsToRemove = new List(); + private readonly static List> afflictionsToUpdate = new List>(); public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount) { if (Unkillable || Character.GodMode) { return; } - foreach (LimbHealth limbHealth in limbHealths) - { - limbHealth.Afflictions.RemoveAll(a => + + afflictionsToRemove.Clear(); + afflictionsToRemove.AddRange(afflictions.Keys.Where(a => a.Prefab.AfflictionType == AfflictionPrefab.InternalDamage.AfflictionType || a.Prefab.AfflictionType == AfflictionPrefab.Burn.AfflictionType || - a.Prefab.AfflictionType == AfflictionPrefab.Bleeding.AfflictionType); + a.Prefab.AfflictionType == AfflictionPrefab.Bleeding.AfflictionType)); + foreach (var affliction in afflictionsToRemove) + { + afflictions.Remove(affliction); + } - if (damageAmount > 0.0f) limbHealth.Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damageAmount)); - if (bleedingDamageAmount > 0.0f && DoesBleed) limbHealth.Afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamageAmount)); - if (burnDamageAmount > 0.0f) limbHealth.Afflictions.Add(AfflictionPrefab.Burn.Instantiate(burnDamageAmount)); + foreach (LimbHealth limbHealth in limbHealths) + { + if (damageAmount > 0.0f) { afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damageAmount), limbHealth); } + if (bleedingDamageAmount > 0.0f && DoesBleed) { afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamageAmount), limbHealth); } + if (burnDamageAmount > 0.0f) { afflictions.Add(AfflictionPrefab.Burn.Instantiate(burnDamageAmount), limbHealth); } } CalculateVitality(); @@ -620,12 +579,12 @@ namespace Barotrauma public void RemoveAllAfflictions() { - foreach (LimbHealth limbHealth in limbHealths) + afflictionsToRemove.Clear(); + afflictionsToRemove.AddRange(afflictions.Keys.Where(a => !irremovableAfflictions.Contains(a))); + foreach (var affliction in afflictionsToRemove) { - limbHealth.Afflictions.Clear(); + afflictions.Remove(affliction); } - - afflictions.RemoveAll(a => !irremovableAfflictions.Contains(a)); foreach (Affliction affliction in irremovableAfflictions) { affliction.Strength = 0.0f; @@ -635,13 +594,16 @@ namespace Barotrauma public void RemoveNegativeAfflictions() { - // also don't remove genetic effects, even if they're negative - foreach (LimbHealth limbHealth in limbHealths) + afflictionsToRemove.Clear(); + afflictionsToRemove.AddRange(afflictions.Keys.Where(a => + !irremovableAfflictions.Contains(a) && + !a.Prefab.IsBuff && + a.Prefab.AfflictionType != "geneticmaterialbuff" && + a.Prefab.AfflictionType != "geneticmaterialdebuff")); + foreach (var affliction in afflictionsToRemove) { - limbHealth.Afflictions.RemoveAll(a => !a.Prefab.IsBuff && a.Prefab.AfflictionType != "geneticmaterialbuff" && a.Prefab.AfflictionType != "geneticmaterialdebuff"); + afflictions.Remove(affliction); } - - afflictions.RemoveAll(a => !irremovableAfflictions.Contains(a) && !a.Prefab.IsBuff && a.Prefab.AfflictionType != "geneticmaterialbuff" && a.Prefab.AfflictionType != "geneticmaterialdebuff"); foreach (Affliction affliction in irremovableAfflictions) { affliction.Strength = 0.0f; @@ -665,36 +627,52 @@ namespace Barotrauma { if (!DoesBleed && newAffliction is AfflictionBleeding) { return; } if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; } - - foreach (Affliction affliction in limbHealth.Afflictions) + if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun") { return; } + if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab) { - if (newAffliction.Prefab == affliction.Prefab) + if (huskPrefab.TargetSpecies.None(s => s.Equals(Character.SpeciesName, StringComparison.OrdinalIgnoreCase))) { - float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab)); - if (allowStacking) - { - // Add the existing strength - newStrength += affliction.Strength; - } - newStrength = Math.Min(affliction.Prefab.MaxStrength, newStrength); - if (affliction == stunAffliction) { Character.SetStun(newStrength, true, true); } - affliction.Strength = newStrength; - affliction.Source = newAffliction.Source; - CalculateVitality(); - if (Vitality <= MinVitality) - { - Kill(); - } return; } } + Affliction existingAffliction = null; + foreach (KeyValuePair kvp in afflictions) + { + var affliction = kvp.Key; + if (kvp.Value == limbHealth && kvp.Key.Prefab == newAffliction.Prefab) + { + existingAffliction = kvp.Key; + break; + } + } + + if (existingAffliction != null) + { + float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(existingAffliction.Prefab)); + if (allowStacking) + { + // Add the existing strength + newStrength += existingAffliction.Strength; + } + newStrength = Math.Min(existingAffliction.Prefab.MaxStrength, newStrength); + if (existingAffliction == stunAffliction) { Character.SetStun(newStrength, true, true); } + existingAffliction.Strength = newStrength; + existingAffliction.Source = newAffliction.Source; + CalculateVitality(); + if (Vitality <= MinVitality) + { + Kill(); + } + return; + } + //create a new instance of the affliction to make sure we don't use the same instance for multiple characters //or modify the affliction instance of an Attack or a StatusEffect var copyAffliction = newAffliction.Prefab.Instantiate( Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))), newAffliction.Source); - limbHealth.Afflictions.Add(copyAffliction); + afflictions.Add(copyAffliction, limbHealth); Character.HealthUpdateInterval = 0.0f; @@ -704,7 +682,7 @@ namespace Barotrauma Kill(); } #if CLIENT - if (CharacterHealth.OpenHealthWindow != this) + if (OpenHealthWindow != this && limbHealth != null) { selectedLimbIndex = -1; } @@ -713,52 +691,7 @@ namespace Barotrauma private void AddAffliction(Affliction newAffliction, bool allowStacking = true) { - if (!DoesBleed && newAffliction is AfflictionBleeding) { return; } - if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun") { return; } - if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; } - if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab) - { - if (huskPrefab.TargetSpecies.None(s => s.Equals(Character.SpeciesName, StringComparison.OrdinalIgnoreCase))) - { - return; - } - } - foreach (Affliction affliction in afflictions) - { - if (newAffliction.Prefab == affliction.Prefab) - { - float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab)); - if (allowStacking) - { - // Add the existing strength - newStrength += affliction.Strength; - } - newStrength = Math.Min(affliction.Prefab.MaxStrength, newStrength); - if (affliction == stunAffliction) { Character.SetStun(newStrength, true, true); } - affliction.Strength = newStrength; - affliction.Source = newAffliction.Source; - CalculateVitality(); - if (Vitality <= MinVitality) - { - Kill(); - } - return; - } - } - - //create a new instance of the affliction to make sure we don't use the same instance for multiple characters - //or modify the affliction instance of an Attack or a StatusEffect - afflictions.Add(newAffliction.Prefab.Instantiate( - Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))), - source: newAffliction.Source)); - - Character.HealthUpdateInterval = 0.0f; - - CalculateVitality(); - if (Vitality <= MinVitality) - { - Kill(); - } + AddLimbAffliction(limbHealth: null, newAffliction, allowStacking); } partial void UpdateLimbAfflictionOverlays(); @@ -771,52 +704,44 @@ namespace Barotrauma if (Character.GodMode) { return; } - for (int i = 0; i < limbHealths.Count; i++) + afflictionsToRemove.Clear(); + afflictionsToUpdate.Clear(); + foreach (KeyValuePair kvp in afflictions) { - for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) - { - if (limbHealths[i].Afflictions[j].Strength <= 0.0f) - { - SteamAchievementManager.OnAfflictionRemoved(limbHealths[i].Afflictions[j], Character); - limbHealths[i].Afflictions.RemoveAt(j); - } - } - for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) - { - var affliction = limbHealths[i].Afflictions[j]; - Limb targetLimb = Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == i); - if (targetLimb == null) - { - targetLimb = Character.AnimController.MainLimb; - } - affliction.Update(this, targetLimb, deltaTime); - affliction.DamagePerSecondTimer += deltaTime; - if (affliction is AfflictionBleeding bleeding) - { - UpdateBleedingProjSpecific(bleeding, targetLimb, deltaTime); - } - Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); - } - } - - for (int i = afflictions.Count - 1; i >= 0; i--) - { - var affliction = afflictions[i]; - if (irremovableAfflictions.Contains(affliction)) { continue; } + var affliction = kvp.Key; if (affliction.Strength <= 0.0f) { SteamAchievementManager.OnAfflictionRemoved(affliction, Character); - afflictions.RemoveAt(i); + if (!irremovableAfflictions.Contains(affliction)) { afflictionsToRemove.Add(affliction); } + continue; } + afflictionsToUpdate.Add(kvp); } - for (int i = 0; i < afflictions.Count; i++) + foreach (KeyValuePair kvp in afflictionsToUpdate) { - var affliction = afflictions[i]; - affliction.Update(this, null, deltaTime); + var affliction = kvp.Key; + Limb targetLimb = null; + if (kvp.Value != null) + { + int healthIndex = limbHealths.IndexOf(kvp.Value); + targetLimb = + Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == healthIndex) ?? + Character.AnimController.MainLimb; + } + affliction.Update(this, targetLimb, deltaTime); affliction.DamagePerSecondTimer += deltaTime; + if (affliction is AfflictionBleeding bleeding) + { + UpdateBleedingProjSpecific(bleeding, targetLimb, deltaTime); + } Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); } + foreach (var affliction in afflictionsToRemove) + { + afflictions.Remove(affliction); + } + Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.MovementSpeed)); // maybe a bit of a hacky way to do this. should inquire if there is a better way. M61T @@ -846,20 +771,9 @@ namespace Barotrauma if (!(Character?.Params?.Health.ApplyAfflictionColors ?? false)) { return; } - for (int i = 0; i < limbHealths.Count; i++) + foreach (KeyValuePair kvp in afflictions) { - for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) - { - var affliction = limbHealths[i].Afflictions[j]; - Color faceTint = affliction.GetFaceTint(); - if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } - Color bodyTint = affliction.GetBodyTint(); - if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; } - } - } - for (int i = 0; i < afflictions.Count; i++) - { - var affliction = afflictions[i]; + var affliction = kvp.Key; Color faceTint = affliction.GetFaceTint(); if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } Color bodyTint = affliction.GetBodyTint(); @@ -896,7 +810,7 @@ namespace Barotrauma public void SetVitality(float newVitality) { - maxVitality = newVitality; + UnmodifiedMaxVitality = newVitality; CalculateVitality(); } @@ -905,29 +819,22 @@ namespace Barotrauma Vitality = MaxVitality; if (Unkillable || Character.GodMode) { return; } - foreach (LimbHealth limbHealth in limbHealths) - { - foreach (Affliction affliction in limbHealth.Afflictions) - { - float vitalityDecrease = affliction.GetVitalityDecrease(this); - string identifier = affliction.Prefab.Identifier.ToLowerInvariant(); - string type = affliction.Prefab.AfflictionType.ToLowerInvariant(); - if (limbHealth.VitalityMultipliers.ContainsKey(identifier)) - { - vitalityDecrease *= limbHealth.VitalityMultipliers[identifier]; - } - if (limbHealth.VitalityTypeMultipliers.ContainsKey(type)) - { - vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type]; - } - Vitality -= vitalityDecrease; - affliction.CalculateDamagePerSecond(vitalityDecrease); - } - } - - foreach (Affliction affliction in afflictions) + foreach (KeyValuePair kvp in afflictions) { + var affliction = kvp.Key; + var limbHealth = kvp.Value; float vitalityDecrease = affliction.GetVitalityDecrease(this); + if (limbHealth != null) + { + if (limbHealth.VitalityMultipliers.ContainsKey(affliction.Prefab.Identifier)) + { + vitalityDecrease *= limbHealth.VitalityMultipliers[affliction.Prefab.Identifier]; + } + if (limbHealth.VitalityTypeMultipliers.ContainsKey(affliction.Prefab.AfflictionType)) + { + vitalityDecrease *= limbHealth.VitalityTypeMultipliers[affliction.Prefab.AfflictionType]; + } + } Vitality -= vitalityDecrease; affliction.CalculateDamagePerSecond(vitalityDecrease); } @@ -955,27 +862,28 @@ namespace Barotrauma // We need to use another list of the afflictions when we call the status effects triggered by afflictions, // because those status effects may add or remove other afflictions while iterating the collection. - private readonly List afflictionsCopy = new List(); + private readonly List<(Affliction affliction, Limb limb)> afflictionsCopy = new List<(Affliction affliction, Limb limb)>(); public void ApplyAfflictionStatusEffects(ActionType type) { - for (int i = 0; i < limbHealths.Count; i++) - { - for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) - { - var affliction = limbHealths[i].Afflictions[j]; - Limb targetLimb = Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == i); - if (targetLimb == null) - { - targetLimb = Character.AnimController.MainLimb; - } - affliction.ApplyStatusEffects(type, 1.0f, this, targetLimb); - } - } afflictionsCopy.Clear(); - afflictionsCopy.AddRange(afflictions); - for (int i = afflictionsCopy.Count - 1; i >= 0; i--) + foreach (KeyValuePair kvp in afflictions) { - afflictionsCopy[i].ApplyStatusEffects(type, 1.0f, this, targetLimb: null); + var affliction = kvp.Key; + var limbHealth = kvp.Value; + Limb targetLimb = null; + if (limbHealth != null) + { + int healthIndex = limbHealths.IndexOf(limbHealth); + targetLimb = + Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == healthIndex) ?? + Character.AnimController.MainLimb; + } + afflictionsCopy.Add((affliction, GetAfflictionLimb(affliction))); + } + + foreach ((Affliction affliction, Limb limb) in afflictionsCopy) + { + affliction.ApplyStatusEffects(type, 1.0f, this, targetLimb: limb); } } @@ -1003,28 +911,26 @@ namespace Barotrauma return (causeOfDeath, strongestAffliction); } - // TODO: this method is called a lot (every half second) -> optimize, don't create new class instances and lists every time! + private readonly List allAfflictions = new List(); private List GetAllAfflictions(bool mergeSameAfflictions) { - List allAfflictions = new List(afflictions); - foreach (LimbHealth limbHealth in limbHealths) + allAfflictions.Clear(); + if (!mergeSameAfflictions) { - allAfflictions.AddRange(limbHealth.Afflictions); + allAfflictions.AddRange(afflictions.Keys); } - - if (mergeSameAfflictions) + else { - List mergedAfflictions = new List(); - foreach (Affliction affliction in allAfflictions) + foreach (Affliction affliction in afflictions.Keys) { - var existingAffliction = mergedAfflictions.Find(a => a.Prefab == affliction.Prefab); + var existingAffliction = allAfflictions.Find(a => a.Prefab == affliction.Prefab); if (existingAffliction == null) { var newAffliction = affliction.Prefab.Instantiate(affliction.Strength); if (affliction.Source != null) { newAffliction.Source = affliction.Source; } newAffliction.DamagePerSecond = affliction.DamagePerSecond; newAffliction.DamagePerSecondTimer = affliction.DamagePerSecondTimer; - mergedAfflictions.Add(newAffliction); + allAfflictions.Add(newAffliction); } else { @@ -1032,10 +938,7 @@ namespace Barotrauma existingAffliction.Strength += affliction.Strength; } } - - return mergedAfflictions; } - return allAfflictions; } @@ -1044,27 +947,46 @@ namespace Barotrauma /// and negative treatment suitabilities (e.g. a medicine that causes oxygen loss may not be suitable if the character is already suffocating) /// /// A dictionary where the key is the identifier of the item and the value the suitability - /// If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable. - /// Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random) - public void GetSuitableTreatments(Dictionary treatmentSuitability, bool normalize, Limb limb = null, bool ignoreHiddenAfflictions = false, float randomization = 0.0f) + /// If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable. + /// If above 0, the method will take into account how much currently active status effects while affect the afflictions in the next x seconds. + public void GetSuitableTreatments(Dictionary treatmentSuitability, bool normalize, Limb limb = null, bool ignoreHiddenAfflictions = false, float predictFutureDuration = 0.0f) { //key = item identifier //float = suitability treatmentSuitability.Clear(); float minSuitability = -10, maxSuitability = 10; - foreach (Affliction affliction in getAfflictions(limb)) + foreach (KeyValuePair kvp in afflictions) { - if (affliction.Strength <= affliction.Prefab.TreatmentThreshold) { continue; } - if (ignoreHiddenAfflictions && affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; } + var affliction = kvp.Key; + var limbHealth = kvp.Value; + if (limb != null) + { + if (limbHealth == null) { continue; } + int healthIndex = limbHealths.IndexOf(limbHealth); + Limb targetLimb = + Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == healthIndex) ?? + Character.AnimController.MainLimb; + if (limb != targetLimb) { continue; } + } + + float strength = affliction.Strength; + if (predictFutureDuration > 0.0f) + { + strength = GetPredictedStrength(affliction, predictFutureDuration, limb); + } + + if (strength <= affliction.Prefab.TreatmentThreshold) { continue; } + if (ignoreHiddenAfflictions && strength < affliction.Prefab.ShowIconThreshold) { continue; } + foreach (KeyValuePair treatment in affliction.Prefab.TreatmentSuitability) { if (!treatmentSuitability.ContainsKey(treatment.Key)) { - treatmentSuitability[treatment.Key] = treatment.Value * affliction.Strength; + treatmentSuitability[treatment.Key] = treatment.Value * strength; } else { - treatmentSuitability[treatment.Key] += treatment.Value * affliction.Strength; + treatmentSuitability[treatment.Key] += treatment.Value * strength; } minSuitability = Math.Min(treatmentSuitability[treatment.Key], minSuitability); maxSuitability = Math.Max(treatmentSuitability[treatment.Key], maxSuitability); @@ -1076,28 +998,51 @@ namespace Barotrauma foreach (string treatment in treatmentSuitability.Keys.ToList()) { treatmentSuitability[treatment] = (treatmentSuitability[treatment] - minSuitability) / (maxSuitability - minSuitability); - treatmentSuitability[treatment] = MathHelper.Lerp(treatmentSuitability[treatment], Rand.Range(0.0f, 1.0f), randomization); - } - } - else - { - foreach (string treatment in treatmentSuitability.Keys.ToList()) - { - treatmentSuitability[treatment] += Rand.Range(-100.0f, 100.0f) * randomization; } } + } - IEnumerable getAfflictions(Limb limb) + public IEnumerable GetActiveAfflictionTags() => GetActiveAfflictionTags(afflictions.Keys); + + private readonly HashSet afflictionTags = new HashSet(); + public IEnumerable GetActiveAfflictionTags(IEnumerable afflictions) + { + afflictionTags.Clear(); + foreach (Affliction affliction in afflictions) { - if (limb == null) + var currentEffect = affliction.GetActiveEffect(); + if (currentEffect != null && !string.IsNullOrEmpty(currentEffect.Tag)) { - return GetAllAfflictions(); - } - else - { - return GetMatchingAfflictions(GetMatchingLimbHealth(limb)); + afflictionTags.Add(currentEffect.Tag); } } + return afflictionTags; + } + + public float GetPredictedStrength(Affliction affliction, float predictFutureDuration, Limb limb = null) + { + float strength = affliction.Strength; + foreach (var statusEffect in StatusEffect.DurationList) + { + if (!statusEffect.Targets.Any(t => t == Character || (limb != null && Character.AnimController.Limbs.Contains(t)))) { continue; } + float statusEffectDuration = Math.Min(statusEffect.Timer, predictFutureDuration); + foreach (var statusEffectAffliction in statusEffect.Parent.Afflictions) + { + if (statusEffectAffliction.Prefab == affliction.Prefab) + { + strength += statusEffectAffliction.Strength * statusEffectDuration; + } + } + foreach (var statusEffectAffliction in statusEffect.Parent.ReduceAffliction) + { + if (statusEffectAffliction.affliction.Equals(affliction.Identifier, StringComparison.OrdinalIgnoreCase) || + statusEffectAffliction.affliction.Equals(affliction.Prefab.AfflictionType, StringComparison.OrdinalIgnoreCase)) + { + strength -= statusEffectAffliction.amount * statusEffectDuration; + } + } + } + return MathHelper.Clamp(strength, 0.0f, affliction.Prefab.MaxStrength); } private readonly List activeAfflictions = new List(); @@ -1105,8 +1050,11 @@ namespace Barotrauma public void ServerWrite(IWriteMessage msg) { activeAfflictions.Clear(); - foreach (var affliction in afflictions) + foreach (KeyValuePair kvp in afflictions) { + var affliction = kvp.Key; + var limbHealth = kvp.Value; + if (limbHealth != null) { continue; } if (affliction.Strength > 0.0f && affliction.Strength >= affliction.Prefab.ActivationThreshold) { activeAfflictions.Add(affliction); @@ -1127,13 +1075,13 @@ namespace Barotrauma } limbAfflictions.Clear(); - foreach (LimbHealth limbHealth in limbHealths) + foreach (KeyValuePair kvp in afflictions) { - foreach (Affliction limbAffliction in limbHealth.Afflictions) - { - if (limbAffliction.Strength <= 0.0f || limbAffliction.Strength < limbAffliction.Prefab.ActivationThreshold) continue; - limbAfflictions.Add((limbHealth, limbAffliction)); - } + var limbAffliction = kvp.Key; + var limbHealth = kvp.Value; + if (limbHealth == null) { continue; } + if (limbAffliction.Strength <= 0.0f || limbAffliction.Strength < limbAffliction.Prefab.ActivationThreshold) { continue; } + limbAfflictions.Add((limbHealth, limbAffliction)); } msg.Write((byte)limbAfflictions.Count); @@ -1167,19 +1115,24 @@ namespace Barotrauma public void Save(XElement healthElement) { - foreach (Affliction affliction in afflictions) + foreach (KeyValuePair kvp in afflictions) { - if (affliction.Strength <= 0.0f) { continue; } + var affliction = kvp.Key; + var limbHealth = kvp.Value; + if (affliction.Strength <= 0.0f || limbHealth != null) { continue; } healthElement.Add(new XElement("Affliction", new XAttribute("identifier", affliction.Identifier), new XAttribute("strength", affliction.Strength.ToString("G", CultureInfo.InvariantCulture)))); } + for (int i = 0; i < limbHealths.Count; i++) { var limbHealthElement = new XElement("LimbHealth", new XAttribute("i", i)); healthElement.Add(limbHealthElement); - foreach (Affliction affliction in limbHealths[i].Afflictions) + foreach (KeyValuePair kvp in afflictions.Where(a => a.Value == limbHealths[i])) { + var affliction = kvp.Key; + var limbHealth = kvp.Value; if (affliction.Strength <= 0.0f) { continue; } limbHealthElement.Add(new XElement("Affliction", new XAttribute("identifier", affliction.Identifier), @@ -1227,13 +1180,9 @@ namespace Barotrauma { irremovableAffliction.Strength = strength; } - else if (limbHealth != null) - { - limbHealth.Afflictions.Add(afflictionPrefab.Instantiate(strength)); - } else { - afflictions.Add(afflictionPrefab.Instantiate(strength)); + afflictions.Add(afflictionPrefab.Instantiate(strength), limbHealth); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs index 0c7fb8a78..47a66ff4f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -194,7 +194,7 @@ namespace Barotrauma { string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created."; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item); GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs index 04c7a3acd..7e11a575b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs @@ -147,7 +147,7 @@ namespace Barotrauma { string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created."; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item); GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index 8f5eb2299..6d7a8f929 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -364,7 +364,7 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace()); #endif - GameAnalyticsManager.AddErrorEventOnce("Limb.LinearVelocity:SimPosition", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.AddErrorEventOnce("Limb.LinearVelocity:SimPosition", GameAnalyticsManager.ErrorSeverity.Error, "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace()); return Vector2.Zero; } @@ -381,7 +381,7 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace()); #endif - GameAnalyticsManager.AddErrorEventOnce("Limb.LinearVelocity:SimPosition", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.AddErrorEventOnce("Limb.LinearVelocity:SimPosition", GameAnalyticsManager.ErrorSeverity.Error, "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace()); return 0.0f; } @@ -401,7 +401,7 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace()); #endif - GameAnalyticsManager.AddErrorEventOnce("Limb.Mass:AccessRemoved", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.AddErrorEventOnce("Limb.Mass:AccessRemoved", GameAnalyticsManager.ErrorSeverity.Error, "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace()); return 1.0f; } @@ -420,7 +420,7 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace()); #endif - GameAnalyticsManager.AddErrorEventOnce("Limb.LinearVelocity:AccessRemoved", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.AddErrorEventOnce("Limb.LinearVelocity:AccessRemoved", GameAnalyticsManager.ErrorSeverity.Error, "Attempted to access a removed limb.\n" + Environment.StackTrace.CleanupStackTrace()); return Vector2.Zero; } @@ -471,7 +471,7 @@ namespace Barotrauma if (!MathUtils.IsValid(value)) { string errorMsg = "Attempted to set the anchor A of a limb's pull joint to an invalid value (" + value + ")\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorA:InvalidValue", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorA:InvalidValue", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #endif @@ -485,7 +485,7 @@ namespace Barotrauma ", limb enabled: " + body.Enabled + ", simple physics enabled: " + character.AnimController.SimplePhysicsEnabled + ")\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorA:ExcessiveValue", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorA:ExcessiveValue", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #endif @@ -504,7 +504,7 @@ namespace Barotrauma if (!MathUtils.IsValid(value)) { string errorMsg = "Attempted to set the anchor B of a limb's pull joint to an invalid value (" + value + ")\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorB:InvalidValue", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorB:InvalidValue", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #endif @@ -518,7 +518,7 @@ namespace Barotrauma ", limb enabled: " + body.Enabled + ", simple physics enabled: " + character.AnimController.SimplePhysicsEnabled + ")\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorB:ExcessiveValue", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorB:ExcessiveValue", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index be1df08e5..4d03cd6fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs @@ -656,6 +656,34 @@ namespace Barotrauma return target != null; } + public bool TryGetTarget(Character targetCharacter, out TargetParams target) + { + if (!TryGetTarget(targetCharacter.SpeciesName, out target)) + { + target = targets.FirstOrDefault(t => string.Equals(t.Tag, targetCharacter.Params.Group.ToString(), StringComparison.OrdinalIgnoreCase)); + } + return target != null; + } + + public bool TryGetTarget(IEnumerable tags, out TargetParams target) + { + target = null; + if (tags == null || tags.None()) { return false; } + float priority = -1; + foreach (var potentialTarget in targets) + { + if (potentialTarget.Priority > priority) + { + if (tags.Any(t => string.Equals(t, potentialTarget.Tag, StringComparison.OrdinalIgnoreCase))) + { + target = potentialTarget; + priority = target.Priority; + } + } + } + return target != null; + } + public TargetParams GetTarget(string targetTag, bool throwError = true) { if (!TryGetTarget(targetTag, out TargetParams target)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs index 1112c063a..055084147 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs @@ -1,4 +1,5 @@ using Barotrauma.Items.Components; +using System; using System.Linq; using System.Xml.Linq; @@ -10,26 +11,25 @@ namespace Barotrauma.Abilities { Any = 0, Melee = 1, - Ranged = 2 + Ranged = 2, + HandheldRanged = 3, + Turret = 4 }; private readonly string itemIdentifier; private readonly string[] tags; private readonly WeaponType weapontype; - private bool ignoreNonHarmfulAttacks; + private readonly bool ignoreNonHarmfulAttacks; public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { itemIdentifier = conditionElement.GetAttributeString("itemidentifier", ""); tags = conditionElement.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true); ignoreNonHarmfulAttacks = conditionElement.GetAttributeBool("ignorenonharmfulattacks", false); - switch (conditionElement.GetAttributeString("weapontype", "")) + + string weaponTypeStr = conditionElement.GetAttributeString("weapontype", "Any"); + if (!Enum.TryParse(weaponTypeStr, ignoreCase: true, out weapontype)) { - case "melee": - weapontype = WeaponType.Melee; - break; - case "ranged": - weapontype = WeaponType.Ranged; - break; + DebugConsole.ThrowError($"Error in talent \"{characterTalent.DebugIdentifier}\": \"{weaponTypeStr}\" is not a valid weapon type."); } } @@ -77,6 +77,16 @@ namespace Barotrauma.Abilities return item.GetComponent() != null; case WeaponType.Ranged: return item.GetComponent() != null; + case WeaponType.HandheldRanged: + { + var projectile = item.GetComponent(); + return projectile?.Launcher?.GetComponent() != null; + } + case WeaponType.Turret: + { + var projectile = item.GetComponent(); + return projectile?.Launcher?.GetComponent() != null; + } } return true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionStatusEffectIdentifier.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionStatusEffectIdentifier.cs new file mode 100644 index 000000000..6be5969f8 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionStatusEffectIdentifier.cs @@ -0,0 +1,28 @@ +using System.Xml.Linq; +using static Barotrauma.StatusEffect; + +namespace Barotrauma.Abilities +{ + class AbilityConditionStatusEffectIdentifier : AbilityConditionData + { + private string effectIdentifier; + + public AbilityConditionStatusEffectIdentifier(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + effectIdentifier = conditionElement.GetAttributeString("effectidentifier", "").ToLowerInvariant(); + } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if (abilityObject is AbilityStatusEffectIdentifier abilityStatusEffectIdentifier) + { + return abilityStatusEffectIdentifier.EffectIdentifier == effectIdentifier; + } + else + { + LogAbilityConditionError(abilityObject, typeof(AbilityStatusEffectIdentifier)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index 6fb4887b3..0998bf475 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -26,7 +26,7 @@ namespace Barotrauma.Abilities value = abilityElement.GetAttributeFloat("value", 0f); maxValue = abilityElement.GetAttributeFloat("maxvalue", float.MaxValue); targetAllies = abilityElement.GetAttributeBool("targetallies", false); - removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); + removeOnDeath = abilityElement.GetAttributeBool("removeondeath", false); giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None); setValue = abilityElement.GetAttributeBool("setvalue", false); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs index e80bf63db..70661cf4b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs @@ -1,6 +1,4 @@ -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Xml.Linq; +using System.Xml.Linq; namespace Barotrauma.Abilities { @@ -13,6 +11,10 @@ namespace Barotrauma.Abilities { itemIdentifier = abilityElement.GetAttributeString("itemidentifier", ""); amount = abilityElement.GetAttributeInt("amount", 1); + if (string.IsNullOrEmpty(itemIdentifier)) + { + DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - itemIdentifier not defined."); + } } protected override void ApplyEffect() @@ -34,7 +36,13 @@ namespace Barotrauma.Abilities if (GameMain.GameSession?.RoundEnding ?? true) { Item item = new Item(itemPrefab, Character.WorldPosition, Character.Submarine); - Character.Inventory.TryPutItem(item, Character, new List() { InvSlotType.Any }); + if (!Character.Inventory.TryPutItem(item, Character)) + { + foreach (Item containedItem in Character.Inventory.AllItemsMod) + { + if (containedItem.OwnInventory?.TryPutItem(item, Character) ?? false) { break; } + } + } } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs index 75668b0ca..8d6c64ac5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs @@ -31,7 +31,7 @@ namespace Barotrauma.Abilities } } - if (closestCharacter.SelectedConstruction == null || !closestCharacter.SelectedConstruction.HasTag(tag)) { return; } + if (closestCharacter?.SelectedConstruction == null || !closestCharacter.SelectedConstruction.HasTag(tag)) { return; } if (closestDistance < squaredMaxDistance) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs index 193bf9c8f..492b857d9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs @@ -31,7 +31,6 @@ namespace Barotrauma ConfigElement = element; Identifier = element.GetAttributeString("identifier", "noidentifier"); DisplayName = TextManager.Get("talentname." + Identifier, returnNull: true) ?? Identifier; - this.CalculatePrefabUIntIdentifier(TalentPrefabs); foreach (XElement subElement in element.Elements()) { @@ -103,7 +102,9 @@ namespace Barotrauma void loadSinglePrefab(XElement element, bool isOverride) { - TalentPrefabs.Add(new TalentPrefab(element, file.Path) { ContentPackage = file.ContentPackage }, isOverride); + var newPrefab = new TalentPrefab(element, file.Path) { ContentPackage = file.ContentPackage }; + TalentPrefabs.Add(newPrefab, isOverride); + newPrefab.CalculatePrefabUIntIdentifier(TalentPrefabs); } void loadMultiplePrefabs(XElement element, bool isOverride) diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 56ab5b7f2..65cb463dd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -218,7 +218,7 @@ namespace Barotrauma try { #if CLIENT - SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), Character.Controlled, out string errorMsg); + SpawnItem(args, Screen.Selected.Cam?.ScreenToWorld(PlayerInput.MousePosition) ?? PlayerInput.MousePosition, Character.Controlled, out string errorMsg); #elif SERVER SpawnItem(args, Vector2.Zero, null, out string errorMsg); #endif @@ -231,7 +231,7 @@ namespace Barotrauma { string errorMsg = "Failed to spawn an item. Arguments: \"" + string.Join(" ", args) + "\"."; ThrowError(errorMsg, e); - GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnItem:Error", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + '\n' + e.Message + '\n' + e.StackTrace.CleanupStackTrace()); + GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnItem:Error", GameAnalyticsManager.ErrorSeverity.Error, errorMsg + '\n' + e.Message + '\n' + e.StackTrace.CleanupStackTrace()); } }, () => @@ -239,7 +239,10 @@ namespace Barotrauma List itemNames = new List(); foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs) { - itemNames.Add(itemPrefab.Name); + if (!itemNames.Contains(itemPrefab.Name)) + { + itemNames.Add(itemPrefab.Name); + } } List spawnPosParams = new List() { "cursor", "inventory" }; @@ -250,8 +253,8 @@ namespace Barotrauma return new string[][] { - itemNames.ToArray(), - spawnPosParams.ToArray() + itemNames.ToArray(), + spawnPosParams.ToArray() }; }, isCheat: true)); @@ -1205,7 +1208,7 @@ namespace Barotrauma catch (InvalidOperationException e) { string errorMsg = "Error while executing the fixhulls command.\n" + e.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("DebugConsole.FixHulls", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("DebugConsole.FixHulls", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } } }, null, true)); @@ -1871,7 +1874,7 @@ namespace Barotrauma ThrowError("Failed to execute command \"" + command + "\"!"); GameAnalyticsManager.AddErrorEventOnce( "DebugConsole.ExecuteCommand:LengthZero", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Failed to execute command \"" + command + "\"!"); return; } @@ -2160,7 +2163,7 @@ namespace Barotrauma { if (spawnPos != null) { - if (Entity.Spawner == null) + if (Entity.Spawner == null || Entity.Spawner.Removed) { new Item(itemPrefab, spawnPos.Value, null); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalManager.cs index 277efee1c..2d7792808 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalManager.cs @@ -74,24 +74,12 @@ namespace Barotrauma continue; } } - Prefabs.Add(new DecalPrefab(element, configFile), allowOverriding || sourceElement.IsOverride()); + var newPrefab = new DecalPrefab(element, configFile); + Prefabs.Add(newPrefab, allowOverriding || sourceElement.IsOverride()); + newPrefab.CalculatePrefabUIntIdentifier(Prefabs); break; } } - - using MD5 md5 = MD5.Create(); - foreach (DecalPrefab prefab in Prefabs) - { - prefab.UIntIdentifier = ToolBox.StringToUInt32Hash(prefab.Identifier, md5); - - //it's theoretically possible for two different values to generate the same hash, but the probability is astronomically small - var collision = Prefabs.Find(p => p != prefab && p.UIntIdentifier == prefab.UIntIdentifier); - if (collision != null) - { - DebugConsole.ThrowError("Hashing collision when generating uint identifiers for Decals: " + prefab.Identifier + " has the same identifier as " + collision.Identifier + " (" + prefab.UIntIdentifier + ")"); - collision.UIntIdentifier++; - } - } } public void RemoveByFile(string filePath) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalPrefab.cs index 3f80bbecd..bc0551501 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalPrefab.cs @@ -5,7 +5,7 @@ using System.Xml.Linq; namespace Barotrauma { - class DecalPrefab : IPrefab, IDisposable + class DecalPrefab : IPrefab, IHasUintIdentifier, IDisposable { public readonly string Name; @@ -21,7 +21,7 @@ namespace Barotrauma /// Unique identifier that's generated by hashing the prefab's string identifier. /// Used to reduce the amount of bytes needed to write decal data into network messages in multiplayer. /// - public uint UIntIdentifier; + public uint UIntIdentifier { get; set; } public string FilePath { get; private set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index 3ac2e2dbf..6bba12392 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -60,6 +60,7 @@ OnGainMissionMoney, OnLocationDiscovered, OnItemDeconstructed, + OnItemDeconstructedByAlly, OnItemDeconstructedMaterial, OnItemDeconstructedInventory, OnStopTinkering, @@ -68,6 +69,7 @@ OnCrewGeneticMaterialCombinedOrRefined, AfterSubmarineAttacked, OnApplyTreatment, + OnStatusEffectIdentifier, } public enum StatTypes @@ -111,6 +113,7 @@ GeneticMaterialRefineBonus, GeneticMaterialTaintedProbabilityReductionOnCombine, SkillGainSpeed, + MedicalItemApplyingMultiplier, // Tinker TinkeringDuration, TinkeringStrength, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ClearTagAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ClearTagAction.cs index f4b6d0f0a..73263a685 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ClearTagAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ClearTagAction.cs @@ -1,5 +1,4 @@ using System.Xml.Linq; -using NLog.Targets; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs index c96723441..a1a054a1c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs @@ -92,6 +92,11 @@ namespace Barotrauma ParentEvent.AddTargetPredicate(Tag, e => e is Item it && SubmarineTypeMatches(it.Submarine) && it.HasTag(tag)); } + private void TagHullsByName(string name) + { + ParentEvent.AddTargetPredicate(Tag, e => e is Hull h && SubmarineTypeMatches(h.Submarine) && h.RoomName.Contains(name, StringComparison.OrdinalIgnoreCase)); + } + private bool SubmarineTypeMatches(Submarine sub) { if (SubmarineType == SubType.Any) { return true; } @@ -144,6 +149,9 @@ namespace Barotrauma case "itemtag": if (kvp.Length > 1) { TagItemsByTag(kvp[1].Trim()); } break; + case "hullname": + if (kvp.Length > 1) { TagHullsByName(kvp[1].Trim()); } + break; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs index fea12aa2a..c75d1ea35 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs @@ -1,5 +1,6 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; +using System.Collections.Generic; using System.Linq; using System.Xml.Linq; @@ -34,6 +35,9 @@ namespace Barotrauma [Serialize(false, true, description: "If true, one target must interact with the other to trigger the action.")] public bool WaitForInteraction { get; set; } + [Serialize(false, true, description: "If true, the action can be triggered by interacting with any matching target (not just the 1st one).")] + public bool AllowMultipleTargets { get; set; } + private float distance; public TriggerAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) @@ -55,7 +59,7 @@ namespace Barotrauma public bool isRunning = false; - private Either npcOrItem = null; + private readonly List> npcsOrItems = new List>(); public override void Update(float deltaTime) { @@ -93,12 +97,16 @@ namespace Barotrauma Character player = null; Character npc = null; Item item = null; - npcOrItem?.TryGet(out npc); - npcOrItem?.TryGet(out item); if (e1 is Character char1) { - if (char1.IsBot) { npc ??= char1; } - else { player = char1; } + if (char1.IsBot) + { + npc ??= char1; + } + else + { + player = char1; + } } else { @@ -106,8 +114,14 @@ namespace Barotrauma } if (e2 is Character char2) { - if (char2.IsBot) { npc ??= char2; } - else { player = char2; } + if (char2.IsBot) + { + npc ??= char2; + } + else + { + player = char2; + } } else { @@ -120,7 +134,10 @@ namespace Barotrauma { if (npc.CampaignInteractionType != CampaignMode.InteractionType.Examine) { - npcOrItem = npc; + if (!npcsOrItems.Any(n => n.TryGet(out Character npc2) && npc2 == npc)) + { + npcsOrItems.Add(npc); + } npc.CampaignInteractionType = CampaignMode.InteractionType.Examine; npc.RequireConsciousnessForCustomInteract = DisableIfTargetIncapacitated; #if CLIENT @@ -134,12 +151,14 @@ namespace Barotrauma GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction }); #endif } - - return; + if (!AllowMultipleTargets) { return; } } else if (item != null) { - npcOrItem = item; + if (!npcsOrItems.Any(n => n.TryGet(out Item item2) && item2 == item)) + { + npcsOrItems.Add(item); + } item.CampaignInteractionType = CampaignMode.InteractionType.Examine; if (player.SelectedConstruction == item || player.Inventory != null && player.Inventory.Contains(item) || @@ -170,19 +189,21 @@ namespace Barotrauma private void ResetTargetIcons() { - if (npcOrItem == null) { return; } - if (npcOrItem.TryGet(out Character npc)) + foreach (var npcOrItem in npcsOrItems) { - npc.CampaignInteractionType = CampaignMode.InteractionType.None; - npc.SetCustomInteract(null, null); - npc.RequireConsciousnessForCustomInteract = true; -#if SERVER - GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction }); -#endif - } - else if (npcOrItem.TryGet(out Item item)) - { - item.CampaignInteractionType = CampaignMode.InteractionType.None; + if (npcOrItem.TryGet(out Character npc)) + { + npc.CampaignInteractionType = CampaignMode.InteractionType.None; + npc.SetCustomInteract(null, null); + npc.RequireConsciousnessForCustomInteract = true; + #if SERVER + GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction }); + #endif + } + else if (npcOrItem.TryGet(out Item item)) + { + item.CampaignInteractionType = CampaignMode.InteractionType.None; + } } } @@ -269,7 +290,7 @@ namespace Barotrauma return $"{ToolBox.GetDebugSymbol(isFinished, isRunning)} {nameof(TriggerAction)} -> (" + (WaitForInteraction ? - $"Selected non-player target: {(npcOrItem?.ToString() ?? "").ColorizeObject()}, " : + $"Selected non-player target: {(npcsOrItems?.ToString() ?? "").ColorizeObject()}, " : $"Distance: {((int)distance).ColorizeObject()}, ") + $"Radius: {Radius.ColorizeObject()}, " + $"TargetTags: {Target1Tag.ColorizeObject()}, " + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 434b1c217..72ff7bdb5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -1,10 +1,9 @@ -using FarseerPhysics; +using Barotrauma.Extensions; +using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; -using Barotrauma.Extensions; -using NLog; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs index 65d851338..53bbb3732 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs @@ -162,24 +162,6 @@ namespace Barotrauma #endif } } - - //if any of the target items is a reactor, prevent exploding it from damaging the player's sub - foreach (Item item in items) - { - if (item.GetComponent() is Reactor reactor && (reactor.statusEffectLists?.ContainsKey(ActionType.OnBroken) ?? false)) - { - foreach (var statusEffect in reactor.statusEffectLists[ActionType.OnBroken]) - { - foreach (Explosion explosion in statusEffect.Explosions) - { - foreach (Submarine sub in Submarine.Loaded) - { - if (sub.TeamID == CharacterTeamType.Team1) { explosion.IgnoredSubmarines.Add(sub); } - } - } - } - } - } } private void InitCharacters(Submarine submarine) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs index 823a3149f..bc72b77cf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs @@ -187,5 +187,14 @@ namespace Barotrauma.Extensions } return retVal; } + + public static int FindIndex(this IReadOnlyList list, Predicate predicate) + { + for (int i=0; i + /// No attempt to contact the consent server has been made + /// + Unknown, + + /// + /// An error occurred while attempting to retrieve consent status + /// + Error, + + /// + /// The consent status was not saved on the remote database + /// + Ask, + + /// + /// The user explicitly denied consent + /// + No, + + /// + /// The user explicitly granted consent + /// + Yes + } + + public static Consent UserConsented { get; private set; } = Consent.Unknown; + + public static bool SendUserStatistics => UserConsented == Consent.Yes && loadedImplementation != null; + + private static bool consentTextAvailable + => TextManager.ContainsTag("statisticsconsentheader") + && TextManager.ContainsTag("statisticsconsenttext"); + + private readonly static string consentServerUrl = "https://barotraumagame.com/baromaster/"; + private readonly static string consentServerFile = "consentserver.php"; + + private static string GetAuthTicket() + { + Steamworks.AuthTicket authTicket = SteamManager.GetAuthSessionTicket(); + //convert byte array to hex + return BitConverter.ToString(authTicket.Data).Replace("-", ""); + } + + /// + /// Sets the consent status. This method cannot be called to + /// set the status to Consent.Yes; only a positive response from + /// the database or the user accepting via the privacy policy + /// prompt should enable it. + /// + public static void SetConsent(Consent consent) + { + if (consent == Consent.Yes) + { + throw new Exception( + "Cannot call SetConsent with value Consent.Yes, must only be set to this value via consent prompt"); + } + SetConsentInternal(consent); + } + + /// + /// Implementation of the bulk of SetConsent. + /// DO NOT CALL THIS UNLESS NEEDED. + /// + private static void SetConsentInternal(Consent consent) + { + if (UserConsented == consent) { return; } + + if (consent == Consent.Ask) + { + CreateConsentPrompt(); + } + + if (consent != Consent.No && consent != Consent.Yes) + { + UserConsented = consent; + ShutDown(); + return; + } + if (consent == Consent.No) + { + UserConsented = consent; + ShutDown(); + } + + string authTicketStr; + try + { + authTicketStr = GetAuthTicket(); + } + catch (Exception e) + { + DebugConsole.ThrowError("Error in GameAnalyticsManager.SetConsent. Could not get a Steam authentication ticket.", e); + return; + } + + RestClient client = null; + try + { + client = new RestClient(consentServerUrl); + } + catch (Exception e) + { + DebugConsole.ThrowError("Error while connecting to consent server", e); + } + if (client == null) { return; } + + var request = new RestRequest(consentServerFile, Method.GET); + request.AddParameter("authticket", authTicketStr); + request.AddParameter("action", "setconsent"); + request.AddParameter("consent", consent == Consent.Yes ? 1 : 0); + + var response = client.Execute(request, Method.GET); + if (CheckResponse(response)) + { + UserConsented = consent; + if (consent == Consent.Yes) + { + Init(); + } + } + } + + static partial void CreateConsentPrompt(); + + public static void InitIfConsented() + { + if (!consentTextAvailable) + { + SetConsent(Consent.Unknown); + return; + } + + static void error(string reason, Exception exception) + { + DebugConsole.ThrowError($"Error in GameAnalyticsManager.GetConsent: {reason}", exception); + SetConsent(Consent.Error); + } + + string authTicketStr; + try + { + authTicketStr = GetAuthTicket(); + } + catch (Exception e) + { + error("Could not get a Steam authentication ticket.", e); + return; + } + + RestClient client; + try + { + client = new RestClient(consentServerUrl); + } + catch (Exception e) + { + error("Error while connecting to consent server.", e); + return; + } + + var request = new RestRequest(consentServerFile, Method.GET); + request.AddParameter("authticket", authTicketStr); + request.AddParameter("action", "getconsent"); + + TaskPool.Add($"{nameof(GameAnalyticsManager)}.{nameof(InitIfConsented)}", client.ExecuteAsync(request), (t) => + { + if (t.Exception != null) + { + error("Error executing the request to the consent server.", t.Exception.InnerException); + return; + } + + var response = ((Task)t).Result; + if (!CheckResponse(response)) + { + SetConsent(Consent.Error); + } + else if (string.IsNullOrEmpty(response.Content)) + { + SetConsent(Consent.Ask); + } + else + { + SetConsentInternal(response.Content[0] == '1' + ? Consent.Yes + : Consent.No); + } + }); + } + + private static bool CheckResponse(IRestResponse response) + { + if (response.ErrorException != null) + { + DebugConsole.ThrowError(TextManager.GetWithVariable("MasterServerErrorException", "[error]", response.ErrorException.ToString())); + return false; + } + else if (response.StatusCode != HttpStatusCode.OK) + { + switch (response.StatusCode) + { + case HttpStatusCode.NotFound: + DebugConsole.ThrowError(TextManager.GetWithVariable("MasterServerError404", "[masterserverurl]", consentServerUrl)); + break; + case HttpStatusCode.ServiceUnavailable: + DebugConsole.ThrowError(TextManager.Get("MasterServerErrorUnavailable")); + break; + default: + DebugConsole.ThrowError(TextManager.GetWithVariables("MasterServerErrorDefault", new string[2] { "[statuscode]", "[statusdescription]" }, + new string[2] { response.StatusCode.ToString(), response.StatusDescription })); + break; + } + } + return response.StatusCode == HttpStatusCode.OK; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsManager.cs new file mode 100644 index 000000000..3e171d632 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsManager.cs @@ -0,0 +1,377 @@ +#nullable enable +using Barotrauma.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; + +namespace Barotrauma +{ + public static partial class GameAnalyticsManager + { + public enum ErrorSeverity + { + Undefined = 0, + Debug = 1, + Info = 2, + Warning = 3, + Error = 4, + Critical = 5 + } + + public enum ProgressionStatus + { + Undefined = 0, + Start = 1, + Complete = 2, + Fail = 3 + } + + private readonly static HashSet sentEventIdentifiers = new HashSet(); + + private class Implementation : IDisposable + { + #region GameAnalytics methods + private readonly Action initialize; + internal void Initialize(string gameKey, string secretKey) + => initialize(gameKey, secretKey); + + private readonly Action configureBuild; + internal void ConfigureBuild(string config) => configureBuild(config); + + private readonly Action addErrorEvent; + internal void AddErrorEvent(ErrorSeverity severity, string message) + => addErrorEvent(severity, message); + + private readonly Action?> addDesignEvent0; + internal void AddDesignEvent(string message, IDictionary? fields = null) + => addDesignEvent0(message, fields); + + private readonly Action addDesignEvent1; + internal void AddDesignEvent(string message, double value) + => addDesignEvent1(message, value); + + private readonly Action addProgressionEvent01; + internal void AddProgressionEvent(ProgressionStatus status, string progression01) + => addProgressionEvent01(status, progression01); + + private readonly Action addProgressionEvent01Score; + internal void AddProgressionEvent(ProgressionStatus status, string progression01, double score) + => addProgressionEvent01Score(status, progression01, score); + + private readonly Action addProgressionEvent02; + internal void AddProgressionEvent(ProgressionStatus status, string progression01, string progression02) + => addProgressionEvent02(status, progression01, progression02); + + private readonly Action addProgressionEvent03; + internal void AddProgressionEvent(ProgressionStatus status, string progression01, string progression02, string progression03) + => addProgressionEvent03(status, progression01, progression02, progression03); + + private readonly Action setCustomDimension01; + internal void SetCustomDimension01(string dimension01) + => setCustomDimension01(dimension01); + + private readonly Action configureAvailableCustomDimensions01; + internal void ConfigureAvailableCustomDimensions01(params string[] customDimensions) + => configureAvailableCustomDimensions01(customDimensions); + + private readonly Action setEnabledInfoLog; + internal void SetEnabledInfoLog(bool enabled) + => setEnabledInfoLog(enabled); + #endregion + + #region Data required to fetch methods via reflection + private const string AssemblyName = "GameAnalytics.NetStandard"; + private const string Namespace = "GameAnalyticsSDK.Net"; + private const string MainClass = "GameAnalytics"; + private const string EnumPrefix = "EGA"; + #endregion + + #region Call implementations + private readonly object?[] args1 = new object?[1]; + private readonly object?[] args2 = new object?[2]; + private readonly object?[] args3 = new object?[3]; + private readonly object?[] args4 = new object?[4]; + + private Action Call(MethodInfo methodInfo) + => () => methodInfo?.Invoke(null, null); + + private Action Call(MethodInfo methodInfo) + => (T arg1) => + { + args1[0] = arg1; + methodInfo.Invoke(null, args1); + }; + + private Action Call(MethodInfo methodInfo) + => (T1 arg1, T2 arg2) => + { + args2[0] = arg1; + args2[1] = arg2; + methodInfo.Invoke(null, args2); + }; + + private Action Call(MethodInfo methodInfo) + => (T1 arg1, T2 arg2, T3 arg3) => + { + args3[0] = arg1; + args3[1] = arg2; + args3[2] = arg3; + methodInfo.Invoke(null, args3); + }; + + private Action Call(MethodInfo methodInfo) + => (T1 arg1, T2 arg2, T3 arg3, T4 arg4) => + { + args4[0] = arg1; + args4[1] = arg2; + args4[2] = arg3; + args4[3] = arg4; + methodInfo.Invoke(null, args4); + }; + #endregion + + private AssemblyLoadContext? loadContext; + private Assembly? assembly; + + private string GetAssemblyPath(string assemblyName) + => Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + $"{assemblyName}.dll"); + + private bool resolvingDependency; + private Assembly? ResolveDependency(AssemblyLoadContext context, AssemblyName dependencyName) + { + if (resolvingDependency) { return null; } + resolvingDependency = true; + Assembly dep = context.LoadFromAssemblyPath(GetAssemblyPath(dependencyName.Name ?? throw new Exception("Dependency name was null"))); + resolvingDependency = false; + return dep; + } + + internal Implementation() + { + loadContext = new AssemblyLoadContext(AssemblyName, isCollectible: true); + loadContext.Resolving += ResolveDependency; + assembly = loadContext.LoadFromAssemblyPath( + GetAssemblyPath(AssemblyName)); + + Type getType(string name) + => assembly.GetType($"{Namespace}.{name}") + ?? throw new Exception($"Could not find type\"{Namespace}.{name}\""); + + var mainClass = getType(MainClass); + var errorSeverityEnumType = getType($"{EnumPrefix}{nameof(ErrorSeverity)}"); + var progressionStatusEnumType = getType($"{EnumPrefix}{nameof(ProgressionStatus)}"); + + MethodInfo getMethod(string name, Type[] types) + { + return mainClass?.GetMethod(name, BindingFlags.Public | BindingFlags.Static, binder: null, types: types, modifiers: null) + ?? throw new Exception($"Could not find method \"{name}\" with types {string.Join(',', types.Select(t => t.Name))}"); + } + + initialize = Call(getMethod(nameof(Initialize), + new Type[] { typeof(string), typeof(string) })); + configureBuild = Call(getMethod(nameof(ConfigureBuild), + new Type[] { typeof(string) })); + addErrorEvent = Call(getMethod(nameof(AddErrorEvent), + new Type[] { errorSeverityEnumType, typeof(string) })); + addDesignEvent0 = Call?>(getMethod(nameof(AddDesignEvent), + new Type[] { typeof(string), typeof(IDictionary) })); + addDesignEvent1 = Call(getMethod(nameof(AddDesignEvent), + new Type[] { typeof(string), typeof(double) })); + addProgressionEvent01 = Call(getMethod(nameof(AddProgressionEvent), + new Type[] { progressionStatusEnumType, typeof(string) })); + addProgressionEvent01Score = Call(getMethod(nameof(AddProgressionEvent), + new Type[] { progressionStatusEnumType, typeof(string), typeof(double) })); + addProgressionEvent02 = Call(getMethod(nameof(AddProgressionEvent), + new Type[] { progressionStatusEnumType, typeof(string), typeof(string) })); + addProgressionEvent03 = Call(getMethod(nameof(AddProgressionEvent), + new Type[] { progressionStatusEnumType, typeof(string), typeof(string), typeof(string) })); + setCustomDimension01 = Call(getMethod(nameof(SetCustomDimension01), + new Type[] { typeof(string) })); + configureAvailableCustomDimensions01 = Call(getMethod(nameof(ConfigureAvailableCustomDimensions01), + new Type[] { typeof(string[]) })); + setEnabledInfoLog = Call(getMethod(nameof(SetEnabledInfoLog), + new Type[] { typeof(bool) })); + + onQuit = Call(getMethod("OnQuit", Array.Empty())); + } + + private readonly Action? onQuit; + private void OnQuit() + { + if (assembly != null) { onQuit?.Invoke(); } + } + + public void Dispose() + { + if (loadContext is null) { return; } + OnQuit(); + loadContext?.Unload(); + loadContext = null; + assembly = null; + } + + ~Implementation() + { + OnQuit(); + } + } + private static Implementation? loadedImplementation; + + public static void AddErrorEvent(ErrorSeverity errorSeverity, string message) + { + if (!SendUserStatistics) { return; } + loadedImplementation?.AddErrorEvent(errorSeverity, message); + } + + /// + /// Adds an error event to GameAnalytics if an event with the same identifier has not been added yet. + /// + public static void AddErrorEventOnce(string identifier, ErrorSeverity errorSeverity, string message) + { + if (!SendUserStatistics) { return; } + if (sentEventIdentifiers.Contains(identifier)) { return; } + + if (GameMain.Config.AllEnabledPackages != null) + { + if (GameMain.VanillaContent == null || GameMain.Config.AllEnabledPackages.Any(p => p.HasMultiplayerIncompatibleContent && p != GameMain.VanillaContent)) + { + message = "[MODDED] " + message; + } + } + + loadedImplementation?.AddErrorEvent(errorSeverity, message); + sentEventIdentifiers.Add(identifier); + } + + public static void AddDesignEvent(string eventID) + { + if (!SendUserStatistics) { return; } + loadedImplementation?.AddDesignEvent(eventID); + } + + public static void AddDesignEvent(string eventID, double value) + { + if (!SendUserStatistics) { return; } + loadedImplementation?.AddDesignEvent(eventID, value); + } + + public static void AddProgressionEvent(ProgressionStatus progressionStatus, string progression01) + { + if (!SendUserStatistics) { return; } + loadedImplementation?.AddProgressionEvent(progressionStatus, progression01); + } + + public static void AddProgressionEvent(ProgressionStatus progressionStatus, string progression01, double score) + { + if (!SendUserStatistics) { return; } + loadedImplementation?.AddProgressionEvent(progressionStatus, progression01, score); + } + + public static void AddProgressionEvent(ProgressionStatus progressionStatus, string progression01, string progression02) + { + if (!SendUserStatistics) { return; } + loadedImplementation?.AddProgressionEvent(progressionStatus, progression01, progression02); + } + + public static void AddProgressionEvent(ProgressionStatus progressionStatus, string progression01, string progression02, string progression03) + { + if (!SendUserStatistics) { return; } + loadedImplementation?.AddProgressionEvent(progressionStatus, progression01, progression02, progression03); + } + + public static void SetCustomDimension01(string dimension) + { + if (!SendUserStatistics) { return; } + loadedImplementation?.SetCustomDimension01(dimension); + } + + private static void Init() + { + ShutDown(); + try + { + loadedImplementation = new Implementation(); + } + catch (Exception e) + { + DebugConsole.ThrowError("Initializing GameAnalytics failed. Disabling user statistics...", e); + SetConsent(Consent.Error); + return; + } +#if DEBUG + try + { + loadedImplementation?.SetEnabledInfoLog(true); + } + catch (Exception e) + { + DebugConsole.ThrowError("Initializing GameAnalytics failed. Disabling user statistics...", e); + SetConsent(Consent.Error); + return; + } +#endif + + string exePath = Assembly.GetEntryAssembly()!.Location; + string? exeName = null; + Md5Hash? exeHash = null; + exeName = Path.GetFileNameWithoutExtension(exePath).Replace(":", ""); + try + { + using (var stream = File.OpenRead(exePath)) + { + exeHash = new Md5Hash(stream); + } + } + catch (Exception e) + { + DebugConsole.ThrowError("Error while calculating MD5 hash for the executable \"" + exePath + "\"", e); + } + try + { + loadedImplementation?.ConfigureBuild(GameMain.Version.ToString() + + (string.IsNullOrEmpty(exeName) ? "Unknown" : exeName) + ":" + + ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash)); + loadedImplementation?.ConfigureAvailableCustomDimensions01("singleplayer", "multiplayer", "editor"); + + InitKeys(); + + loadedImplementation?.AddDesignEvent("Executable:" + + (string.IsNullOrEmpty(exeName) ? "Unknown" : exeName) + ":" + + ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash)); + } + catch (Exception e) + { + DebugConsole.ThrowError("Initializing GameAnalytics failed. Disabling user statistics...", e); + SetConsent(Consent.Error); + return; + } + + var allPackages = GameMain.Config?.AllEnabledPackages.ToList(); + if (allPackages?.Count > 0) + { + StringBuilder sb = new StringBuilder("ContentPackage: "); + int i = 0; + foreach (ContentPackage cp in allPackages) + { + string trimmedName = cp.Name.Replace(":", "").Replace(" ", ""); + sb.Append(trimmedName.Substring(0, Math.Min(32, trimmedName.Length))); + if (i < allPackages.Count - 1) { sb.Append(" "); } + } + loadedImplementation?.AddDesignEvent(sb.ToString()); + } + } + + static partial void InitKeys(); + + public static void ShutDown() + { + loadedImplementation?.Dispose(); + loadedImplementation = null; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs index 84d41b23a..5d20749f1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs @@ -168,9 +168,9 @@ namespace Barotrauma { if (itemPrefab == null) { - string errorMsg = "Error in AutoItemPlacer.SpawnItems - itemPrefab was null.\n"+Environment.StackTrace.CleanupStackTrace(); + string errorMsg = "Error in AutoItemPlacer.SpawnItems - itemPrefab was null.\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("AutoItemPlacer.SpawnItems:ItemNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("AutoItemPlacer.SpawnItems:ItemNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return false; } bool success = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index d55314017..1d7049542 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -246,7 +246,8 @@ namespace Barotrauma continue; } - Item containerItem = new Item(containerPrefab, position, wp.Submarine); + Vector2 containerPosition = GetCargoPos(cargoRoom, containerPrefab); + Item containerItem = new Item(containerPrefab, containerPosition, wp.Submarine); itemContainer = containerItem.GetComponent(); if (itemContainer == null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index ef3ddc9f4..ff3462958 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -11,10 +11,10 @@ namespace Barotrauma { internal struct CampaignSettings { - public static CampaignSettings Empty = new CampaignSettings(); + public static CampaignSettings Empty => new CampaignSettings(); // Anything that uses this field I wasn't sure if actually needed the proper campaign settings to be passed down - public static CampaignSettings Unsure = Empty; + public static CampaignSettings Unsure => Empty; public bool RadiationEnabled { get; set; } public int TotalMaxMissionCount => MaxMissionCount + GetAddedMissionCount(); @@ -34,7 +34,7 @@ namespace Barotrauma { maxMissionCount = DefaultMaxMissionCount; RadiationEnabled = inc.ReadBoolean(); - MaxMissionCount = inc.ReadInt32(); + MaxMissionCount = inc.ReadRangedInteger(MinMissionCountLimit, MaxMissionCountLimit); } public CampaignSettings(XElement element) @@ -47,7 +47,7 @@ namespace Barotrauma public void Serialize(IWriteMessage msg) { msg.Write(RadiationEnabled); - msg.Write(MaxMissionCount); + msg.WriteRangedInteger(MaxMissionCount, MinMissionCountLimit, MaxMissionCountLimit); } public int GetAddedMissionCount() @@ -484,6 +484,10 @@ namespace Barotrauma /// private Submarine GetLeavingSub() { + if (Level.IsLoadedOutpost) + { + return Submarine.MainSub; + } //in single player, only the sub the controlled character is inside can transition between levels //in multiplayer, if there's subs at both ends of the level, only the one with more players inside can transition //TODO: ignore players who don't have the permission to trigger a transition between levels? @@ -493,11 +497,6 @@ namespace Barotrauma Submarine leavingSubAtStart = GetLeavingSubAtStart(leavingPlayers); Submarine leavingSubAtEnd = GetLeavingSubAtEnd(leavingPlayers); - if (Level.IsLoadedOutpost) - { - leavingSubAtStart ??= Submarine.MainSub; - leavingSubAtEnd ??= Submarine.MainSub; - } int playersInSubAtStart = leavingSubAtStart == null || !leavingSubAtStart.AtStartExit ? 0 : leavingPlayers.Count(c => c.Submarine == leavingSubAtStart || leavingSubAtStart.DockedTo.Contains(c.Submarine) || (Level.Loaded.StartOutpost != null && c.Submarine == Level.Loaded.StartOutpost)); int playersInSubAtEnd = leavingSubAtEnd == null || !leavingSubAtEnd.AtEndExit ? 0 : @@ -686,6 +685,14 @@ namespace Barotrauma int loops = CampaignMetadata.GetInt("campaign.endings", 0); CampaignMetadata.SetValue("campaign.endings", loops + 1); } + + GameAnalyticsManager.AddProgressionEvent( + GameAnalyticsManager.ProgressionStatus.Complete, + Name ?? "none"); + string eventId = "FinishCampaign:"; + GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none")); + GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count() ?? 0)); + GameAnalyticsManager.AddDesignEvent(eventId + "Money", Money); } protected virtual void EndCampaignProjSpecific() { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs index 739f6f112..1191f30c4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -2,6 +2,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Linq; using System.Xml.Linq; namespace Barotrauma @@ -119,12 +120,6 @@ namespace Barotrauma #endif } -#if SERVER - List availableSubs = new List(); - List sourceList = new List(); - sourceList.AddRange(SubmarineInfo.SavedSubmarines); -#endif - foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -165,14 +160,6 @@ namespace Barotrauma petsElement = subElement; break; #if SERVER - case "availablesubs": - foreach (XElement availableSub in subElement.Elements()) - { - string subName = availableSub.GetAttributeString("name", ""); - SubmarineInfo matchingSub = sourceList.Find(s => s.Name == subName); - if (matchingSub != null) { availableSubs.Add(matchingSub); } - } - break; case "savedexperiencepoints": foreach (XElement savedExp in subElement.Elements()) { @@ -188,14 +175,6 @@ namespace Barotrauma InitCampaignData(); #if SERVER - // Fallback if using a save with no available subs assigned, use vanilla submarines - if (availableSubs.Count == 0) - { - GameMain.NetLobbyScreen.CampaignSubmarines.AddRange(sourceList.FindAll(s => s.IsCampaignCompatible && s.IsVanillaSubmarine())); - } - - GameMain.NetLobbyScreen.CampaignSubmarines = availableSubs; - characterData.Clear(); string characterDataPath = GetCharacterDataSavePath(); if (!File.Exists(characterDataPath)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 0c9628fc9..d5a8784d7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -406,6 +406,21 @@ namespace Barotrauma InitializeLevel(level); + GameAnalyticsManager.AddProgressionEvent( + GameAnalyticsManager.ProgressionStatus.Start, + GameMode?.Name ?? "none"); + + string eventId = "StartRound:GameMode:" + (GameMode?.Name ?? "none") + ":"; + GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none")); + GameAnalyticsManager.AddDesignEvent(eventId + "GameMode:" + (GameMode?.Name ?? "none")); + GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count() ?? 0)); + foreach (Mission mission in missions) + { + GameAnalyticsManager.AddDesignEvent(eventId + "MissionType:" + (mission.Prefab.Type.ToString() ?? "none") + ":" + mission.Prefab.Identifier); + } + GameAnalyticsManager.AddDesignEvent(eventId + "LevelType:" + (Level.Loaded?.Type.ToString() ?? "none")); + GameAnalyticsManager.AddDesignEvent(eventId + "Biome:" + (Level.Loaded?.LevelData?.Biome?.Identifier ?? "none")); + #if CLIENT if (GameMode is CampaignMode) { SteamAchievementManager.OnBiomeDiscovered(levelData.Biome); } @@ -674,6 +689,8 @@ namespace Barotrauma { IEnumerable crewCharacters = GetSessionCrewCharacters(); + int prevMoney = (GameMode as CampaignMode)?.Money ?? 0; + foreach (Mission mission in missions) { mission.End(); @@ -731,6 +748,32 @@ namespace Barotrauma missions.Clear(); IsRunning = false; + + bool success = false; +#if CLIENT + success = CrewManager.GetCharacters().Any(c => !c.IsDead); +#else + success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead); +#endif + double roundDuration = Timing.TotalTime - RoundStartTime; + GameAnalyticsManager.AddProgressionEvent( + success ? GameAnalyticsManager.ProgressionStatus.Complete : GameAnalyticsManager.ProgressionStatus.Fail, + GameMode?.Name ?? "none", + roundDuration); + string eventId = "EndRound:GameMode:" + (GameMode?.Name ?? "none") + ":"; + GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none"), roundDuration); + GameAnalyticsManager.AddDesignEvent(eventId + "GameMode:" + (GameMode?.Name ?? "none"), roundDuration); + GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count() ?? 0), roundDuration); + foreach (Mission mission in missions) + { + GameAnalyticsManager.AddDesignEvent(eventId + "MissionType:" + (mission.Prefab.Type.ToString() ?? "none") + ":" + mission.Prefab.Identifier + ":" + (mission.Completed ? "Completed" : "Failed"), roundDuration); + } + GameAnalyticsManager.AddDesignEvent(eventId + "LevelType:" + (Level.Loaded?.Type.ToString() ?? "none"), roundDuration); + GameAnalyticsManager.AddDesignEvent(eventId + "Biome:" + (Level.Loaded?.LevelData?.Biome?.Identifier ?? "none"), roundDuration); + if (GameMode is CampaignMode campaignMode) + { + GameAnalyticsManager.AddDesignEvent(eventId + "MoneyEarned", campaignMode.Money - prevMoney); + } #if CLIENT HintManager.OnRoundEnded(); #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs index b8b37bcc3..28a6d9571 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs @@ -757,25 +757,6 @@ namespace Barotrauma public bool CampaignDisclaimerShown, EditorDisclaimerShown; - private static bool sendUserStatistics = true; - public static bool SendUserStatistics - { - get - { - return false; -/*#if DEBUG - return false; -#endif - return sendUserStatistics;*/ - } - set - { - sendUserStatistics = value; - GameMain.Config.SaveNewPlayerConfig(); - } - } - public static bool ShowUserStatisticsPrompt { get; set; } - public bool ShowLanguageSelectionPrompt { get; set; } public static bool ShowOffensiveServerPrompt { get; set; } @@ -858,7 +839,6 @@ namespace Barotrauma if (!fileFound) { ShowLanguageSelectionPrompt = true; - ShowUserStatisticsPrompt = true; SaveNewPlayerConfig(); } } @@ -870,9 +850,8 @@ namespace Barotrauma private bool LoadPlayerConfigInternal() { XDocument doc = XMLExtensions.LoadXml(PlayerSavePath); - if (doc == null || doc.Root == null) + if (doc?.Root == null) { - ShowUserStatisticsPrompt = true; ShowTutorialSkipWarning = true; return false; } @@ -989,12 +968,7 @@ namespace Barotrauma if (!string.IsNullOrEmpty(overrideMultiplayerSaveFolder)) { doc.Root.Add(new XAttribute("overridemultiplayersavefolder", overrideMultiplayerSaveFolder)); - } - - if (!ShowUserStatisticsPrompt) - { - doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics)); - } + } XElement gMode = doc.Root.Element("graphicsmode"); if (gMode == null) @@ -1194,7 +1168,7 @@ namespace Barotrauma catch (Exception e) { DebugConsole.ThrowError("Saving game settings failed.", e); - GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsManager.ErrorSeverity.Error, "Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace()); return false; } @@ -1211,7 +1185,6 @@ namespace Barotrauma Language = doc.Root.GetAttributeString("language", Language); } AutoCheckUpdates = doc.Root.GetAttributeBool("autocheckupdates", AutoCheckUpdates); - sendUserStatistics = doc.Root.GetAttributeBool("senduserstatistics", sendUserStatistics); QuickStartSubmarineName = doc.Root.GetAttributeString("quickstartsub", QuickStartSubmarineName); EnableSubmarineAutoSave = doc.Root.GetAttributeBool("submarineautosave", true); MaximumAutoSaves = doc.Root.GetAttributeInt("maxautosaves", 8); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index 0aa9fc4fc..2aeb8ca59 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -430,7 +430,7 @@ namespace Barotrauma if (index < 0 || index >= slots.Length) { string errorMsg = "CharacterInventory.TryPutItem failed: index was out of range(" + index + ").\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("CharacterInventory.TryPutItem:IndexOutOfRange", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("CharacterInventory.TryPutItem:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return false; } #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs index e2d433d8c..ec33efc66 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs @@ -484,7 +484,7 @@ namespace Barotrauma.Items.Components DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "DockingPort.CreateDoorBody:InvalidPosition", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, errorMsg); position = Vector2.Zero; } @@ -779,29 +779,25 @@ namespace Barotrauma.Items.Components if (IsHorizontal) { - if (hulls[0].WorldRect.X < hulls[1].WorldRect.X) + if (hulls[0].WorldRect.X > hulls[1].WorldRect.X) { - gap.linkedTo.Add(hulls[0]); - gap.linkedTo.Add(hulls[1]); - } - else - { - gap.linkedTo.Add(hulls[1]); - gap.linkedTo.Add(hulls[0]); + var temp = hulls[0]; + hulls[0] = hulls[1]; + hulls[1] = temp; } + gap.linkedTo.Add(hulls[0]); + gap.linkedTo.Add(hulls[1]); } else { - if (hulls[0].WorldRect.Y > hulls[1].WorldRect.Y) + if (hulls[0].WorldRect.Y < hulls[1].WorldRect.Y) { - gap.linkedTo.Add(hulls[0]); - gap.linkedTo.Add(hulls[1]); - } - else - { - gap.linkedTo.Add(hulls[1]); - gap.linkedTo.Add(hulls[0]); + var temp = hulls[0]; + hulls[0] = hulls[1]; + hulls[1] = temp; } + gap.linkedTo.Add(hulls[0]); + gap.linkedTo.Add(hulls[1]); } for (int i = 0; i < 2; i++) @@ -813,7 +809,7 @@ namespace Barotrauma.Items.Components if (IsHorizontal) { - if (item.WorldPosition.X < DockingTarget.item.WorldPosition.X) + if (doorGap.WorldPosition.X < gap.WorldPosition.X) { if (!doorGap.linkedTo.Contains(hulls[0])) { doorGap.linkedTo.Add(hulls[0]); } } @@ -831,7 +827,7 @@ namespace Barotrauma.Items.Components } else { - if (item.WorldPosition.Y > DockingTarget.item.WorldPosition.Y) + if (doorGap.WorldPosition.Y > gap.WorldPosition.Y) { if (!doorGap.linkedTo.Contains(hulls[0])) { doorGap.linkedTo.Add(hulls[0]); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs index bf6875202..2ff2e7d77 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs @@ -204,9 +204,12 @@ namespace Barotrauma.Items.Components set; } - [Serialize(true, true, description: ""), Editable] + [Editable, Serialize(true, true, description: "", alwaysUseInstanceValues: true)] public bool UseBetweenOutpostModules { get; private set; } + [Editable, Serialize(false, false, description: "If true, bots won't try to close this door behind them.", alwaysUseInstanceValues: true)] + public bool BotsShouldKeepOpen { get; private set; } + public Door(Item item, XElement element) : base(item, element) { @@ -291,7 +294,7 @@ namespace Barotrauma.Items.Components public override bool Pick(Character picker) { - if (item.Condition < RepairThreshold) { return true; } + if (item.Condition < RepairThreshold && item.GetComponent().HasRequiredItems(picker, addMessage: false)) { return true; } if (requiredItems.None()) { return false; } if (HasAccess(picker) && HasRequiredItems(picker, false)) { return false; } return base.Pick(picker); @@ -299,7 +302,7 @@ namespace Barotrauma.Items.Components public override bool OnPicked(Character picker) { - if (item.Condition < RepairThreshold) { return true; } + if (item.Condition < RepairThreshold && item.GetComponent().HasRequiredItems(picker, addMessage: false)) { return true; } if (!HasAccess(picker)) { ToggleState(ActionType.OnPicked, picker); @@ -339,6 +342,7 @@ namespace Barotrauma.Items.Components ToggleState(ActionType.OnUse, character); PickingTime = originalPickingTime; StopPicking(picker); + return true; } #if CLIENT else if (hasRequiredItems && character != null && character == Character.Controlled) @@ -545,7 +549,7 @@ namespace Barotrauma.Items.Components if (!itemPosErrorShown) { DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")"); - GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:DoorPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:DoorPosInvalid", GameAnalyticsManager.ErrorSeverity.Error, "Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")."); itemPosErrorShown = true; } @@ -568,8 +572,8 @@ namespace Barotrauma.Items.Components if (!characterPosErrorShown.Contains(c)) { if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")"); } - GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:CharacterPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")." + + GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:CharacterPosInvalid", GameAnalyticsManager.ErrorSeverity.Error, + "Failed to push a character out of a doorway - position of the character \"" + c.SpeciesName + "\" is not valid (" + c.SimPosition + ")." + " Removed: " + c.Removed + " Remoteplayer: " + c.IsRemotePlayer); characterPosErrorShown.Add(c); @@ -598,8 +602,8 @@ namespace Barotrauma.Items.Components if (!MathUtils.IsValid(body.SimPosition)) { DebugConsole.ThrowError("Failed to push a limb out of a doorway - position of the body (character \"" + c.Name + "\") is not valid (" + body.SimPosition + ")"); - GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:LimbPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + body.SimPosition + ")." + + GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:LimbPosInvalid", GameAnalyticsManager.ErrorSeverity.Error, + "Failed to push a character out of a doorway - position of the character \"" + c.SpeciesName + "\" is not valid (" + body.SimPosition + ")." + " Removed: " + c.Removed + " Remoteplayer: " + c.IsRemotePlayer); return false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs index 27c566b83..bb67dea57 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs @@ -310,12 +310,6 @@ namespace Barotrauma.Items.Components internal static class GrowthSideExtension { - // Enum.HasFlag() sucks - public static bool IsBitSet(this TileSide side, TileSide bit) - { - return ((int) side & (int) bit) != 0; - } - // K&R algorithm for counting how many bits are set in a bit field public static int Count(this TileSide side) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index e88c44e43..c03c26b72 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -169,7 +169,7 @@ namespace Barotrauma.Items.Components CollidesWith = Physics.CollisionCharacter, CollisionCategories = Physics.CollisionItemBlocking, Enabled = false, - UserData = "Holdable.Pusher" + UserData = this }; Pusher.FarseerBody.OnCollision += OnPusherCollision; Pusher.FarseerBody.FixedRotation = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs index f7fc8ec5a..f2561695e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs @@ -69,14 +69,28 @@ namespace Barotrauma.Items.Components public override bool Pick(Character picker) { //return if someone is already trying to pick the item - if (pickTimer > 0.0f) return false; - if (picker == null || picker.Inventory == null) return false; + if (pickTimer > 0.0f) { return false; } + if (picker == null || picker.Inventory == null) { return false; } if (PickingTime > 0.0f) { var abilityPickingTime = new AbilityValueItem(PickingTime, item.Prefab); picker.CheckTalents(AbilityEffectType.OnItemPicked, abilityPickingTime); + if (requiredItems.ContainsKey(RelatedItem.RelationType.Equipped)) + { + foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Equipped]) + { + foreach (var heldItem in picker.HeldItems) + { + if (ri.MatchesItem(heldItem)) + { + abilityPickingTime.Value /= 1 + heldItem.Prefab.AddedPickingSpeedMultiplier; + } + } + } + } + if ((picker.PickingItem == null || picker.PickingItem == item) && PickingTime <= float.MaxValue) { #if SERVER diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index 89f3efb8b..bee423e3d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -32,6 +32,13 @@ namespace Barotrauma.Items.Components set { reload = Math.Max(value, 0.0f); } } + [Serialize(false, false, description: "Tells the AI to hold the trigger down when it uses this weapon")] + public bool HoldTrigger + { + get; + set; + } + [Serialize(1, false, description: "How projectiles the weapon launches when fired once.")] public int ProjectileCount { @@ -110,9 +117,7 @@ namespace Barotrauma.Items.Components if (ReloadTimer < 0.0f) { ReloadTimer = 0.0f; - // was this an optimization or related to something else? it cannot occur for charge-type weapons - //IsActive = false; - if (MaxChargeTime == 0.0f) + if (MaxChargeTime <= 0f) { IsActive = false; return; @@ -147,7 +152,7 @@ namespace Barotrauma.Items.Components private float GetSpread(Character user) { - float degreeOfFailure = 1.0f - DegreeOfSuccess(user); + float degreeOfFailure = MathHelper.Clamp(1.0f - DegreeOfSuccess(user), 0.0f, 1.0f); degreeOfFailure *= degreeOfFailure; float spread = MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure) / (1f + user.GetStatValue(StatTypes.RangedSpreadReduction)); return MathHelper.ToRadians(spread); @@ -204,7 +209,8 @@ namespace Barotrauma.Items.Components { lastProjectile?.Item.GetComponent()?.Snap(); } - float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.StoppingPowerMultiplier); + float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.FirepowerMultiplier); + projectile.Launcher = item; projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false, damageMultiplier); projectile.Item.GetComponent()?.Attach(Item, projectile.Item); if (i == 0) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index 5c8801e2a..9f4fec7f2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -613,7 +613,7 @@ namespace Barotrauma.Items.Components { string errorMsg = "ItemComponent.DegreeOfSuccess failed (character was null).\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return 0.0f; } @@ -889,7 +889,7 @@ namespace Barotrauma.Items.Components { DebugConsole.ThrowError("Error while loading entity of the type " + t + ".", e.InnerException); GameAnalyticsManager.AddErrorEventOnce("ItemComponent.Load:TargetInvocationException" + item.Name + element.Name, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Error while loading entity of the type " + t + " (" + e.InnerException + ")\n" + Environment.StackTrace.CleanupStackTrace()); } @@ -994,7 +994,7 @@ namespace Barotrauma.Items.Components { containObjective = new AIObjectiveContainItem(character, container.ContainableItemIdentifiers.ToArray(), container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound) { - targetItemCount = itemCount, + ItemCount = itemCount, Equip = equip, RemoveEmpty = removeEmpty, GetItemPriority = i => diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 5cad0a43c..a7559b690 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -471,7 +471,7 @@ namespace Barotrauma.Items.Components if (!slotRestrictions.Any(s => s.MatchesItem(item))) { return false; } if (user != null && !user.CanAccessInventory(Inventory)) { return false; } //genetic materials use special logic for combining, don't allow doing it by placing them inside each other here - if (item.GetComponent() != null) { return false; } + if (this.Item.GetComponent() != null) { return false; } if (Inventory.TryPutItem(item, user)) { @@ -577,7 +577,7 @@ namespace Barotrauma.Items.Components { DebugConsole.Log("SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("ItemContainer.SetContainedItemPositions.InvalidPosition:" + contained.Name, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace.CleanupStackTrace()); } contained.body.Submarine = item.Submarine; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index c15a5a5b4..77323447c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -61,14 +61,14 @@ namespace Barotrauma.Items.Components public IEnumerable LimbPositions { get { return limbPositions; } } - [Editable, Serialize(false, false, description: "When enabled, the item will continuously send out a 0/1 signal and interacting with it will flip the signal (making the item behave like a switch). When disabled, the item will simply send out 1 when interacted with.")] + [Editable, Serialize(false, false, description: "When enabled, the item will continuously send out a 0/1 signal and interacting with it will flip the signal (making the item behave like a switch). When disabled, the item will simply send out 1 when interacted with.", alwaysUseInstanceValues: true)] public bool IsToggle { get; set; } - [Editable, Serialize(false, false, description: "Whether the item is toggled on/off. Only valid if IsToggle is set to true.")] + [Editable, Serialize(false, false, description: "Whether the item is toggled on/off. Only valid if IsToggle is set to true.", alwaysUseInstanceValues: true)] public bool State { get; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index a83b29b97..4c0f5009f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -158,10 +158,21 @@ namespace Barotrauma.Items.Components // In multiplayer, the server handles the deconstruction into new items if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } + float amountMultiplier = 1f; + if (user != null && !user.Removed) { - var abilityTargetItem = new AbilityItem(targetItem); + var abilityTargetItem = new AbilityDeconstructedItem(targetItem, user); user.CheckTalents(AbilityEffectType.OnItemDeconstructed, abilityTargetItem); + + foreach (Character character in Character.GetFriendlyCrew(user)) + { + character.CheckTalents(AbilityEffectType.OnItemDeconstructedByAlly, abilityTargetItem); + } + + var itemCreationMultiplier = new AbilityValueItem(amountMultiplier, targetItem.Prefab); + user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemCreationMultiplier); + amountMultiplier = (int)itemCreationMultiplier.Value; } if (targetItem.Prefab.RandomDeconstructionOutput) @@ -187,18 +198,18 @@ namespace Barotrauma.Items.Components foreach (DeconstructItem deconstructProduct in products) { - CreateDeconstructProduct(deconstructProduct, inputItems); + CreateDeconstructProduct(deconstructProduct, inputItems, amountMultiplier); } } else { foreach (DeconstructItem deconstructProduct in validDeconstructItems) { - CreateDeconstructProduct(deconstructProduct, inputItems); + CreateDeconstructProduct(deconstructProduct, inputItems, amountMultiplier); } } - void CreateDeconstructProduct(DeconstructItem deconstructProduct, IEnumerable inputItems) + void CreateDeconstructProduct(DeconstructItem deconstructProduct, IEnumerable inputItems, float amountMultiplier) { float percentageHealth = targetItem.Condition / targetItem.MaxCondition; @@ -247,19 +258,15 @@ namespace Barotrauma.Items.Components } } - int amount = 1; - if (user != null && !user.Removed) { - var itemsCreated = new AbilityValueItem(amount, targetItem.Prefab); - user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemsCreated); - amount = (int)itemsCreated.Value; - // used to spawn items directly into the deconstructor var itemContainer = new AbilityItemPrefabItem(item, targetItem.Prefab); user.CheckTalents(AbilityEffectType.OnItemDeconstructedInventory, itemContainer); } + int amount = (int)amountMultiplier; + for (int i = 0; i < amount; i++) { Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) => @@ -269,7 +276,17 @@ namespace Barotrauma.Items.Components for (int i = 0; i < outputContainer.Capacity; i++) { var containedItem = outputContainer.Inventory.GetItemAt(i); - if (containedItem?.Combine(spawnedItem, null) ?? false) + if (containedItem?.OwnInventory != null) + { + foreach (Item subItem in containedItem.ContainedItems.ToList()) + { + if (subItem.Combine(spawnedItem, null)) + { + break; + } + } + } + else if (containedItem?.Combine(spawnedItem, null) ?? false) { break; } @@ -409,4 +426,15 @@ namespace Barotrauma.Items.Components inputContainer.Inventory.Locked = IsActive; } } + class AbilityDeconstructedItem : AbilityObject, IAbilityItem, IAbilityCharacter + { + public AbilityDeconstructedItem(Item item, Character character) + { + Item = item; + Character = character; + } + public Item Item { get; set; } + public Character Character { get; set; } + } + } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index 279644ef0..1dd754e3e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -376,7 +376,7 @@ namespace Barotrauma.Items.Components int quality = 0; if (user?.Info != null) { - foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID)) + foreach (Character character in Character.GetFriendlyCrew(user)) { character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationValueItem); } @@ -433,13 +433,12 @@ namespace Barotrauma.Items.Components { float userSkill = user.GetSkillLevel(skill.Identifier); float addedSkill = skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f); - var addedSkillValue = new AbilityValueString(0f, skill.Identifier); + var addedSkillValue = new AbilityValueString(addedSkill, skill.Identifier); user.CheckTalents(AbilityEffectType.OnItemFabricationSkillGain, addedSkillValue); - addedSkill += addedSkillValue.Value; user.Info.IncreaseSkillLevel( skill.Identifier, - addedSkill); + addedSkillValue.Value); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs index 39049b9bd..d89269827 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs @@ -147,16 +147,12 @@ namespace Barotrauma.Items.Components { case "water_data_in": //cheating a bit because water detectors don't actually send the water level - float waterAmount; - if (source.GetComponent() == null) + bool fromWaterDetector = source.GetComponent() != null; + hullData.ReceivedWaterAmount = null; + if (fromWaterDetector) { - waterAmount = Rand.Range(0.0f, 1.0f); + hullData.ReceivedWaterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f); } - else - { - waterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f); - } - hullData.ReceivedWaterAmount = waterAmount; foreach (var linked in sourceHull.linkedTo) { if (!(linked is Hull linkedHull)) { continue; } @@ -165,7 +161,11 @@ namespace Barotrauma.Items.Components linkedHullData = new HullData(); hullDatas.Add(linkedHull, linkedHullData); } - linkedHullData.ReceivedWaterAmount = waterAmount; + linkedHullData.ReceivedWaterAmount = null; + if (fromWaterDetector) + { + linkedHullData.ReceivedWaterAmount = Math.Min(linkedHull.WaterVolume / linkedHull.Volume, 1.0f); + } } break; case "oxygen_data_in": diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs index 273862942..91d3d3b5f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs @@ -186,6 +186,13 @@ namespace Barotrauma.Items.Components [Serialize(0.0f, true)] public float CorrectTurbineOutput { get; set; } + [Editable, Serialize(true, true)] + public bool ExplosionDamagesOtherSubs + { + get; + set; + } + public Reactor(Item item, XElement element) : base(item, element) { @@ -544,6 +551,20 @@ namespace Barotrauma.Items.Components if (item.Condition <= 0.0f) { return; } if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } + if (!ExplosionDamagesOtherSubs && (statusEffectLists?.ContainsKey(ActionType.OnBroken) ?? false)) + { + foreach (var statusEffect in statusEffectLists[ActionType.OnBroken]) + { + foreach (Explosion explosion in statusEffect.Explosions) + { + foreach (Submarine sub in Submarine.Loaded) + { + if (sub != item.Submarine) { explosion.IgnoredSubmarines.Add(sub); } + } + } + } + } + item.Condition = 0.0f; fireTimer = 0.0f; meltDownTimer = 0.0f; @@ -600,7 +621,7 @@ namespace Barotrauma.Items.Components var container = item.GetComponent(); if (objective.SubObjectives.None()) { - var containObjective = AIContainItems(container, character, objective, itemCount: 1, equip: true, removeEmpty: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC, dropItemOnDeselected: true); + var containObjective = AIContainItems(container, character, objective, itemCount: 1, equip: true, removeEmpty: true, spawnItemIfNotFound: !character.IsOnPlayerTeam, dropItemOnDeselected: true); containObjective.Completed += ReportFuelRodCount; containObjective.Abandoned += ReportFuelRodCount; character.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index b44026a4f..9b1c13af8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -61,6 +61,11 @@ namespace Barotrauma.Items.Components public List IgnoredBodies; + /// + /// The item that launched this projectile (if any) + /// + public Item Launcher; + private Character stickTargetCharacter; private Character _user; @@ -324,6 +329,7 @@ namespace Barotrauma.Items.Components item.body.SetTransform(item.body.SimPosition, launchAngle); float modifiedLaunchImpulse = LaunchImpulse * (1 + Rand.Range(-ImpulseSpread, ImpulseSpread)); DoLaunch(launchDir * modifiedLaunchImpulse * item.body.Mass); + System.Diagnostics.Debug.WriteLine("launch: " + modifiedLaunchImpulse + " - " + item.body.LinearVelocity); } } User = character; @@ -347,7 +353,7 @@ namespace Barotrauma.Items.Components launchPos = item.SimPosition; item.body.Enabled = true; - item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.9f); + item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.95f); item.body.FarseerBody.OnCollision += OnProjectileCollision; item.body.FarseerBody.IsBullet = true; @@ -523,11 +529,13 @@ namespace Barotrauma.Items.Components if (fixture.Body.UserData is Item item && (item.GetComponent() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; } if (fixture.Body.UserData as string == "ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; } - - //ignore everything else than characters, sub walls and level walls - if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) && - !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && - !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return -1; } + if (!(fixture.Body.UserData is Holdable holdable && holdable.CanPush)) + { + //ignore everything else than characters, sub walls and level walls + if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) && + !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && + !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return -1; } + } //if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub if (submarine != null) @@ -566,7 +574,7 @@ namespace Barotrauma.Items.Components hits.Add(new HitscanResult(fixture, point, normal, fraction)); return 1; - }, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel); + }, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking); return hits; } @@ -754,7 +762,7 @@ namespace Barotrauma.Items.Components if (Attack != null) { attackResult = Attack.DoDamageToLimb(User ?? Attacker, limb, item.WorldPosition, 1.0f); } if (limb.character != null) { character = limb.character; } } - else if (target.Body.UserData is Item targetItem) + else if ((target.Body.UserData as Item ?? (target.Body.UserData as ItemComponent)?.Item) is Item targetItem) { if (targetItem.Removed) { return false; } if (Attack != null && targetItem.Prefab.DamagedByProjectiles && targetItem.Condition > 0) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs index 75f82175c..090028dc7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs @@ -26,12 +26,13 @@ namespace Barotrauma.Items.Components RepairToolStructureRepairMultiplier, RepairToolStructureDamageMultiplier, RepairToolDeattachTimeMultiplier, - StoppingPowerMultiplier, + FirepowerMultiplier, StrikingPowerMultiplier, StrikingSpeedMultiplier, FiringRateMultiplier, // unused as of now AttackMultiplier, + // unused as of now AttackSpeedMultiplier, ForceDoorsOpenSpeedMultiplier, RangedSpreadReduction, @@ -46,7 +47,7 @@ namespace Barotrauma.Items.Components private int qualityLevel; - [Serialize(0, true)] + [Editable, Serialize(0, true)] public int QualityLevel { get { return qualityLevel; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 7a6f66a64..9712e80b2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -16,6 +16,8 @@ namespace Barotrauma.Items.Components private float deteriorationTimer; private float deteriorateAlwaysResetTimer; + private float repairBoost; + bool wasBroken; bool wasGoodCondition; @@ -214,6 +216,18 @@ namespace Barotrauma.Items.Components return ((average + 100.0f) / 2.0f) / 100.0f; } + public void RepairBoost(bool qteSuccess) + { + if (qteSuccess) + { + repairBoost = RepairDegreeOfSuccess(CurrentFixer, requiredSkills) * 3 * (currentFixerAction == FixActions.Repair ? 1.0f : -1.0f); + } + else + { + repairBoost = (1 - RepairDegreeOfSuccess(CurrentFixer, requiredSkills)) * 10 * (currentFixerAction == FixActions.Repair ? -1.0f : 1.0f); + } + } + public bool StartRepairing(Character character, FixActions action) { if (character == null || character.IsDead || action == FixActions.None) @@ -409,6 +423,12 @@ namespace Barotrauma.Items.Components wasGoodCondition = true; } + if (!MathUtils.NearlyEqual(repairBoost, 0.0f)) + { + item.Condition += repairBoost; + repairBoost = 0.0f; + } + float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor); fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f; fixDuration /= 1 + item.GetQualityModifier(Quality.StatType.RepairSpeed); @@ -444,6 +464,7 @@ namespace Barotrauma.Items.Components SteamAchievementManager.OnItemRepaired(item, CurrentFixer); CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete); } + if (CurrentFixer?.SelectedConstruction == item) { CurrentFixer.SelectedConstruction = null; } deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay); wasBroken = false; StopRepairing(CurrentFixer); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ColorComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ColorComponent.cs index e272750a3..d3d41c271 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ColorComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ColorComponent.cs @@ -36,9 +36,9 @@ namespace Barotrauma.Items.Components if (UseHSV) { Color hsvColor = ToolBox.HSVToRGB(signalR, signalG, signalB); - signalR = hsvColor.R / (float) byte.MaxValue; - signalG = hsvColor.G / (float) byte.MaxValue; - signalB = hsvColor.B / (float) byte.MaxValue; + signalR = hsvColor.R; + signalG = hsvColor.G; + signalB = hsvColor.B; } output = signalR.ToString("G", CultureInfo.InvariantCulture); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/FunctionComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/FunctionComponent.cs index 5d2a76a86..8d464e70a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/FunctionComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/FunctionComponent.cs @@ -36,9 +36,17 @@ namespace Barotrauma.Items.Components { case FunctionType.Round: value = MathF.Round(value); + if (value == -0) + { + value = 0; + } break; case FunctionType.Ceil: value = MathF.Ceiling(value); + if (value == -0) + { + value = 0; + } break; case FunctionType.Floor: value = MathF.Floor(value); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs index 1c4bb9b4f..22fd92e52 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs @@ -105,7 +105,7 @@ namespace Barotrauma.Items.Components switch (connection.Name) { case "set_text": - + case "signal_in": if (signal.value.Length > MaxMessageLength) { signal.value = signal.value.Substring(0, MaxMessageLength); @@ -128,6 +128,8 @@ namespace Barotrauma.Items.Components { history.ClearChildren(); } + + CreateFillerBlock(); #endif break; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs index 763800065..fb178b7ad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs @@ -100,7 +100,12 @@ namespace Barotrauma.Items.Components if (item.CurrentHull != null) { - int waterPercentage = MathHelper.Clamp((int)Math.Ceiling(item.CurrentHull.WaterPercentage), 0, 100); + int waterPercentage = 0; + //ignore minuscule amounts of water + if (item.CurrentHull.WaterVolume < 1.0f) + { + waterPercentage = MathHelper.Clamp((int)Math.Ceiling(item.CurrentHull.WaterPercentage), 0, 100); + } item.SendSignal(waterPercentage.ToString(), "water_%"); } string highPressureOut = (item.CurrentHull == null || item.CurrentHull.LethalPressure > 5.0f) ? "1" : "0"; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs index 06e12a07a..dc5194e40 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs @@ -112,7 +112,10 @@ namespace Barotrauma.Items.Components { return HasRequiredContainedItems(user: null, addMessage: false); } - + + /// + /// Returns the wifi components that can receive signals from this one + /// public IEnumerable GetReceiversInRange() { return list.Where(w => w != this && w.CanReceive(this)); @@ -121,11 +124,7 @@ namespace Barotrauma.Items.Components public bool CanReceive(WifiComponent sender) { if (sender == null || sender.channel != channel) { return false; } - - if (sender.TeamID != TeamID && !AllowCrossTeamCommunication) - { - return false; - } + if (sender.TeamID != TeamID && !AllowCrossTeamCommunication) { return false; } //if the component is not linked to chat and has nothing connected to the output, sending a signal to it does nothing // = no point in receiving @@ -142,6 +141,21 @@ namespace Barotrauma.Items.Components return HasRequiredContainedItems(user: null, addMessage: false); } + /// + /// Returns the wifi components that can transmit signals to this one + /// + public IEnumerable GetTransmittersInRange() + { + return list.Where(w => w != this && w.CanTransmit(this)); + } + + public bool CanTransmit(WifiComponent sender) + { + if (sender == null || sender.channel != channel) { return false; } + if (sender.TeamID != TeamID && !AllowCrossTeamCommunication) { return false; } + if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) { return false; } + return HasRequiredContainedItems(user: null, addMessage: false); + } public override void Update(float deltaTime, Camera cam) { chatMsgCooldown -= deltaTime; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs index 2b80a81c8..a3dae763f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs @@ -756,14 +756,17 @@ namespace Barotrauma.Items.Components { if (item.ParentInventory != null) { return; } #if CLIENT - if (!relativeToSub && Screen.Selected != GameMain.SubEditorScreen) { return; } + if (!relativeToSub) + { + if (Screen.Selected != GameMain.SubEditorScreen || (item.Submarine?.Loading ?? false)) { return; } + } #else if (!relativeToSub) { return; } #endif Vector2 refPos = item.Submarine == null ? Vector2.Zero : - item.Position - item.Submarine.HiddenSubPosition; + item.Position - item.Submarine.HiddenSubPosition; for (int i = 0; i < nodes.Count; i++) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 9c4770659..fdaca5d18 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -64,6 +64,8 @@ namespace Barotrauma.Items.Components private Character currentTarget; const float aiFindTargetInterval = 5.0f; + private int currentLoaderIndex; + private const float TinkeringPowerCostReduction = 0.2f; private const float TinkeringDamageIncrease = 0.2f; private const float TinkeringReloadDecrease = 0.2f; @@ -592,16 +594,17 @@ namespace Barotrauma.Items.Components } else { - foreach (MapEntity e in item.linkedTo) + for (int j = 0; j < item.linkedTo.Count; j++) { + var e = item.linkedTo[(j + currentLoaderIndex) % item.linkedTo.Count]; //use linked projectile containers in case they have to react to the turret being launched somehow //(play a sound, spawn more projectiles) if (!(e is Item linkedItem)) { continue; } if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; } - if (linkedItem.Condition <= 0.0f) + if (linkedItem.Condition <= 0.0f) { loaderBroken = true; - continue; + continue; } ItemContainer projectileContainer = linkedItem.GetComponent(); if (projectileContainer != null) @@ -610,7 +613,6 @@ namespace Barotrauma.Items.Components projectiles = GetLoadedProjectiles(); if (projectiles.Any()) { break; } } - } } if (projectiles.Count == 0 && !LaunchWithoutProjectile) @@ -705,6 +707,10 @@ namespace Barotrauma.Items.Components { ShiftItemsInProjectileContainer(container.GetComponent()); } + if (item.linkedTo.Count > 0) + { + currentLoaderIndex = (currentLoaderIndex + 1) % item.linkedTo.Count; + } } } @@ -759,6 +765,7 @@ namespace Barotrauma.Items.Components Projectile projectileComponent = projectile.GetComponent(); if (projectileComponent != null) { + projectileComponent.Launcher = item; projectileComponent.Attacker = projectileComponent.User = user; if (projectileComponent.Attack != null) { @@ -1163,7 +1170,7 @@ namespace Barotrauma.Items.Components { targetPos = closestEnemy.WorldPosition; //if the enemy is inside another sub, aim at the room they're in to make it less obvious that the enemy "knows" exactly where the target is - if (closestEnemy.Submarine != null && closestEnemy.CurrentHull != null && closestEnemy.Submarine != item.Submarine) + if (closestEnemy.Submarine != null && closestEnemy.CurrentHull != null && closestEnemy.Submarine != item.Submarine && !closestEnemy.CanSeeTarget(Item)) { targetPos = closestEnemy.CurrentHull.WorldPosition; } @@ -1442,8 +1449,9 @@ namespace Barotrauma.Items.Components List projectiles = new List(); // check the item itself first CheckProjectileContainer(item, projectiles, out bool _); - foreach (MapEntity e in item.linkedTo) + for (int j = 0; j < item.linkedTo.Count; j++) { + var e = item.linkedTo[(j + currentLoaderIndex) % item.linkedTo.Count]; if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; } if (e is Item projectileContainer) { @@ -1451,7 +1459,6 @@ namespace Barotrauma.Items.Components if (projectiles.Any() || stopSearching) { return projectiles; } } } - return projectiles; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index 32a31cfe5..dbfdcf3b7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -480,7 +480,7 @@ namespace Barotrauma if (i < 0 || i >= slots.Length) { string errorMsg = "Inventory.TryPutItem failed: index was out of range(" + i + ").\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("Inventory.TryPutItem:IndexOutOfRange", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Inventory.TryPutItem:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return false; } @@ -531,7 +531,7 @@ namespace Barotrauma if (i < 0 || i >= slots.Length) { string errorMsg = "Inventory.PutItem failed: index was out of range(" + i + ").\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("Inventory.PutItem:IndexOutOfRange", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Inventory.PutItem:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index bcc30233d..c1c6c53e8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -26,7 +26,7 @@ namespace Barotrauma public static bool ShowLinks = true; - private HashSet tags; + private readonly HashSet tags; private bool isWire, isLogic; @@ -40,6 +40,10 @@ namespace Barotrauma } } + public float HullOxygenPercentage + { + get { return CurrentHull?.OxygenPercentage ?? 0.0f; } + } private CampaignMode.InteractionType campaignInteractionType = CampaignMode.InteractionType.None; public CampaignMode.InteractionType CampaignInteractionType @@ -64,13 +68,13 @@ namespace Barotrauma #endif //components that determine the functionality of the item - private Dictionary componentsByType = new Dictionary(); - private List components; + private readonly Dictionary componentsByType = new Dictionary(); + private readonly List components; /// /// Components that are Active or need to be updated for some other reason (status effects, sounds) /// private readonly List updateableComponents = new List(); - private List drawableComponents; + private readonly List drawableComponents; private bool hasComponentsToDraw; public PhysicsBody body; @@ -99,7 +103,7 @@ namespace Barotrauma private readonly List repairables; - private Quality qualityComponent; + private readonly Quality qualityComponent; private readonly Queue impactQueue = new Queue(); @@ -358,7 +362,6 @@ namespace Barotrauma protected set; } - [Serialize("", false)] /// /// Can be used by status effects or conditionals to check what item this item is contained inside /// @@ -371,7 +374,6 @@ namespace Barotrauma ParentInventory?.Owner?.ToString() ?? ""; } - set { /*do nothing*/ } } @@ -392,7 +394,6 @@ namespace Barotrauma } } - [Serialize(false, false)] /// /// Can be used by status effects or conditionals to check if the physics body of the item is active /// @@ -402,7 +403,6 @@ namespace Barotrauma { return body != null && body.Enabled; } - set { /*do nothing*/ } } [Serialize(0.0f, false)] @@ -540,9 +540,19 @@ namespace Barotrauma isActive = true; } } + + LastConditionChange = condition - prev; + ConditionLastUpdated = Timing.TotalTime; } } + private double ConditionLastUpdated { get; set; } + private float LastConditionChange { get; set; } + /// + /// Return true if the condition of this item increased within the last second. + /// + public bool ConditionIncreasedRecently => (Timing.TotalTime < ConditionLastUpdated + 1.0f) && LastConditionChange > 0.0f; + public float Health { get { return condition; } @@ -998,7 +1008,8 @@ namespace Barotrauma partial void InitProjSpecific(); - public bool IsContainerPreferred(ItemContainer container, out bool isPreferencesDefined, out bool isSecondary) => Prefab.IsContainerPreferred(this, container, out isPreferencesDefined, out isSecondary); + public bool IsContainerPreferred(ItemContainer container, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRestriction = false) + => Prefab.IsContainerPreferred(this, container, out isPreferencesDefined, out isSecondary, requireConditionRestriction); public override MapEntity Clone() { @@ -1018,7 +1029,7 @@ namespace Barotrauma errorMsg += "Original components: " + string.Join(", ", components.Select(c => c.GetType().ToString())); errorMsg += ", cloned components: " + string.Join(", ", clone.components.Select(c => c.GetType().ToString())); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Item.Clone:" + Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.Clone:" + Name, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } for (int i = 0; i < components.Count && i < clone.components.Count; i++) @@ -1190,7 +1201,7 @@ namespace Barotrauma DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "Item.SetPosition:InvalidPosition" + ID, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } @@ -2014,7 +2025,7 @@ namespace Barotrauma var wifiComponent = recipient.Item.GetComponent(); if (wifiComponent != null && wifiComponent.CanTransmit()) { - foreach (var wifiReceiver in wifiComponent.GetReceiversInRange()) + foreach (var wifiReceiver in wifiComponent.GetTransmittersInRange()) { var receiverConnections = wifiReceiver.Item.Connections; if (receiverConnections == null) { continue; } @@ -2171,7 +2182,7 @@ namespace Barotrauma return c != null && c.Character != null && c.Character.CanInteractWith(this); } - public bool TryInteract(Character picker, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceActionKey = false) + public bool TryInteract(Character user, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceUseKey = false) { if (CampaignInteractionType != CampaignMode.InteractionType.None) { return false; } @@ -2181,12 +2192,11 @@ namespace Barotrauma Skill requiredSkill = null; float skillMultiplier = 1; #endif - if (!IsInteractable(picker)) { return false; } + if (!IsInteractable(user)) { return false; } foreach (ItemComponent ic in components) { bool pickHit = false, selectHit = false; - - if (picker.IsKeyDown(InputType.Aim)) + if (user.IsKeyDown(InputType.Aim)) { pickHit = false; selectHit = false; @@ -2195,26 +2205,44 @@ namespace Barotrauma { if (forceSelectKey) { - if (ic.PickKey == InputType.Select) pickHit = true; - if (ic.SelectKey == InputType.Select) selectHit = true; + if (ic.PickKey == InputType.Select) + { + pickHit = true; + } + if (ic.SelectKey == InputType.Select) + { + selectHit = true; + } } - else if (forceActionKey) + else if (forceUseKey) { - if (ic.PickKey == InputType.Use) pickHit = true; - if (ic.SelectKey == InputType.Use) selectHit = true; + if (ic.PickKey == InputType.Use) + { + pickHit = true; + } + if (ic.SelectKey == InputType.Use) + { + selectHit = true; + } } else { - pickHit = picker.IsKeyHit(ic.PickKey); - selectHit = picker.IsKeyHit(ic.SelectKey); + pickHit = user.IsKeyHit(ic.PickKey); + selectHit = user.IsKeyHit(ic.SelectKey); #if CLIENT //if the cursor is on a UI component, disable interaction with the left mouse button //to prevent accidentally selecting items when clicking UI elements - if (picker == Character.Controlled && GUI.MouseOn != null) + if (user == Character.Controlled && GUI.MouseOn != null) { - if (GameMain.Config.KeyBind(ic.PickKey).MouseButton == 0) pickHit = false; - if (GameMain.Config.KeyBind(ic.SelectKey).MouseButton == 0) selectHit = false; + if (GameMain.Config.KeyBind(ic.PickKey).MouseButton == 0) + { + pickHit = false; + } + if (GameMain.Config.KeyBind(ic.SelectKey).MouseButton == 0) + { + selectHit = false; + } } #endif } @@ -2225,50 +2253,50 @@ namespace Barotrauma if (Screen.Selected == GameMain.SubEditorScreen && GameMain.SubEditorScreen.WiringMode) { pickHit = selectHit = GameMain.Config.KeyBind(InputType.Use).MouseButton == MouseButton.None ? - picker.IsKeyHit(InputType.Use) : - picker.IsKeyHit(InputType.Select); + user.IsKeyHit(InputType.Use) : + user.IsKeyHit(InputType.Select); } #endif if (!pickHit && !selectHit) { continue; } bool showUiMsg = false; #if CLIENT - if (!ic.HasRequiredSkills(picker, out Skill tempRequiredSkill)) { hasRequiredSkills = false; skillMultiplier = ic.GetSkillMultiplier(); } - showUiMsg = picker == Character.Controlled && Screen.Selected != GameMain.SubEditorScreen; + if (!ic.HasRequiredSkills(user, out Skill tempRequiredSkill)) { hasRequiredSkills = false; skillMultiplier = ic.GetSkillMultiplier(); } + showUiMsg = user == Character.Controlled && Screen.Selected != GameMain.SubEditorScreen; #endif - if (!ignoreRequiredItems && !ic.HasRequiredItems(picker, showUiMsg)) { continue; } - if ((ic.CanBePicked && pickHit && ic.Pick(picker)) || - (ic.CanBeSelected && selectHit && ic.Select(picker))) + if (!ignoreRequiredItems && !ic.HasRequiredItems(user, showUiMsg)) { continue; } + if ((ic.CanBePicked && pickHit && ic.Pick(user)) || + (ic.CanBeSelected && selectHit && ic.Select(user))) { picked = true; - ic.ApplyStatusEffects(ActionType.OnPicked, 1.0f, picker); + ic.ApplyStatusEffects(ActionType.OnPicked, 1.0f, user); #if CLIENT - if (picker == Character.Controlled) { GUI.ForceMouseOn(null); } + if (user == Character.Controlled) { GUI.ForceMouseOn(null); } if (tempRequiredSkill != null) { requiredSkill = tempRequiredSkill; } #endif - if (ic.CanBeSelected) { selected = true; } + if (ic.CanBeSelected && !(ic is Door)) { selected = true; } } } if (!picked) { return false; } - if (picker != null) + if (user != null) { - if (picker.SelectedConstruction == this) + if (user.SelectedConstruction == this) { - if (picker.IsKeyHit(InputType.Select) || forceSelectKey) + if (user.IsKeyHit(InputType.Select) || forceSelectKey) { - picker.SelectedConstruction = null; + user.SelectedConstruction = null; } } else if (selected) { - picker.SelectedConstruction = this; + user.SelectedConstruction = this; } } #if CLIENT - if (!hasRequiredSkills && Character.Controlled == picker && Screen.Selected != GameMain.SubEditorScreen) + if (!hasRequiredSkills && Character.Controlled == user && Screen.Selected != GameMain.SubEditorScreen) { if (requiredSkill != null) { @@ -2278,7 +2306,10 @@ namespace Barotrauma } #endif - if (Container != null) Container.RemoveContained(this); + if (Container != null) + { + Container.RemoveContained(this); + } return true; } @@ -2553,21 +2584,21 @@ namespace Barotrauma } object value = property.GetValue(propertyOwner.First); - if (value is string) + if (value is string stringVal) { - msg.Write((string)value); + msg.Write(stringVal); } - else if (value is float) + else if (value is float floatVal) { - msg.Write((float)value); + msg.Write(floatVal); } - else if (value is int) + else if (value is int intVal) { - msg.Write((int)value); + msg.Write(intVal); } - else if (value is bool) + else if (value is bool boolVal) { - msg.Write((bool)value); + msg.Write(boolVal); } else if (value is Color color) { @@ -2576,35 +2607,35 @@ namespace Barotrauma msg.Write(color.B); msg.Write(color.A); } - else if (value is Vector2) + else if (value is Vector2 vector2) { - msg.Write(((Vector2)value).X); - msg.Write(((Vector2)value).Y); + msg.Write(vector2.X); + msg.Write(vector2.Y); } - else if (value is Vector3) + else if (value is Vector3 vector3) { - msg.Write(((Vector3)value).X); - msg.Write(((Vector3)value).Y); - msg.Write(((Vector3)value).Z); + msg.Write(vector3.X); + msg.Write(vector3.Y); + msg.Write(vector3.Z); } - else if (value is Vector4) + else if (value is Vector4 vector4) { - msg.Write(((Vector4)value).X); - msg.Write(((Vector4)value).Y); - msg.Write(((Vector4)value).Z); - msg.Write(((Vector4)value).W); + msg.Write(vector4.X); + msg.Write(vector4.Y); + msg.Write(vector4.Z); + msg.Write(vector4.W); } - else if (value is Point) + else if (value is Point point) { - msg.Write(((Point)value).X); - msg.Write(((Point)value).Y); + msg.Write(point.X); + msg.Write(point.Y); } - else if (value is Rectangle) + else if (value is Rectangle rect) { - msg.Write(((Rectangle)value).X); - msg.Write(((Rectangle)value).Y); - msg.Write(((Rectangle)value).Width); - msg.Write(((Rectangle)value).Height); + msg.Write(rect.X); + msg.Write(rect.Y); + msg.Write(rect.Width); + msg.Write(rect.Height); } else if (value is Enum) { @@ -2756,7 +2787,7 @@ namespace Barotrauma #endif GameAnalyticsManager.AddErrorEventOnce( "Item.ReadPropertyChange:" + Name + ":" + type, - GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, + GameAnalyticsManager.ErrorSeverity.Warning, "Failed to convert the int value \"" + intVal + "\" to " + type + " (item " + Name + ")"); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index 3870e883a..7df7912f9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs @@ -141,7 +141,7 @@ namespace Barotrauma DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "ItemInventory.CreateServerEvent:EventForUninitializedItem" + container.Item.Name + container.Item.ID, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 5090ae9ea..dfec46927 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -138,7 +138,7 @@ namespace Barotrauma float maxCondition = subElement.GetAttributeFloat("maxcondition", 1.0f); //Substract mincondition from required item's condition or delete it regardless? bool useCondition = subElement.GetAttributeBool("usecondition", true); - int count = subElement.GetAttributeInt("count", 1); + int amount = subElement.GetAttributeInt("count", subElement.GetAttributeInt("amount", 1)); if (!string.IsNullOrEmpty(requiredItemIdentifier)) { @@ -153,11 +153,11 @@ namespace Barotrauma MathUtils.NearlyEqual(r.MinCondition, minCondition) && MathUtils.NearlyEqual(r.MaxCondition, maxCondition)); if (existing == null) { - RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, maxCondition, useCondition)); + RequiredItems.Add(new RequiredItem(requiredItem, amount, minCondition, maxCondition, useCondition)); } else { - existing.Amount += count; + existing.Amount += amount; } } else @@ -175,11 +175,11 @@ namespace Barotrauma MathUtils.NearlyEqual(r.MaxCondition, maxCondition)); if (existing == null) { - RequiredItems.Add(new RequiredItem(matchingItems, count, minCondition, maxCondition, useCondition)); + RequiredItems.Add(new RequiredItem(matchingItems, amount, minCondition, maxCondition, useCondition)); } else { - existing.Amount += count; + existing.Amount += amount; } } break; @@ -549,6 +549,13 @@ namespace Barotrauma private set; } + [Serialize(0.0f, false)] + public float AddedPickingSpeedMultiplier + { + get; + private set; + } + [Serialize(false, false)] public bool CannotRepairFail { @@ -1298,21 +1305,24 @@ namespace Barotrauma public ImmutableDictionary GetBuyPricesUnder(int maxCost = 0) { Dictionary priceLocations = new Dictionary(); - foreach (KeyValuePair locationPrice in locationPrices) + if (locationPrices != null) { - PriceInfo priceInfo = locationPrice.Value; + foreach (KeyValuePair locationPrice in locationPrices) + { + PriceInfo priceInfo = locationPrice.Value; - if (priceInfo == null) - { - continue; - } - if (!priceInfo.CanBeBought) - { - continue; - } - if (priceInfo.Price < maxCost || maxCost == 0) - { - priceLocations.Add(locationPrice.Key, priceInfo); + if (priceInfo == null) + { + continue; + } + if (!priceInfo.CanBeBought) + { + continue; + } + if (priceInfo.Price < maxCost || maxCost == 0) + { + priceLocations.Add(locationPrice.Key, priceInfo); + } } } return priceLocations.ToImmutableDictionary(); @@ -1343,17 +1353,18 @@ namespace Barotrauma return priceLocations.ToImmutableDictionary(); } - public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary) + public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement = false) { isPreferencesDefined = PreferredContainers.Any(); isSecondary = false; if (!isPreferencesDefined) { return true; } - if (PreferredContainers.Any(pc => IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Primary, targetContainer))) + if (PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Primary, targetContainer))) { return true; } isSecondary = true; - return PreferredContainers.Any(pc => IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Secondary, targetContainer)); + return PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Secondary, targetContainer)); + static bool HasConditionRequirement(PreferredContainer pc) => pc.MinCondition > 0 || pc.MaxCondition < 100; } public bool IsContainerPreferred(Item item, string[] identifiersOrTags, out bool isPreferencesDefined, out bool isSecondary) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs index 27bd6bcde..4c356b657 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs @@ -159,7 +159,7 @@ namespace Barotrauma DebugConsole.ThrowError($"Error while removing entity \"{e}\"", exception); GameAnalyticsManager.AddErrorEventOnce( $"Entity.RemoveAll:Exception{e}", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, $"Error while removing entity \"{e} ({exception.Message})\n{exception.StackTrace.CleanupStackTrace()}"); } } @@ -223,7 +223,7 @@ namespace Barotrauma { DebugConsole.ThrowError(errorLine); } - GameAnalyticsManager.AddErrorEventOnce("Entity.RemoveAll", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg.ToString()); + GameAnalyticsManager.AddErrorEventOnce("Entity.RemoveAll", GameAnalyticsManager.ErrorSeverity.Error, errorMsg.ToString()); } dictionary.Clear(); @@ -243,14 +243,14 @@ namespace Barotrauma DebugConsole.ThrowError($"Entity {ToString()} ({ID}) not present in entity dictionary."); GameAnalyticsManager.AddErrorEventOnce( $"Entity.FreeID:EntityNotFound{ID}", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, $"Entity {ToString()} ({ID}) not present in entity dictionary.\n{Environment.StackTrace.CleanupStackTrace()}"); } else if (existingEntity != this) { DebugConsole.ThrowError($"Entity ID mismatch in entity dictionary. Entity {existingEntity} had the ID {ID} (expecting {ToString()})"); GameAnalyticsManager.AddErrorEventOnce("Entity.FreeID:EntityMismatch" + ID, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, $"Entity ID mismatch in entity dictionary. Entity {existingEntity} had the ID {ID} (expecting {ToString()})"); } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index 4268507a7..6993379c2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -1575,7 +1575,7 @@ namespace Barotrauma { string errorMsg = "Error - tried to save a hull that's not a part of any submarine.\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Hull.Save:WorldHull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Hull.Save:WorldHull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/CaveGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/CaveGenerator.cs index 3eb478afb..8d1c11c4b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/CaveGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/CaveGenerator.cs @@ -440,7 +440,7 @@ namespace Barotrauma DebugConsole.ThrowError("Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + ")"); GameAnalyticsManager.AddErrorEventOnce( "CaveGenerator.GeneratePolygons:InvalidTriangle", - GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, + GameAnalyticsManager.ErrorSeverity.Warning, "Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + "). Seed: " + level.Seed); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 81ba3d49b..94b486cc2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -954,6 +954,13 @@ namespace Barotrauma { for (int i = 0; i < cavePathCells.Count; i++) { + var connectingEdge = i > 0 ? cavePathCells[i].Edges.Find(e => e.AdjacentCell(cavePathCells[i]) == cavePathCells[i - 1]) : null; + if (connectingEdge != null) + { + var edgeWayPoint = new WayPoint(connectingEdge.Center, SpawnType.Path, submarine: null); + ConnectWaypoints(prevWp, edgeWayPoint, 500.0f); + prevWp = edgeWayPoint; + } var newWaypoint = new WayPoint(cavePathCells[i].Center, SpawnType.Path, submarine: null); ConnectWaypoints(prevWp, newWaypoint, 500.0f); prevWp = newWaypoint; @@ -1382,6 +1389,7 @@ namespace Barotrauma if (tunnel.Cells.Count == 0) { return; } List wayPoints = new List(); + WayPoint prevWayPoint = null; for (int i = 0; i < tunnel.Cells.Count; i++) { tunnel.Cells[i].CellType = CellType.Path; @@ -1391,10 +1399,58 @@ namespace Barotrauma }; wayPoints.Add(newWaypoint); - if (wayPoints.Count > 1) + if (prevWayPoint != null) { - wayPoints[wayPoints.Count - 2].ConnectTo(newWaypoint); + bool solidCellBetween = false; + foreach (GraphEdge edge in tunnel.Cells[i].Edges) + { + if (edge.AdjacentCell(tunnel.Cells[i])?.CellType == CellType.Solid && + MathUtils.LinesIntersect(newWaypoint.WorldPosition, prevWayPoint.WorldPosition, edge.Point1, edge.Point2)) + { + solidCellBetween = true; + break; + } + } + + if (solidCellBetween) + { + //something between the previous waypoint and this one + // -> find the edge that connects the cells and place a waypoint there, instead of connecting the centers of the cells directly + var edgeBetweenCells = tunnel.Cells[i].Edges.Find(e => e.AdjacentCell(tunnel.Cells[i]) == tunnel.Cells[i - 1]); + if (edgeBetweenCells != null) + { + var edgeWaypoint = new WayPoint(new Rectangle((int)edgeBetweenCells.Center.X, (int)edgeBetweenCells.Center.Y, 10, 10), null) + { + Tunnel = tunnel + }; + prevWayPoint.ConnectTo(edgeWaypoint); + prevWayPoint = edgeWaypoint; + } + } + prevWayPoint.ConnectTo(newWaypoint); + + //look back at the tunnel cells before the previous one, and see if the current cell shares edges with them + //= if we can "skip" from cell #1 to cell #3, create a waypoint between them. + //Fixes there sometimes not being a path past a destructible ice chunk even if there's space to go past it. + for (int j = i - 2; j > 0 && j > i - 5; j--) + { + foreach (GraphEdge edge in tunnel.Cells[i].Edges) + { + if (Vector2.DistanceSquared(edge.Point1, edge.Point2) < 30.0f * 30.0f) { continue; } + if (!edge.IsSolid && edge.AdjacentCell(tunnel.Cells[i]) == tunnel.Cells[j]) + { + var edgeWaypoint = new WayPoint(new Rectangle((int)edge.Center.X, (int)edge.Center.Y, 10, 10), null) + { + Tunnel = tunnel + }; + wayPoints[j].ConnectTo(edgeWaypoint); + edgeWaypoint.ConnectTo(newWaypoint); + break; + } + } + } } + prevWayPoint = newWaypoint; } tunnel.WayPoints.AddRange(wayPoints); @@ -1954,6 +2010,8 @@ namespace Barotrauma wayPoints.RemoveAt(i); } + Debug.Assert(wayPoints.Any(), "Couldn't generate waypoints around ruins."); + //connect ruin entrances to the outside waypoints foreach (Gap g in Gap.GapList) { @@ -1997,6 +2055,13 @@ namespace Barotrauma { for (int i = 0; i < ruin.PathCells.Count; i++) { + var connectingEdge = i > 0 ? ruin.PathCells[i].Edges.Find(e => e.AdjacentCell(ruin.PathCells[i]) == ruin.PathCells[i - 1]) : null; + if (connectingEdge != null) + { + var edgeWayPoint = new WayPoint(connectingEdge.Center, SpawnType.Path, submarine: null); + ConnectWaypoints(prevWp, edgeWayPoint, outSideWaypointInterval); + prevWp = edgeWayPoint; + } var newWaypoint = new WayPoint(ruin.PathCells[i].Center, SpawnType.Path, submarine: null); ConnectWaypoints(prevWp, newWaypoint, outSideWaypointInterval); prevWp = newWaypoint; @@ -2947,7 +3012,7 @@ namespace Barotrauma if (!suitablePositions.Any()) { string errorMsg = "Could not find a suitable position of interest. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:PositionTypeNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:PositionTypeNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #endif @@ -2972,7 +3037,7 @@ namespace Barotrauma if (!farEnoughPositions.Any()) { string errorMsg = "Could not find a position of interest far enough from the submarines. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:TooCloseToSubs", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:TooCloseToSubs", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #endif @@ -3084,7 +3149,25 @@ namespace Barotrauma foreach (LevelWall wall in ExtraWalls) { - if (wall is DestructibleLevelWall destructibleWall && destructibleWall.Destroyed) { continue; } + if (wall == SeaFloor) + { + if (SeaFloorTopPos < worldPos.Y - searchDepth * GridCellSize) { continue; } + } + else + { + if (wall is DestructibleLevelWall destructibleWall && destructibleWall.Destroyed) { continue; } + bool closeEnough = false; + foreach (VoronoiCell cell in wall.Cells) + { + if (Math.Abs(cell.Center.X - worldPos.X) < (searchDepth + 1) * GridCellSize && + Math.Abs(cell.Center.Y - worldPos.Y) < (searchDepth + 1) * GridCellSize) + { + closeEnough = true; + break; + } + } + if (!closeEnough) { continue; } + } foreach (VoronoiCell cell in wall.Cells) { tempCells.Add(cell); @@ -3739,7 +3822,7 @@ namespace Barotrauma subDockingPortOffset = MathHelper.Clamp(subDockingPortOffset, -5000.0f, 5000.0f); string warningMsg = "Docking port very far from the sub's center of mass (submarine: " + Submarine.MainSub.Info.Name + ", dist: " + subDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible."; DebugConsole.NewMessage(warningMsg, Color.Orange); - GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Info.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg); + GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg); } float outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X; @@ -3749,7 +3832,7 @@ namespace Barotrauma outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset, -5000.0f, 5000.0f); string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible."; DebugConsole.NewMessage(warningMsg, Color.Orange); - GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg); + GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg); } Vector2 spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, subDockingPortOffset - outpostDockingPortOffset, verticalMoveDir: 1); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index 7609cf623..d4a2ba6e5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -764,7 +764,7 @@ namespace Barotrauma { string errorMsg = "Failed to select a location. " + (location?.Name ?? "null") + " not found in the map."; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Map.SelectLocation:LocationNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Map.SelectLocation:LocationNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } @@ -783,7 +783,7 @@ namespace Barotrauma { string errorMsg = "Failed to select a mission (current location not set)."; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Map.SelectMission:CurrentLocationNotSet", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Map.SelectMission:CurrentLocationNotSet", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index b14ee383e..c53c98b81 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -386,7 +386,7 @@ namespace Barotrauma DebugConsole.ThrowError("Cloning entity \"" + e.Name + "\" failed.", ex); GameAnalyticsManager.AddErrorEventOnce( "MapEntity.Clone:" + e.Name, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Cloning entity \"" + e.Name + "\" failed (" + ex.Message + ").\n" + ex.StackTrace.CleanupStackTrace()); return clones; } @@ -447,7 +447,7 @@ namespace Barotrauma { DebugConsole.ThrowError("Error while cloning wires - item \"" + connectedItem.Name + "\" was not found in entities to clone."); GameAnalyticsManager.AddErrorEventOnce("MapEntity.Clone:ConnectedNotFound" + connectedItem.ID, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Error while cloning wires - item \"" + connectedItem.Name + "\" was not found in entities to clone."); continue; } @@ -458,7 +458,7 @@ namespace Barotrauma { DebugConsole.ThrowError("Error while cloning wires - connection \"" + originalWire.Connections[n].Name + "\" was not found in connected item \"" + connectedItem.Name + "\"."); GameAnalyticsManager.AddErrorEventOnce("MapEntity.Clone:ConnectionNotFound" + connectedItem.ID, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Error while cloning wires - connection \"" + originalWire.Connections[n].Name + "\" was not found in connected item \"" + connectedItem.Name + "\"."); continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index 2907db5ce..9169f06e4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -1407,7 +1407,7 @@ namespace Barotrauma { string errorMsg = $"Error while loading structure \"{s.Name}\". Section damage index out of bounds. Index: {index}, section count: {s.SectionCount}."; DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Structure.Load:SectionIndexOutOfBounds", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Structure.Load:SectionIndexOutOfBounds", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index 7084f3ada..9dad1fd8d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -137,6 +137,9 @@ namespace Barotrauma get { return subBody?.Body; } } + /// + /// Extents of the solid items/structures (ones with a physics body) and hulls + /// public Rectangle Borders { get @@ -145,6 +148,17 @@ namespace Barotrauma } } + /// + /// Extents of all the visible items/structures/hulls (including ones without a physics body) + /// + public Rectangle VisibleBorders + { + get + { + return subBody == null ? Rectangle.Empty : subBody.VisibleBorders; + } + } + public override Vector2 Position { get { return subBody == null ? Vector2.Zero : subBody.Position - HiddenSubPosition; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index 18443c725..913d9e2cf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -68,12 +68,24 @@ namespace Barotrauma } } + /// + /// Extents of the solid items/structures (ones with a physics body) and hulls + /// public Rectangle Borders { get; private set; } - + + /// + /// Extents of all the visible items/structures/hulls (including ones without a physics body) + /// + public Rectangle VisibleBorders + { + get; + private set; + } + public Vector2 Velocity { get { return Body.LinearVelocity; } @@ -122,14 +134,21 @@ namespace Barotrauma HullVertices = convexHull; Vector2 minExtents = Vector2.Zero, maxExtents = Vector2.Zero; + Vector2 visibleMinExtents = Vector2.Zero, visibleMaxExtents = Vector2.Zero; farseerBody = GameMain.World.CreateBody(); farseerBody.UserData = this; - foreach (Structure wall in Structure.WallList) + foreach (var mapEntity in MapEntity.mapEntityList) { - if (wall.Submarine != submarine || wall.IsPlatform) { continue; } + if (mapEntity.Submarine != submarine || !(mapEntity is Structure wall)) { continue; } Rectangle rect = wall.Rect; + visibleMinExtents.X = Math.Min(rect.X, visibleMinExtents.X); + visibleMinExtents.Y = Math.Min(rect.Y - rect.Height, visibleMinExtents.Y); + visibleMaxExtents.X = Math.Max(rect.Right, visibleMaxExtents.X); + visibleMaxExtents.Y = Math.Max(rect.Y, visibleMaxExtents.Y); + + if (!wall.HasBody || wall.IsPlatform || wall.StairDirection != Direction.None) { continue; } farseerBody.CreateRectangle( ConvertUnits.ToSimUnits(wall.BodyWidth), @@ -138,10 +157,10 @@ namespace Barotrauma -wall.BodyRotation, ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + wall.BodyOffset)).UserData = wall; - minExtents.X = Math.Min(rect.X, minExtents.X); - minExtents.Y = Math.Min(rect.Y - rect.Height, minExtents.Y); - maxExtents.X = Math.Max(rect.Right, maxExtents.X); - maxExtents.Y = Math.Max(rect.Y, maxExtents.Y); + minExtents.X = Math.Min(visibleMinExtents.X, minExtents.X); + minExtents.Y = Math.Min(visibleMinExtents.Y, minExtents.Y); + maxExtents.X = Math.Max(visibleMaxExtents.X, maxExtents.X); + maxExtents.Y = Math.Max(visibleMaxExtents.Y, maxExtents.Y); } foreach (Hull hull in Hull.hullList) @@ -155,14 +174,20 @@ namespace Barotrauma 100.0f, ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2))).UserData = hull; - minExtents.X = Math.Min(rect.X, minExtents.X); - minExtents.Y = Math.Min(rect.Y - rect.Height, minExtents.Y); - maxExtents.X = Math.Max(rect.Right, maxExtents.X); - maxExtents.Y = Math.Max(rect.Y, maxExtents.Y); + visibleMinExtents.X = Math.Min(rect.X, visibleMinExtents.X); + visibleMinExtents.Y = Math.Min(rect.Y - rect.Height, visibleMinExtents.Y); + visibleMaxExtents.X = Math.Max(rect.Right, visibleMaxExtents.X); + visibleMaxExtents.Y = Math.Max(rect.Y, visibleMaxExtents.Y); + + minExtents.X = Math.Min(visibleMinExtents.X, minExtents.X); + minExtents.Y = Math.Min(visibleMinExtents.Y, minExtents.Y); + maxExtents.X = Math.Max(visibleMaxExtents.X, maxExtents.X); + maxExtents.Y = Math.Max(visibleMaxExtents.Y, maxExtents.Y); } foreach (Item item in Item.ItemList) { + if (item.Submarine != submarine) { continue; } if (item.StaticBodyConfig == null || item.Submarine != submarine) { continue; } float radius = item.StaticBodyConfig.GetAttributeFloat("radius", 0.0f) * item.Scale; @@ -183,43 +208,48 @@ namespace Barotrauma { item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos)); - minExtents.X = Math.Min(item.Position.X - width / 2, minExtents.X); - minExtents.Y = Math.Min(item.Position.Y - height / 2, minExtents.Y); - maxExtents.X = Math.Max(item.Position.X + width / 2, maxExtents.X); - maxExtents.Y = Math.Max(item.Position.Y + height / 2, maxExtents.Y); + visibleMinExtents.X = Math.Min(item.Position.X - width / 2, visibleMinExtents.X); + visibleMinExtents.Y = Math.Min(item.Position.Y - height / 2, visibleMinExtents.Y); + visibleMaxExtents.X = Math.Max(item.Position.X + width / 2, visibleMaxExtents.X); + visibleMaxExtents.Y = Math.Max(item.Position.Y + height / 2, visibleMaxExtents.Y); } else if (radius > 0.0f && width > 0.0f) { item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos)); item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2)); item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2)); - minExtents.X = Math.Min(item.Position.X - width / 2 - radius, minExtents.X); - minExtents.Y = Math.Min(item.Position.Y - radius, minExtents.Y); - maxExtents.X = Math.Max(item.Position.X + width / 2 + radius, maxExtents.X); - maxExtents.Y = Math.Max(item.Position.Y + radius, maxExtents.Y); + visibleMinExtents.X = Math.Min(item.Position.X - width / 2 - radius, visibleMinExtents.X); + visibleMinExtents.Y = Math.Min(item.Position.Y - radius, visibleMinExtents.Y); + visibleMaxExtents.X = Math.Max(item.Position.X + width / 2 + radius, visibleMaxExtents.X); + visibleMaxExtents.Y = Math.Max(item.Position.Y + radius, visibleMaxExtents.Y); } else if (radius > 0.0f && height > 0.0f) { item.StaticFixtures.Add(farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos)); item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2)); item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simHeight / 2)); - minExtents.X = Math.Min(item.Position.X - radius, minExtents.X); - minExtents.Y = Math.Min(item.Position.Y - height / 2 - radius, minExtents.Y); - maxExtents.X = Math.Max(item.Position.X + radius, maxExtents.X); - maxExtents.Y = Math.Max(item.Position.Y + height / 2 + radius, maxExtents.Y); + visibleMinExtents.X = Math.Min(item.Position.X - radius, visibleMinExtents.X); + visibleMinExtents.Y = Math.Min(item.Position.Y - height / 2 - radius, visibleMinExtents.Y); + visibleMaxExtents.X = Math.Max(item.Position.X + radius, visibleMaxExtents.X); + visibleMaxExtents.Y = Math.Max(item.Position.Y + height / 2 + radius, visibleMaxExtents.Y); } else if (radius > 0.0f) { item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos)); - minExtents.X = Math.Min(item.Position.X - radius, minExtents.X); - minExtents.Y = Math.Min(item.Position.Y - radius, minExtents.Y); - maxExtents.X = Math.Max(item.Position.X + radius, maxExtents.X); - maxExtents.Y = Math.Max(item.Position.Y + radius, maxExtents.Y); + visibleMinExtents.X = Math.Min(item.Position.X - radius, visibleMinExtents.X); + visibleMinExtents.Y = Math.Min(item.Position.Y - radius, visibleMinExtents.Y); + visibleMaxExtents.X = Math.Max(item.Position.X + radius, visibleMaxExtents.X); + visibleMaxExtents.Y = Math.Max(item.Position.Y + radius, visibleMaxExtents.Y); } item.StaticFixtures.ForEach(f => f.UserData = item); + minExtents.X = Math.Min(visibleMinExtents.X, minExtents.X); + minExtents.Y = Math.Min(visibleMinExtents.Y, minExtents.Y); + maxExtents.X = Math.Max(visibleMaxExtents.X, maxExtents.X); + maxExtents.Y = Math.Max(visibleMaxExtents.Y, maxExtents.Y); } Borders = new Rectangle((int)minExtents.X, (int)maxExtents.Y, (int)(maxExtents.X - minExtents.X), (int)(maxExtents.Y - minExtents.Y)); + VisibleBorders = new Rectangle((int)visibleMinExtents.X, (int)visibleMaxExtents.Y, (int)(visibleMaxExtents.X - visibleMinExtents.X), (int)(visibleMaxExtents.Y - visibleMinExtents.Y)); } farseerBody.BodyType = BodyType.Dynamic; @@ -660,7 +690,7 @@ namespace Barotrauma { GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.HandleLimbCollision:" + submarine.ID, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Invalid velocity change in SubmarineBody.HandleLimbCollision (submarine velocity: " + Body.LinearVelocity + ", avgContactNormal: " + avgContactNormal + ", contactDot: " + contactDot @@ -835,7 +865,7 @@ namespace Barotrauma if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.ApplyImpact:InvalidImpulse", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs index d43ef274d..c5095ed42 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs @@ -1,39 +1,35 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; -using Barotrauma.IO; -using System.IO.Pipes; +using System.Collections.Concurrent; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; +using Barotrauma.Extensions; + +#if SERVER +using PipeType = System.IO.Pipes.AnonymousPipeClientStream; +#else +using PipeType = System.IO.Pipes.AnonymousPipeServerStream; +#endif namespace Barotrauma.Networking { static partial class ChildServerRelay { - private static System.IO.Stream writeStream; - private static System.IO.Stream readStream; - private static volatile bool shutDown; - public static bool HasShutDown - { - get { return shutDown; } - } + private static PipeType writeStream; + private static PipeType readStream; + private static ManualResetEvent writeManualResetEvent; - private static byte[] tempBytes; - private enum ReadState - { - WaitingForPacketStart, - WaitingForPacketEnd - }; - private static ReadState readState; - private static byte[] readIncBuf; + private static volatile bool shutDown; + public static bool HasShutDown => shutDown; + + private const int ReadBufferSize = MsgConstants.MTU * 2; + private static byte[] readTempBytes; private static int readIncOffset; private static int readIncTotal; - private static Queue msgsToWrite; - private static Queue msgsToRead; + private static ConcurrentQueue msgsToWrite; + private static ConcurrentQueue msgsToRead; private static Thread readThread; private static Thread writeThread; @@ -42,14 +38,13 @@ namespace Barotrauma.Networking private static void PrivateStart() { - readState = ReadState.WaitingForPacketStart; readIncOffset = 0; readIncTotal = 0; - tempBytes = new byte[MsgConstants.MTU * 2]; + readTempBytes = new byte[ReadBufferSize]; - msgsToWrite = new Queue(); - msgsToRead = new Queue(); + msgsToWrite = new ConcurrentQueue(); + msgsToRead = new ConcurrentQueue(); shutDown = false; @@ -88,113 +83,86 @@ namespace Barotrauma.Networking private static int ReadIncomingMsgs() { - Task readTask = readStream?.ReadAsync(tempBytes, 0, tempBytes.Length, readCancellationToken.Token); + Task readTask = readStream?.ReadAsync(readTempBytes, 0, readTempBytes.Length, readCancellationToken.Token); + if (readTask is null) { return -1; } + TimeSpan timeOut = TimeSpan.FromMilliseconds(100); for (int i = 0; i < 150; i++) { if (shutDown) { readCancellationToken?.Cancel(); - shutDown = true; return -1; } - if ((readTask?.IsCompleted ?? true) || (readTask?.Wait(timeOut) ?? true)) + if (readTask.IsCompleted || readTask.Wait(timeOut)) { break; } } - if (readTask == null || !readTask.IsCompleted) - { - readCancellationToken?.Cancel(); - shutDown = true; - return -1; - } - if (readTask.Status != TaskStatus.RanToCompletion) { - shutDown = true; - return -1; + bool swallowException = shutDown + && ((readTask.Exception?.InnerException is ObjectDisposedException) + || (readTask.Exception?.InnerException is System.IO.IOException)); + if (swallowException) + { + readCancellationToken?.Cancel(); + return -1; + } + throw new Exception( + $"ChildServerRelay readTask did not run to completion: status was {readTask.Status}.", + readTask.Exception); } return readTask.Result; } + private static void CheckPipeConnected(string name, PipeType pipe) + { + if (!(pipe is { IsConnected: true })) + { + throw new Exception($"{name} was disconnected unexpectedly"); + } + } private static void UpdateRead() { + Span msgLengthSpan = stackalloc byte[2]; while (!shutDown) { -#if SERVER - if (!((readStream as AnonymousPipeClientStream)?.IsConnected ?? false)) + CheckPipeConnected(nameof(readStream), readStream); + + bool readBytes(Span readTo) { - shutDown = true; - return; - } -#else - if (!((readStream as AnonymousPipeServerStream)?.IsConnected ?? false)) - { - shutDown = true; - return; - } -#endif - - int readLen = ReadIncomingMsgs(); - - if (readLen < 0) { shutDown = true; return; } - - int procIndex = 0; - - while (procIndex < readLen) - { - if (readState == ReadState.WaitingForPacketStart) + for (int i = 0; i < readTo.Length; i++) { - readIncTotal = tempBytes[procIndex]; - procIndex++; - - if (procIndex >= readLen) + if (readIncOffset >= readIncTotal) { - readLen = ReadIncomingMsgs(); - - if (readLen < 0) { shutDown = true; return; } - - procIndex = 0; + readIncTotal = ReadIncomingMsgs(); + readIncOffset = 0; + if (readIncTotal == 0) { Thread.Yield(); continue; } + if (readIncTotal < 0) { return false; } } - - readIncTotal |= (tempBytes[procIndex] << 8); - procIndex++; - - if (readIncTotal <= 0) { continue; } - - readIncOffset = 0; - readIncBuf = new byte[readIncTotal]; - readState = ReadState.WaitingForPacketEnd; + readTo[i] = readTempBytes[readIncOffset]; + readIncOffset++; } - else if (readState == ReadState.WaitingForPacketEnd) - { - if ((readIncTotal - readIncOffset) > (readLen - procIndex)) - { - Array.Copy(tempBytes, procIndex, readIncBuf, readIncOffset, readLen - procIndex); - readIncOffset += (readLen - procIndex); - procIndex = readLen; - } - else - { - Array.Copy(tempBytes, procIndex, readIncBuf, readIncOffset, readIncTotal - readIncOffset); - procIndex += (readIncTotal - readIncOffset); - readIncOffset = readIncTotal; - lock (msgsToRead) - { - msgsToRead.Enqueue(readIncBuf); - } - readIncBuf = null; - readState = ReadState.WaitingForPacketStart; - } - } - - if (shutDown) { break; } + return true; } + + if (!readBytes(msgLengthSpan)) { shutDown = true; break; } + + int msgLength = msgLengthSpan[0] | (msgLengthSpan[1] << 8); + + if (msgLength > 0) + { + byte[] msg = new byte[msgLength]; + if (!readBytes(msg.AsSpan())) { shutDown = true; break; } + + msgsToRead.Enqueue(msg); + } + Thread.Yield(); } } @@ -203,81 +171,62 @@ namespace Barotrauma.Networking { while (!shutDown) { -#if SERVER - if (!((writeStream as AnonymousPipeClientStream)?.IsConnected ?? false)) - { - shutDown = true; - return; - } -#else - if (!((writeStream as AnonymousPipeServerStream)?.IsConnected ?? false)) - { - shutDown = true; - return; - } -#endif - bool msgAvailable; byte[] msg; - lock (msgsToWrite) - { - msgAvailable = msgsToWrite.TryDequeue(out msg); - } - while (msgAvailable) - { - byte[] lengthBytes = new byte[2]; - lengthBytes[0] = (byte)(msg.Length & 0xFF); - lengthBytes[1] = (byte)((msg.Length >> 8) & 0xFF); + CheckPipeConnected(nameof(writeStream), writeStream); - msg = lengthBytes.Concat(msg).ToArray(); + bool msgAvailable; byte[] msg; + + void writeMsg() + { + // It's SUPER IMPORTANT that this stack allocation + // remains in this local function and is never inlined, + // because C# is stupid and only calls for deallocation + // when the function returns; placing it in the loop + // this method is based around would lead to a stack + // overflow real quick! + Span bytesToWrite = stackalloc byte[2 + msg.Length]; + + bytesToWrite[0] = (byte)(msg.Length & 0xFF); + bytesToWrite[1] = (byte)((msg.Length >> 8) & 0xFF); + Span msgSlice = bytesToWrite.Slice(2, msg.Length); + + msg.AsSpan().CopyTo(msgSlice); try { - writeStream?.Write(msg, 0, msg.Length); + writeStream?.Write(bytesToWrite); } - catch (ObjectDisposedException) + catch (Exception exception) { - shutDown = true; - break; - } - catch (System.IO.IOException) - { - shutDown = true; - break; + switch (exception) + { + case ObjectDisposedException _: + case System.IO.IOException _: + if (!shutDown) { throw; } + break; + default: + throw; + }; } + } + + msgAvailable = msgsToWrite.TryDequeue(out msg); + while (msgAvailable) + { + writeMsg(); if (shutDown) { break; } - lock (msgsToWrite) - { - msgAvailable = msgsToWrite.TryDequeue(out msg); - } + msgAvailable = msgsToWrite.TryDequeue(out msg); } if (!shutDown) { writeManualResetEvent.Reset(); if (!writeManualResetEvent.WaitOne(1000)) { - if (shutDown) - { - return; - } - try - { - //heartbeat to keep the other end alive - byte[] lengthBytes = new byte[2]; - lengthBytes[0] = (byte)0; - lengthBytes[1] = (byte)0; - writeStream?.Write(lengthBytes, 0, 2); - } - catch (ObjectDisposedException) - { - shutDown = true; - break; - } - catch (System.IO.IOException) - { - shutDown = true; - break; - } + if (shutDown) { return; } + + //heartbeat to keep the other end alive + msg = Array.Empty(); writeMsg(); } } } @@ -287,21 +236,15 @@ namespace Barotrauma.Networking { if (shutDown) { return; } - lock (msgsToWrite) - { - msgsToWrite.Enqueue(msg); - writeManualResetEvent.Set(); - } + msgsToWrite.Enqueue(msg); + writeManualResetEvent.Set(); } public static bool Read(out byte[] msg) { if (shutDown) { msg = null; return false; } - lock (msgsToRead) - { - return msgsToRead.TryDequeue(out msg); - } + return msgsToRead.TryDequeue(out msg); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs index b5f155b33..1e3495f4f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs @@ -257,7 +257,7 @@ namespace Barotrauma { string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue1:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue1:ItemPrefabNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, onSpawned, condition, quality)); @@ -270,7 +270,7 @@ namespace Barotrauma { string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue2:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue2:ItemPrefabNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, onSpawned, condition, quality)); @@ -283,7 +283,7 @@ namespace Barotrauma { string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory, onSpawned, condition, quality) @@ -301,7 +301,7 @@ namespace Barotrauma { string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, worldPosition, onSpawn)); @@ -314,7 +314,7 @@ namespace Barotrauma { string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue5:SpeciesNameNullOrEmpty", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue5:SpeciesNameNullOrEmpty", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, position, sub, onSpawn)); @@ -327,7 +327,7 @@ namespace Barotrauma { string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return; } spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, worldPosition, characterInfo, onSpawn)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetConfig.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetConfig.cs index ddf794d29..9314d093d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetConfig.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetConfig.cs @@ -11,6 +11,7 @@ namespace Barotrauma.Networking public const int MaxPlayers = 16; public const int ServerNameMaxLength = 60; + public const int ServerMessageMaxLength = 2000; public static string MasterServerUrl = GameMain.Config.MasterServerUrl; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEventManager.cs index 4ab3a4c33..f2bb95b69 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEventManager.cs @@ -31,7 +31,7 @@ namespace Barotrauma.Networking { DebugConsole.ThrowError("Failed to write an event for the entity \"" + e.Entity + "\"", exception); GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:WriteFailed" + e.Entity.ToString(), - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, "Failed to write an event for the entity \"" + e.Entity + "\"\n" + exception.StackTrace.CleanupStackTrace()); //write an empty event to avoid messing up IDs diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Enums.cs index 3f0f520db..e3ccaab6c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Enums.cs @@ -2,14 +2,14 @@ namespace Barotrauma.Networking { - public enum DeliveryMethod : byte + public enum DeliveryMethod : int { Unreliable = 0x0, Reliable = 0x1, ReliableOrdered = 0x2 } - public enum ConnectionInitialization : byte + public enum ConnectionInitialization : int { //used by all peer implementations SteamTicketAndVersion = 0x1, @@ -22,7 +22,7 @@ namespace Barotrauma.Networking } [Flags] - public enum PacketHeader : byte + public enum PacketHeader : int { //used by all peer implementations None = 0x0, @@ -34,5 +34,23 @@ namespace Barotrauma.Networking IsServerMessage = 0x8, IsHeartbeatMessage = 0x10 } + + public static class NetworkEnumExtensions + { + public static bool IsCompressed(this PacketHeader h) + => h.IsBitSet(PacketHeader.IsCompressed); + + public static bool IsConnectionInitializationStep(this PacketHeader h) + => h.IsBitSet(PacketHeader.IsConnectionInitializationStep); + + public static bool IsDisconnectMessage(this PacketHeader h) + => h.IsBitSet(PacketHeader.IsDisconnectMessage); + + public static bool IsServerMessage(this PacketHeader h) + => h.IsBitSet(PacketHeader.IsServerMessage); + + public static bool IsHeartbeatMessage(this PacketHeader h) + => h.IsBitSet(PacketHeader.IsHeartbeatMessage); + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs index deb765620..def9c670e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs @@ -219,7 +219,7 @@ namespace Barotrauma.Networking internal static void EnsureBufferSize(ref byte[] buf, int numberOfBits) { - int byteLen = ((numberOfBits + 7) >> 3); + int byteLen = (numberOfBits + 7) / 8; if (buf == null) { buf = new byte[byteLen + MsgConstants.BufferOverAllocateAmount]; @@ -461,7 +461,7 @@ namespace Barotrauma.Networking { get { - return (LengthBits + ((8 - (LengthBits % 8)) % 8)) / 8; + return (LengthBits + 7) / 8; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index 304996d66..381b13677 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -45,6 +45,7 @@ namespace Barotrauma.Networking [Flags] public enum NetFlags : byte { + None = 0x0, Name = 0x1, Message = 0x2, Properties = 0x4, @@ -342,8 +343,14 @@ namespace Barotrauma.Networking get { return serverName; } set { - serverName = value; - if (serverName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); } + string val = value; + if (val.Length > NetConfig.ServerNameMaxLength) { val = val.Substring(0, NetConfig.ServerNameMaxLength); } + if (serverName == val) { return; } + serverName = val; + ServerDetailsChanged = true; +#if SERVER + UpdateFlag(NetFlags.Name); +#endif } } @@ -353,9 +360,14 @@ namespace Barotrauma.Networking get { return serverMessageText; } set { - if (serverMessageText == value) { return; } - serverMessageText = value; + string val = value; + if (val.Length > NetConfig.ServerMessageMaxLength) { val = val.Substring(0, NetConfig.ServerMessageMaxLength); } + if (serverMessageText == val) { return; } + serverMessageText = val; ServerDetailsChanged = true; +#if SERVER + UpdateFlag(NetFlags.Message); +#endif } } @@ -371,6 +383,8 @@ namespace Barotrauma.Networking public Dictionary MonsterEnabled { get; private set; } + public const int MaxExtraCargoItemsOfType = 10; + public const int MaxExtraCargoItemTypes = 20; public Dictionary ExtraCargo { get; private set; } public HashSet HiddenSubs { get; private set; } @@ -1003,16 +1017,17 @@ namespace Barotrauma.Networking { bool changed = false; UInt32 count = msg.ReadUInt32(); - if (ExtraCargo == null || count != ExtraCargo.Count) changed = true; + if (ExtraCargo == null || count != ExtraCargo.Count) { changed = true; } Dictionary extraCargo = new Dictionary(); for (int i = 0; i < count; i++) { string prefabIdentifier = msg.ReadString(); byte amount = msg.ReadByte(); - var itemPrefab = MapEntityPrefab.Find(null, prefabIdentifier, showErrorMessages: false) as ItemPrefab; - if (itemPrefab != null && amount > 0) + if (MapEntityPrefab.Find(null, prefabIdentifier, showErrorMessages: false) is ItemPrefab itemPrefab && amount > 0) { + if (ExtraCargo.Keys.Count() >= MaxExtraCargoItemTypes) { continue; } + if (ExtraCargo.ContainsKey(itemPrefab) && ExtraCargo[itemPrefab] >= MaxExtraCargoItemsOfType) { continue; } if (changed || !ExtraCargo.ContainsKey(itemPrefab) || ExtraCargo[itemPrefab] != amount) { changed = true; } extraCargo.Add(itemPrefab, amount); } @@ -1039,11 +1054,15 @@ namespace Barotrauma.Networking public void ReadHiddenSubs(IReadMessage msg) { + var subList = GameMain.NetLobbyScreen.GetSubList(); + HiddenSubs.Clear(); uint count = msg.ReadVariableUInt32(); for (int i = 0; i < count; i++) { - string submarineName = msg.ReadString(); + int index = msg.ReadUInt16(); + if (index < 0 || index >= subList.Count) { continue; } + string submarineName = subList[index].Name; HiddenSubs.Add(submarineName); } @@ -1054,10 +1073,12 @@ namespace Barotrauma.Networking public void WriteHiddenSubs(IWriteMessage msg) { + var subList = GameMain.NetLobbyScreen.GetSubList(); + msg.WriteVariableUInt32((uint)HiddenSubs.Count); foreach (string submarineName in HiddenSubs) { - msg.Write(submarineName); + msg.Write((UInt16)subList.FindIndex(s => s.Name.Equals(submarineName, StringComparison.OrdinalIgnoreCase))); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/SteamManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/SteamManager.cs index 5e236219e..98e6022d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/SteamManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/SteamManager.cs @@ -74,6 +74,19 @@ namespace Barotrauma.Steam return Steamworks.SteamClient.Name; } + private static Steamworks.AuthTicket currentTicket = null; + public static Steamworks.AuthTicket GetAuthSessionTicket() + { + if (!isInitialized) + { + return null; + } + + currentTicket?.Cancel(); + currentTicket = Steamworks.SteamUser.GetAuthSessionTicket(); + return currentTicket; + } + public static bool OverlayCustomURL(string url) { if (!isInitialized || !Steamworks.SteamClient.IsValid) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs index 4707fa46e..5b4fd2ac9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs @@ -546,7 +546,7 @@ namespace Barotrauma if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "PhysicsBody.SetPosition:InvalidPosition" + userData, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return false; } @@ -573,7 +573,7 @@ namespace Barotrauma if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "PhysicsBody.SetPosition:InvalidPosition" + userData, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Prefabs/IPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Prefabs/IPrefab.cs index d2b2e9d27..def8398f7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Prefabs/IPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Prefabs/IPrefab.cs @@ -27,7 +27,7 @@ namespace Barotrauma prefab.UIntIdentifier = ToolBox.StringToUInt32Hash(prefab.Identifier, md5); //it's theoretically possible for two different values to generate the same hash, but the probability is astronomically small - var collision = prefabs.Find(p => p != prefab && p.UIntIdentifier == prefab.UIntIdentifier); + var collision = prefabs.Find(p => p.Identifier != prefab.Identifier && p.UIntIdentifier == prefab.UIntIdentifier); if (collision != null) { DebugConsole.ThrowError($"Hashing collision when generating uint identifiers for {typeof(T).Name}: {prefab.Identifier} has the same identifier as {collision.Identifier} ({prefab.UIntIdentifier})"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ProcGen/VoronoiElements.cs b/Barotrauma/BarotraumaShared/SharedSource/ProcGen/VoronoiElements.cs index feeb54020..8c461594f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ProcGen/VoronoiElements.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ProcGen/VoronoiElements.cs @@ -54,6 +54,7 @@ using Barotrauma; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using System.Collections.Generic; +using System.Linq; namespace Voronoi2 { @@ -194,9 +195,14 @@ namespace Voronoi2 public bool IsPointInside(Vector2 point) { + Vector2 transformedPoint = point - Translation; + if (Edges.All(e => e.Point1.X < transformedPoint.X && e.Point2.X < transformedPoint.X)) { return false; } + if (Edges.All(e => e.Point1.Y < transformedPoint.Y && e.Point2.Y < transformedPoint.Y)) { return false; } + if (Edges.All(e => e.Point1.X > transformedPoint.X && e.Point2.X > transformedPoint.X)) { return false; } + if (Edges.All(e => e.Point1.Y > transformedPoint.Y && e.Point2.Y > transformedPoint.Y)) { return false; } foreach (GraphEdge edge in Edges) { - if (MathUtils.LinesIntersect(point, Center, edge.Point1 + Translation, edge.Point2 + Translation)) { return false; } + if (MathUtils.LinesIntersect(transformedPoint, Center - Translation, edge.Point1, edge.Point2)) { return false; } } return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index eecd32a46..f6ad777c5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -231,7 +231,7 @@ namespace Barotrauma Vector2 screenTargetPos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) * 0.5f; if (CharacterHealth.OpenHealthWindow != null) { - screenTargetPos.X = GameMain.GraphicsWidth * (CharacterHealth.OpenHealthWindow.Alignment == Alignment.Left ? 0.75f : 0.25f); + screenTargetPos.X = GameMain.GraphicsWidth * (CharacterHealth.OpenHealthWindow.Alignment == Alignment.Left ? 0.6f : 0.4f); } else if (ConversationAction.IsDialogOpen) { @@ -305,7 +305,7 @@ namespace Barotrauma { string errorMsg = "Attempted to modify the state of the physics simulation while a time step was running."; DebugConsole.ThrowError(errorMsg, e); - GameAnalyticsManager.AddErrorEventOnce("GameScreen.Update:WorldLockedException" + e.Message, GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameScreen.Update:WorldLockedException" + e.Message, GameAnalyticsManager.ErrorSeverity.Critical, errorMsg); } #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs index f8ce3190b..2b8e658f9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs @@ -37,45 +37,6 @@ namespace Barotrauma #endif } - public void SetRadiationEnabled(bool enabled) - { -#if CLIENT - if (radiationEnabledTickBox == null) { return; } - radiationEnabledTickBox.Selected = enabled; -#endif - } - - public bool IsRadiationEnabled() - { -#if CLIENT - return radiationEnabledTickBox != null && radiationEnabledTickBox.Selected; -#elif SERVER - return GameMain.Server.ServerSettings.RadiationEnabled; -#endif - } - - public void SetMaxMissionCount(int maxMissionCount) - { -#if SERVER - if (GameMain.Server != null) - { - if (maxMissionCount < CampaignSettings.MinMissionCountLimit) maxMissionCount = CampaignSettings.MaxMissionCountLimit; - if (maxMissionCount > CampaignSettings.MaxMissionCountLimit) maxMissionCount = CampaignSettings.MinMissionCountLimit; - - GameMain.Server.ServerSettings.MaxMissionCount = maxMissionCount; - lastUpdateID++; - } -#endif -#if CLIENT - (maxMissionCountText as GUITextBlock).Text = maxMissionCount.ToString(); -#endif - } - - public int GetMaxMissionCount() - { - return GameMain.NetworkMember?.ServerSettings?.MaxMissionCount ?? 0; - } - public void ToggleTraitorsEnabled(int dir) { #if SERVER diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs index c66595a6c..c5ee6f977 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Xml; using System.Xml.Linq; @@ -414,6 +415,15 @@ namespace Barotrauma return ushortValue; } + public static T GetAttributeEnum(this XElement element, string name, T defaultValue) where T : struct, Enum + { + var attr = element?.GetAttribute(name); + if (attr == null) { return defaultValue; } + return Enum.TryParse(attr.Value, true, out T result) ? result : + int.TryParse(attr.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out int resultInt) ? Unsafe.As(ref resultInt) : + defaultValue; + } + public static bool GetAttributeBool(this XElement element, string name, bool defaultValue) { if (element?.Attribute(name) == null) { return defaultValue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index a165e9c50..43cd51e95 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -1,4 +1,5 @@ -using Barotrauma.Extensions; +using Barotrauma.Abilities; +using Barotrauma.Extensions; using Barotrauma.Items.Components; using FarseerPhysics; using Microsoft.Xna.Framework; @@ -202,6 +203,15 @@ namespace Barotrauma } } + public class AbilityStatusEffectIdentifier : AbilityObject + { + public AbilityStatusEffectIdentifier(string effectIdentifier) + { + EffectIdentifier = effectIdentifier; + } + public string EffectIdentifier { get; set; } + } + public class GiveTalentInfo { public string[] TalentIdentifiers; @@ -214,6 +224,23 @@ namespace Barotrauma } } + public class GiveSkill + { + public string SkillIdentifier; + public float Amount; + + public GiveSkill(XElement element, string parentDebugName) + { + SkillIdentifier = element.GetAttributeString("skillidentifier", string.Empty); + Amount = element.GetAttributeFloat("amount", 0); + + if (SkillIdentifier == string.Empty) + { + DebugConsole.ThrowError($"GiveSkill StatusEffect did not have a skill identifier defined in {parentDebugName}!"); + } + } + } + public class CharacterSpawnInfo : ISerializableEntity { public string Name => $"Character Spawn Info ({SpeciesName})"; @@ -346,8 +373,9 @@ namespace Barotrauma public readonly List<(string affliction, float amount)> ReduceAffliction; + private readonly List talentTriggers; private readonly List giveExperiences; - private readonly List<(string identifier, float amount)> giveSkills; + private readonly List giveSkills; public float Duration => duration; @@ -399,8 +427,9 @@ namespace Barotrauma Explosions = new List(); triggeredEvents = new List(); ReduceAffliction = new List<(string affliction, float amount)>(); + talentTriggers = new List(); giveExperiences = new List(); - giveSkills = new List<(string, float)>(); + giveSkills = new List(); multiplyAfflictionsByMaxVitality = element.GetAttributeBool("multiplyafflictionsbymaxvitality", false); tags = new HashSet(element.GetAttributeString("tags", "").Split(',')); @@ -696,11 +725,14 @@ namespace Barotrauma case "aitrigger": aiTriggers.Add(new AITrigger(subElement)); break; + case "talenttrigger": + talentTriggers.Add(subElement.GetAttributeString("effectidentifier", string.Empty)); + break; case "giveexperience": giveExperiences.Add(subElement.GetAttributeInt("amount", 0)); break; case "giveskill": - giveSkills.Add((subElement.GetAttributeString("skillidentifier", ""), subElement.GetAttributeFloat("amount", 0))); + giveSkills.Add(new GiveSkill(subElement, parentDebugName)); break; } } @@ -1256,6 +1288,7 @@ namespace Barotrauma float reduceAmount = amount * GetAfflictionMultiplier(entity, targetCharacter, deltaTime); float prevVitality = targetCharacter.Vitality; targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, affliction, reduceAmount, treatmentAction: actionType); + targetCharacter.AIController?.OnHealed(healer: user, targetCharacter.Vitality - prevVitality); if (user != null && user != targetCharacter) { if (!targetCharacter.IsDead) @@ -1295,6 +1328,19 @@ namespace Barotrauma } } + if (talentTriggers.Any()) + { + Character targetCharacter = CharacterFromTarget(target); + if (targetCharacter != null && !targetCharacter.Removed) + { + foreach (string talentTrigger in talentTriggers) + { + targetCharacter.CheckTalents(AbilityEffectType.OnStatusEffectIdentifier, new AbilityStatusEffectIdentifier(talentTrigger)); + } + + } + } + if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { // these effects do not need to be run clientside, as they are replicated from server to clients anyway @@ -1310,23 +1356,18 @@ namespace Barotrauma if (giveSkills.Any()) { - foreach ((string skillIdentifier, float amount) in giveSkills) + foreach (GiveSkill giveSkill in giveSkills) { Character targetCharacter = CharacterFromTarget(target); if (targetCharacter != null && !targetCharacter.Removed) { - if (skillIdentifier?.ToLowerInvariant() == "randomskill") - { - targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount); + string skillIdentifier = giveSkill.SkillIdentifier.ToLowerInvariant() == "randomskill" ? GetRandomSkill() : giveSkill.SkillIdentifier; - string GetRandomSkill() - { - return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom(); - } - } - else + targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier, giveSkill.Amount); + + string GetRandomSkill() { - targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount); + return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom(); } } } @@ -1685,6 +1726,7 @@ namespace Barotrauma targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, affliction, reduceAmount, treatmentAction: actionType); if (element.User != null && element.User != targetCharacter) { + targetCharacter.AIController?.OnHealed(healer: element.User, targetCharacter.Vitality - prevVitality); if (!targetCharacter.IsDead) { targetCharacter.TryAdjustAttackerSkill(element.User, targetCharacter.Vitality - prevVitality); @@ -1717,6 +1759,11 @@ namespace Barotrauma if (entity is Item sourceItem && sourceItem.HasTag("medical")) { multiplier *= 1 + targetCharacter.GetStatValue(StatTypes.MedicalItemEffectivenessMultiplier); + + if (user != null) + { + multiplier *= 1 + user.GetStatValue(StatTypes.MedicalItemApplyingMultiplier); + } } return multiplier * AfflictionMultiplier; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs b/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs index c19d2db20..6def082f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs @@ -439,7 +439,7 @@ namespace Barotrauma catch (FormatException) { string errorMsg = "Failed to format text \"" + text + "\", args: " + string.Join(", ", args); - GameAnalyticsManager.AddErrorEventOnce("TextManager.GetFormatted:FormatException", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("TextManager.GetFormatted:FormatException", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return text; } } @@ -721,7 +721,7 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError(errorMsg, exception); #endif - GameAnalyticsManager.AddErrorEventOnce("TextManager.GetServerMessage:" + serverMessage, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("TextManager.GetServerMessage:" + serverMessage, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); return errorMsg; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/Either.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/Either.cs index 76897c56d..2f24d0601 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/Either.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/Either.cs @@ -30,12 +30,20 @@ namespace Barotrauma } public override bool TryGet(out T t) { t = Value; return true; } - public override bool TryGet(out U u) { u = default(U); return false; } + public override bool TryGet(out U u) { u = default; return false; } public override bool TryCast(out V v) { - if (Value is V result) { v = result; return true; } - else { v = default(V); return false; } + if (Value is V result) + { + v = result; + return true; + } + else + { + v = default; + return false; + } } } @@ -50,13 +58,21 @@ namespace Barotrauma return Value.ToString(); } - public override bool TryGet(out T t) { t = default(T); return false; } + public override bool TryGet(out T t) { t = default; return false; } public override bool TryGet(out U u) { u = Value; return true; } public override bool TryCast(out V v) { - if (Value is V result) { v = result; return true; } - else { v = default(V); return false; } + if (Value is V result) + { + v = result; + return true; + } + else + { + v = default; + return false; + } } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs index 94cfaac4f..4233862e6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; +using System.Runtime.CompilerServices; namespace Barotrauma { @@ -534,6 +535,16 @@ namespace Barotrauma } } + // Enum.HasFlag() sucks + public static bool IsBitSet(this T self, T bit) where T : struct, Enum + { + // This uses Unsafe.As for performance reasons, as + // C# will otherwise not allow a T -> int cast + // without first casting to object, which would make + // this not any better than Enum.HasFlag + return (Unsafe.As(ref self) & Unsafe.As(ref bit)) != 0; + } + public static string ByteArrayToString(byte[] ba) { StringBuilder hex = new StringBuilder(ba.Length * 2); diff --git a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub index e41b82691..ae5ed2c05 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub and b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub index 773c3ee81..6693ecc37 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub and b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub index 88858bb97..9a3ab3069 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub and b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub b/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub index f4f87a18d..6d55d0f9d 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub and b/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub b/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub index 0b77952ca..c88e6472d 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub and b/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca.sub b/Barotrauma/BarotraumaShared/Submarines/Orca.sub index 6b3646cab..d329b6ebf 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca2.sub b/Barotrauma/BarotraumaShared/Submarines/Orca2.sub index f8c492f18..295e36362 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca2.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca2.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/R-29.sub b/Barotrauma/BarotraumaShared/Submarines/R-29.sub index 97053ee07..0fae516df 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/R-29.sub and b/Barotrauma/BarotraumaShared/Submarines/R-29.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index a79c97e86..33015d9d3 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Remora.sub and b/Barotrauma/BarotraumaShared/Submarines/Remora.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub index e75e187ce..99a5bc348 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub index 9274b5c66..777acf1be 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 493fde24f..536930f4c 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,166 @@ +--------------------------------------------------------------------------------------------------------- +v0.15.17.0 +--------------------------------------------------------------------------------------------------------- + +Changes: +- Readded GameAnalytics, a library that allows us to collect gameplay statistics and error reports automatically. Sending statistics is completely optional: when you launch the game, you're asked whether you want to allow the game to send us data or not. If you decline, no data of any kind is collected. The collected data is completely anonymous and doesn't contain any personal information, but only things such as error reports and statistics about the selected game mode, submarine, missions and mods. +- Characters can climb up ladders faster by holding the sprint key. +- Mudraptor shell shields block projectiles and can be destroyed by firing at them. +- Bots take previous heals into account when determining when to react to attacks. Fixes deusizine and liquid oxygenite aggroing the bots when using them to treat oxygen loss. +- Diving suits play a different warning sound when low on oxygen (<5% left in tank). +- PUCS doesn't consume oxygen tanks in rooms with oxygen. +- Added new option-specific bot dialogue for when bots can't find "loadables" for the Load Items order (unstable only). +- Reworked "evasive maneuvers": now gives 15% more money for nest/swarm missions instead of giving attack bonus inside submarine. +- Rework "expanded research": whenever you or another allied crewmember deconstructs depleted fuel, they gain 400 experience. This effect can only occur up to 3 times until you finish another mission. +- Prototype Steam Cannon can damage ballast flora. +- Water detectors treat minuscule amounts (< 1.0 water volume) as 0. +- Improvements to the "infiltration" outpost event: you can talk to any of the clowns to progress, not just the one that spawned 1st, and the clowns head to the airlock when told to leave. +- Improved the bots door interaction so that they don't slam the door at your face, nor bump into closed doors so often, or keep "smashing the select key" as frequently. There's now a three second cooldown after succesfully pressing a button before next attempt. +- Changes to the default order assignment priorities, which determine the initial order of the orders when multiple is issued. You can still alter the order manually by dragging the order icons. +- Bots now treat handcuffed targets as neutralized, unless they are hostiles spawned from events. Fixes e.g. bots killing captured prisoners, but also allows you to submit to the outpost guards by handcuffing yourself. +- Load Items order: bots now prioritize containers that have empty space. +- Load Items order: the Refill Oxygen Tanks option now also puts tanks into oxygen tank shelves. +- Load Items order: bots now prioritize items further from the target condition when searching for items to laod, i.e. emptier oxygen tanks and battery cells and fuller ammunition boxes. + +Fixes: +- Fixed certain monsters (e.g. hammerheads) attacking each other (unstable only). +- Fixed mudraptors and crawlers trying to swim to the bottom of the ocean. +- Fixed other clients' characters sometimes disappearing in MP when moving from sub to another (unstable only). +- Fixes and improvements to Orca 2. +- Fixed canister shell texture path (unstable only). +- Fixed structures without a collider not being taken into account when determining which submarines are visible, causing them to vanish if there are only structures without a collider on the screen. +- Fixed Round and Ceil components returning -0 when rounding a negative value to 0. +- Fixed inability to swim up through a small hull on top of another one (e.g. the upper airlock in R-29). +- Fixed skillbooks, oxygenite shards and sulphurite shards not spawning in crates. +- Fixed name of the currently installed turret not being displayed in the submarine upgrade menu. +- Fixed AI gunners not shooting at visible enemies inside the enemy submarine. +- Prevent bots from healing the targets that have recently attacked them. Fixes bots healing and shooting you at the same time. +- Fixed rescue order (healing) not working outside of the sub. +- Fixed bots not knowing how to use the Rapid Fissile Accelerator. Also improves on how they use smg, assault rifle, and autoshotgun. +- Fixed status monitor's electrical view not showing the power/load values in Korean. +- Fixed status monitor displaying water levels strangely in multi-hull rooms (unstable only). +- Fixed water flow sound volume having no upper limit, sometimes causing the sounds to drown out everything else (unstable only). +- Fixed bots trying to equip a scooter when their hands are occupied by something that can't be put in the inventory (e.g. a crate), leading to weird twitching when they drop and immediately pick up the carried item. +- Fixed unconscious NPCs being able to see you steal. +- Fixed inability to order bots to operate turrets that are controlled via wifi components (unstable only). +- Fixed bots staying in the "fight intruders" state indefinitely when there are neutralized (= handcuffed) hostiles on board, and repeatedly reporting that there are hostiles on board. +- Bots stop moving before starting to repair a device. Fixes bots "flying" when the repair fails, because they'd start the repair while still running at full speed and would keep the momentum if they get stunned because of the failed repair. +- Fixed bots sometimes repathing too eagerly while they are on ladders, sometimes causing them to get stuck in places where they should switch from a ladder to another one nearby. +- Fixed looking at the sonar sometimes causing a significant performance hit (most often in the abyss). If a path to a marker couldn't be found when determining the distance to the marker, the game would keep trying to find a path every frame. +- Fixed "expert commando" talent affecting turrets. +- Fixed Alien_Entrance3's left opening getting connected to other modules instead of being used as an entrance. +- Fixed coilgun draining ammo boxes at the same rate regardless of the number of linked loaders. +- Fixed some of the artifacts still playing sounds when inside a transport case. +- Fixed inability to put genetic materials into a container inside another container. +- Fixed characters legs sometimes bending the wrong way around when climbing (seems to happen most often when trying to get on ladders while swimming up-side down). +- Fixed borked "Front E P2" collider. +- Fixed bots taking items from fabricators/deconstructors when ordered to load items. +- Fixed bots sometimes running headlessly for a brief period after having been ordered to load items. +- Fixed hostages in the abandoned outpost rescue mission refusing to follow a player that has some diving gear on they don't have access to. +- Fixed pirates giving redundant orders and consequently talking too much in the chat. +- Fixed pirates not always shooting monsters if there's few of them. +- Fixes to the bot navigation outside of the sub. +- Fixed bots still trying to go through destructible ice chunks. +- Fixed bots not closing the airlock doors when they go outside +- Fixed bots not always trying to press the right button to interact with a door. +- Fixed bots getting stuck when trying to follow a target with some diving gear they don't have access to. Now they should self-dismiss the order instead. +- Fixed auto-dismissing orders clearing all the orders instead of just the one that should dismiss. +- Fixed the follow distances between the characters when there's multiple bots following the player. +- Fixed monsters always ignoring all items inside the sub when they are outside, consequently they didn't target the sonar or the engine, for example. +- Fixed bots sometimes deploying Kastrull's drone for no reason. +- Fixed the Load Items order sometimes dropping one item from the target container, even when there's space for the new item. +- Fixed bots removing items too aggressively from their preferred containers (e.g. oxygen tanks from diving suits) during the Load Items order when the items had been even just very slightly used. + +Modding: +- Fixed overriding a Controller component (e.g. button) resetting its Toggle and State settings. +- Fixed ParticleEmitter's angle not getting mirrored when spawned by a mirrored item. + +--------------------------------------------------------------------------------------------------------- +v0.15.16.0 +--------------------------------------------------------------------------------------------------------- + +Changes: +- Added a simple QTE "minigame" to the repair interface. Allows speeding up the repairs slightly and hopefully makes repairing feel a little more engaging. +- Medical curtains can be opened and closed. +- Restrict the amount of additional cargo you can choose in multiplayer: max 10 items of a type, max 20 types of items. +- Connected power to oxygen tank shelves in Azimuth, Berilia, Dugong, R-29 and Remora. +- Allow editing item quality in the sub editor. +- Changed the way purchasable submarines for the multiplayer campaign are selected: instead of choosing them when creating the campaign, all of the server's visible submarines are made available for purchase. +- Added a new "Load Items" order with options "Recharge battery cells", "Refill oxygen tanks", and "Reload ammo": bots will take battery cells to be recharged, take oxygen tanks to be refilled, or place more ammo to loaders depending on the order option. +- Reduced initial assignment priority for Operate orders: Operate Reactor, Operate Weapons, and Steer orders will now be assigned as the lowest priority order by default, but the current order priorities can still be adjusted like before by rearranging the orders on the crew list. +- Minor adjustments to the the monster spawns in the ruins. +- Husks now remain neutral towards characters that are late in the husk transformation, unless they act offensively. +- Reduce Husk and Humanhusk sight from 1 to 0.5. +- Prepare for expedition order: the bots now try to take five oxygen tanks instead of just one. +- Prepare for experition order: require a diving suit for the order to complete. +- Oxygenite shards are no longer oxygen sources and can't be inserted into diving gear or tools directly. They are still a source of liquid oxygenite, which can be used to craft oxygenite tanks. +- Allow to grab targets when their inventory can be accessed. Allow to access the inventory of the bots. +- Adjust the health interface: target is shown closer to the center of the screen. +- Shotguns now spawn with 6 shells, autoshotguns with 8 (a full stack). +- Add some dialog for the bots when they are getting attacked outside a friendly sub to tell the player why they are fleeing from an enemy and that they don't have a weapon. + +Talent improvements and fixes: +- Overall balancing to the stats boots from talents. +- Fixed "tandem fire" crashing the game if there are no other crewmates present. +- Fixed "safety first" talent giving you mechanical skill when you gain electrical skill instead of the other way around. +- Fixed talent menu showing extra talents as still being selectable after picking all-seeing-eye, even if there's no talent points left (unstable only). +- Reworked "implacable": now allows the character to stay conscious for 15 seconds after falling below 0 health. +- Reworked "field medic": if you complete a mission and all crew members survived, you gain medical skill. +- Reworked "beat cop": now gives 25% repair speed and increases inflicted stuns by 25% (and the tackle). +- Reworked "reverse engineer": whenever you deconstruct an alien artifact, you have a 50% chance to gain double output from it and gain 8 to a random skill. Small alien artifacts, when deconstructed, give 4 skill instead. +- Reworked "curiosity": whenever you deconstruct an alien artifact, you and another random allied crewmember gains 125 experience. Small alien artifacts, when deconstructed, give 50 experience instead. +- Reworked "expert commando": now gives, to ranged weapons, 70% spread reduction, 40% attack multiplier and 30% attack speed reduction while crouched. +- Reworked "gene therapist": now gives an additional 25% medical item potency, correctly displays as adding a flat 10% to each refined genetic material. +- Reworked "genetic stability": no longer gives 25% repair speed, and instead gives 15 to all skills. Probability of tainting genetic material reduced to 50%. +- Reworked "still kicking": now rapidly heals over a short duration instead. +- Reworked "stand and deliver": assistant skillbook can no longer be used on another character, instead the nearest ally gains 5 to all skills when the book is read by the talent owner. +- Reworked "first aid training": Now gives a flat 35% increase to medical item potency when applied to the character. +- "Advanced splicing", "optimized power-flow" and "elbow grease" can now only give up to 100 skill bonus. +- Adjustments to some of the ability descriptions to address some inaccuracies/ambiguities. +- Rebalanced combat stimulant: now applies faster, but less in total per dose. Healing reduced. +- Droped type "poison" from chem addiction, making it non-healable (only chem withdrawal can be healed). + +Fixes: +- Fixed certain wires in vanilla subs being impossible to remove. +- Improvements to cave waypoints: should fix bots getting stuck next to destructible ice chunks even if there's a way past them. +- Fixed other clients' characters sometimes appearing to teleport outside the sub for 1 frame when moving from sub to another, leading to further issues if the client has water-sensitive items in their inventory. +- Fixed gaps sometimes getting connected to incorrect hulls between docking ports, preventing water from flowing down from the space between the ports. +- Fixed mudraptor shell armor's sprite depth getting messed up when climbing ladders. +- Fixed drunkenness, opiate withdrawal and chem withdrawal not causing nausea. +- Fixed wire nodes getting misplaced in the sub editor when loading mirrored wires. +- Fixed whitelist controls getting disabled when adding new players to the list if whitelist wasn't enabled when opening the server settings. +- Fixed hardened/dementonite crowbars not opening doors faster than normal ones. +- Fixed campaign setup menu going crazy if you try to scroll it with arrow keys. +- Fixed bots losing their items if you quit the 1st MP campaign round without saving, continue, and repeat that again. +- Fixed cargo sometimes spawning inside the floor. Happened because the spawn position was determined based on the purchased item's size, instead of the crate the item spawns in. +- Fixed playstyle always being displayed as Serious in the server lobby unless you're hosting yourself. +- Fixed handheld status monitor drawing the water levels and character icons of the previous sub when it's taken outside. +- Fixed spawnitem command not working in the sub editor. +- Fixed "slipsuit.png not found" error" when placing a slipsuit in a locker (unstable only). +- Fixed alien pistol not having a crosshair. +- Fixed weapon skill above 100 making weapons less accurate. +- Fixed misaligned outpost reactor meters. +- Fixed R and T not bringing up the chat if it's minimized (unstable only). +- Fixed terminal inputs not working (unstable only). +- Fixed hulls' "hidden in game" setting doing nothing. +- Fixed certain items causing the "analyzeitem" and "deconstructvalue" console commands to crash. +- Fixed context menus not opening when right-clicking player names in the chat and server log. +- Improved the error message shown when a Steam lobby could not be created. +- Reduced the severity and frequency of the "Maximum packet size exceeded" error in the server lobby. +- Fixed the contextual order for fiding a weapon not targeting the selected item. +- Fixed combat objective overriding the priority of find safety objective when the combat objective shouldn't even be active, leading to bots sometimes suffocating after combat if they don't have enough oxygen in the currently equipped tank. +- Fixed bots trying to shoot through level walls. +- Prepare for expedition: Fix bots not equipping a diving suit in multiplayer game. +- Fixed sprite bleed in the outpost computer terminal sprite. +- Fixed campaign getting "locked" if the submarine undocks immediately at the start of the round (on the first frame, e.g. as a result of some custom docking circuit). +- Fixed deconstructor being unable to combine the output items if they're inside a container in the output slots. + +Modding: +- Fixed certain explosion flashduration values causing the light to flicker/loop. +- Fixed remaining errors with talent overrides. +- Allow the enemy ai to target using groups in addition to species names. +- Move the turret ammo box definitions from containers.xml to where the gun and the ammo is defined. + --------------------------------------------------------------------------------------------------------- v0.15.15.0 --------------------------------------------------------------------------------------------------------- diff --git a/Barotrauma/BarotraumaShared/daedalic_privacypolicy.txt b/Barotrauma/BarotraumaShared/daedalic_privacypolicy.txt new file mode 100644 index 000000000..3f55a5247 --- /dev/null +++ b/Barotrauma/BarotraumaShared/daedalic_privacypolicy.txt @@ -0,0 +1,71 @@ +1. Name and contact data of the controllers responsible for processing, as well as the company data protection officer + +This data protection information applies to data processing by: + +Controller: + Daedalic Entertainment GmbH + Papenreye 51 + 22453 Hamburg + +represented by the CEO Carsten Fichtelmann and the COO Stephan Harms, + e-mail: info@daedalic.com, + fax: + 49 40 356 741 36 + +The companies’ data protection officer is + + Frederik Bockslaff + +who can be reached under: + + Nimrod Rechtsanwälte + Emser Straße 9 + 10719 Berlin + E-mail: info@nimrod-rechtsanwaelte.de + tel: +49 (0)30 544 61 793 + fax: +49 (0)30 544 61 794 + +2. Collection and storage of personal data and the purpose of their use +Your personal data will be processed by Valve Corporation as the provider of the Steam store. You can find further relevant information regarding your rights in this respect in the privacy statements of Valve Corporation under the following link: + +https://store.steampowered.com/privacy_agreement/ + +Additionally, subject to your consent, for the purposes of quality management, analytics and marketing purposes the following personal data is processed and controlled by GameAnalytics Ltd: + • Time spent playing different game modes + • Round statistics (e.g. selected submarine, duration, mission success/failure, difficulty, selected job, visited caves/wrecks/ruins etc...) + • Campaign statistics (e.g. number of visited outposts and completed missions, amount of money, time used per campaign, purchased/sold items, radiation enabled/disabled, etc...) + • Tutorial completion + • Purchased submarine upgrades + • Character deaths and causes of death + • Info of server settings (anti-griefing and traitor settings) + • Enabled mods + • Error messages + • Crash logs + +The legal basis for the data processing is Art. 6 (1) lit. b, f GDPR. Our legitimate interest is derived from the purposes listed above for data collection. +For more information about privacy related to GameAnalytics Ltd, see the following link: https://gameanalytics.com/privacy/ + +3. Duration of personal data processing +The personal data which is processed according to clause 2, is processed as long, as your player client is active. If you delete your account, any personal data processed so far will be erased. + +4. Forwarding data +Unless stated otherwise under clause 2, your personal data will not be transferred to any third party for any purpose other than those listed below. +We shall forward your personal data to third parties only + • if you have given your explicit consent, in accordance with Art. 6 (1) lit. a GDPR + • if under Art. 6 (1) lit. f GDPR the transfer is necessary for the establishment, exercise or defence of legal claims, and there is no reason to assume that you have an overriding interest, which must be protected, in the non-forwarding of your data + • in the event that there is a legal obligation to forward the data under Art. 6 (1) lit. c GDPR and + • if this is legally permissible and necessary under Art. 6 (1) lit. b GDPR for the processing of contractual relationships with you. + +5. Data subject rights +You have the right: +• pursuant to Art. 15 GDPR, to demand information about your personal data that we have processed. In particular, you can obtain information about the purposes of the processing, The categories of personal data concerned, the recipients or category of recipients to whom you data have been or will be disclosed, the envisaged period for which the data will be stored, the existence of the right to request rectification, erasure, or restriction of the processing, or to object to it, the right to lodge a complaint with a supervisory authority, the source of your data, if these have not been collected by us, and on the existence of automated decision-making, including profiling, and any other meaningful information about their details or the logic involved; + + • pursuant to Art. 16 GDPR, to demand the immediate rectification or completion of inaccurate personal data stored by us; + • pursuant to Art. 17 GDPR, to demand the erasure of personal data stored by us, unless their processing is necessary for exercising the right of freedom of expression and information, for compliance with a legal obligation, for reasons of public interest, or for the establishment, exercise or defence of legal claims; + • pursuant to Art. 18 GDPR, to demand the restriction of the processing of your personal data in cases where you contest the accuracy of the data, where the processing is unlawful yet you oppose the erasure of the personal data, where we no longer need the data but you still require them to establish, exercise or defend legal claims, or where you have objected to the processing of the data pursuant to Art. 21 GDPR; + • pursuant to Art. 20 GDPR, to obtain your personal data that you have provided to us in a structured, commonly used and machine-readable format and to demand the transfer of these data to another controller; + • pursuant to Art. 7 (3) GDPR, to withdraw your consent at any time, which will mean that in future we may no longer carry out the data processing that was contingent upon this consent and + • pursuant to Art. 77 GDPR, to lodge a complaint with a supervisory authority. In general, you can contact the supervisory authority of your usual residence or place of employment, or that of our company headquarters for this purpose. + +6. Right of objection +Insofar as your personal data is processed on the basis of legitimate interests in accordance with Art. 6 (1) f GDPR, you have the right, under Art. 21 GDPR, to object to the processing of your personal data, provided there are reasons relating to your particular situation or if the objection relates to direct marketing. In the latter case, you have a general right to object, which shall be implemented by us without any reference to a particular situation. +If you wish to avail of your right to withdraw or object, an e-mail to info@nimrod-rechtsanwaelte.de will suffice. \ No newline at end of file diff --git a/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj b/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj index 99073a079..725dda808 100644 --- a/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj +++ b/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj @@ -1,20 +1,20 @@ - - - - netstandard2.1 - AnyCPU;x64 - Concentus - Logan Stromberg - 1.1.6.0 - Copyright © Xiph.Org Foundation, Skype Limited, CSIRO, Microsoft Corp. - This package is a pure portable C# implementation of the Opus audio compression codec (see https://opus-codec.org/ for more details). This package contains the Opus encoder, decoder, multistream codecs, repacketizer, as well as a port of the libspeexdsp resampler. It does NOT contain code to parse .ogg or .opus container files or to manage RTP packet streams - - https://github.com/lostromb/concentus - - - - full - true - - - + + + + netstandard2.1 + AnyCPU;x64 + Concentus + Logan Stromberg + 1.1.6.0 + Copyright © Xiph.Org Foundation, Skype Limited, CSIRO, Microsoft Corp. + This package is a pure portable C# implementation of the Opus audio compression codec (see https://opus-codec.org/ for more details). This package contains the Opus encoder, decoder, multistream codecs, repacketizer, as well as a port of the libspeexdsp resampler. It does NOT contain code to parse .ogg or .opus container files or to manage RTP packet streams + + https://github.com/lostromb/concentus + + + + full + true + + + diff --git a/Libraries/Facepunch.Steamworks/SteamMatchmaking.cs b/Libraries/Facepunch.Steamworks/SteamMatchmaking.cs index 9c7463ba7..10374743c 100644 --- a/Libraries/Facepunch.Steamworks/SteamMatchmaking.cs +++ b/Libraries/Facepunch.Steamworks/SteamMatchmaking.cs @@ -166,9 +166,9 @@ namespace Steamworks public static async Task CreateLobbyAsync( int maxMembers = 100 ) { var lobby = await Internal.CreateLobby( LobbyType.Invisible, maxMembers ); - if ( !lobby.HasValue || lobby.Value.Result != Result.OK ) return null; + if ( !lobby.HasValue ) { return null; } - return new Lobby { Id = lobby.Value.SteamIDLobby }; + return new Lobby { Id = lobby.Value.SteamIDLobby, Result = lobby.Value.Result }; } /// diff --git a/Libraries/Facepunch.Steamworks/Structs/Lobby.cs b/Libraries/Facepunch.Steamworks/Structs/Lobby.cs index 70ecdf3d7..1a3b3fb2d 100644 --- a/Libraries/Facepunch.Steamworks/Structs/Lobby.cs +++ b/Libraries/Facepunch.Steamworks/Structs/Lobby.cs @@ -8,11 +8,13 @@ namespace Steamworks.Data public struct Lobby { public SteamId Id { get; internal set; } + public Result Result { get; internal set; } public Lobby( SteamId id ) { Id = id; + Result = Result.OK; } /// diff --git a/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj b/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj index 69fbafa2f..96e8ca92c 100644 --- a/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj +++ b/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj @@ -1,40 +1,40 @@ - - - - netstandard2.1 - FarseerPhysics - Copyright Ian Qvist © 2013 - Farseer Physics Engine - - 3.5.0.0 - Ian Qvist - AnyCPU;x64 - - - - TRACE - portable - true - - - - - - - - - - - - - - - - - - - - - - - + + + + netstandard2.1 + FarseerPhysics + Copyright Ian Qvist © 2013 + Farseer Physics Engine + + 3.5.0.0 + Ian Qvist + AnyCPU;x64 + + + + TRACE + portable + true + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj b/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj index cd6803b0e..aab701817 100644 --- a/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj +++ b/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj @@ -1,35 +1,35 @@ - - - - netstandard2.1 - GameAnalytics.NetStandard - GameAnalytics.Net - AnyCPU;x64 - Game Analytics - Copyright (c) 2016 Game Analytics - - - - TRACE;MONO - - - - TRACE;MONO - - - - TRACE;MONO - - - - TRACE;MONO - - - - - - - - - - + + + + netstandard2.1 + GameAnalytics.NetStandard + GameAnalytics.Net + AnyCPU;x64 + Game Analytics + Copyright (c) 2016 Game Analytics + + + + TRACE;MONO + + + + TRACE;MONO + + + + TRACE;MONO + + + + TRACE;MONO + + + + + + + + + + diff --git a/Libraries/Hyper.ComponentModel/Hyper.ComponentModel.NetStandard.csproj b/Libraries/Hyper.ComponentModel/Hyper.ComponentModel.NetStandard.csproj index 4cd0d3594..3e53cc156 100644 --- a/Libraries/Hyper.ComponentModel/Hyper.ComponentModel.NetStandard.csproj +++ b/Libraries/Hyper.ComponentModel/Hyper.ComponentModel.NetStandard.csproj @@ -1,16 +1,16 @@ - - - - netstandard2.1 - Pavel Nosovich - - Hyper.ComponentModel - Copyright (c) 2014 Pavel Nosovich - AnyCPU;x64 - - - - - - - + + + + netstandard2.1 + Pavel Nosovich + + Hyper.ComponentModel + Copyright (c) 2014 Pavel Nosovich + AnyCPU;x64 + + + + + + + diff --git a/Libraries/Lidgren.Network/Lidgren.NetStandard.csproj b/Libraries/Lidgren.Network/Lidgren.NetStandard.csproj index 3e265503b..4cf45b8e4 100644 --- a/Libraries/Lidgren.Network/Lidgren.NetStandard.csproj +++ b/Libraries/Lidgren.Network/Lidgren.NetStandard.csproj @@ -1,33 +1,33 @@ - - - - netstandard2.1 - Lidgren - - Lidgren.Network - Copyright (c) 2015 lidgren - 2012.1.7.0 - AnyCPU;x64 - - - - 1701;1702;3021 - - - - 1701;1702;3021 - - - - 1701;1702;3021 - - - - 1701;1702;3021 - - - - - - - + + + + netstandard2.1 + Lidgren + + Lidgren.Network + Copyright (c) 2015 lidgren + 2012.1.7.0 + AnyCPU;x64 + + + + 1701;1702;3021 + + + + 1701;1702;3021 + + + + 1701;1702;3021 + + + + 1701;1702;3021 + + + + + + + diff --git a/Libraries/MonoGame.Framework/Src/.gitignore b/Libraries/MonoGame.Framework/Src/.gitignore index f605f607e..b0bd399e1 100644 --- a/Libraries/MonoGame.Framework/Src/.gitignore +++ b/Libraries/MonoGame.Framework/Src/.gitignore @@ -1,93 +1,93 @@ -#OS junk files -[Tt]humbs.db -*.DS_Store - -#Output Linux Installer -Installers/Linux/tmp_deb/ -Installers/Linux/tmp_run/ -*.run -*.deb - -#Visual Studio files -*.pidb -*.userprefs -*.[Oo]bj -*.exe -*.pdb -*.user -*.aps -*.pch -*.vspscc -*.vssscc -*_i.c -*_p.c -*.ncb -*.suo -*.tlb -*.tlh -*.bak -*.[Cc]ache -*.ilk -*.log -*.lib -*.sbr -*.sdf -*.csproj.csdat -ipch/ -obj/ -[Bb]in -[Dd]ebug*/ -[Rr]elease*/ -Ankh.NoLoad -.vs/ -project.lock.json -/MonoGame.Framework/MonoGame.Framework.Net.WindowsUniversal.project.lock.json -/MonoGame.Framework/MonoGame.Framework.WindowsUniversal.project.lock.json -artifacts/ - -# JetBrains Rider -.idea/ - -#Tooling -_ReSharper*/ -*.resharper -[Tt]est[Rr]esult* - -#Visual Studio Rebracer extension, allows the user to automatically change the style configuration by project -rebracer.xml - -#Subversion files -.svn - -# Office Temp Files -~$* - -#monodroid private beta -monodroid*.msi - -#Unix temporary files -*~ - -# Output docs -Documentation/Output/ - -# Protobuild Generated Files -*.speccache -*.ncrunchproject -*.ncrunchsolution - -#Mac Package Files -*.pkg -*.mpack -**/packages - -#Nuget Packages -**/*.nupkg - -#Zip files -*.zip - -Installers/MacOS/Scripts/Framework/postinstall -IDE/MonoDevelop/MonoDevelop.MonoGame/templates/Common/MonoGame.Framework.dll.config - -ThirdParty/* +#OS junk files +[Tt]humbs.db +*.DS_Store + +#Output Linux Installer +Installers/Linux/tmp_deb/ +Installers/Linux/tmp_run/ +*.run +*.deb + +#Visual Studio files +*.pidb +*.userprefs +*.[Oo]bj +*.exe +*.pdb +*.user +*.aps +*.pch +*.vspscc +*.vssscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.[Cc]ache +*.ilk +*.log +*.lib +*.sbr +*.sdf +*.csproj.csdat +ipch/ +obj/ +[Bb]in +[Dd]ebug*/ +[Rr]elease*/ +Ankh.NoLoad +.vs/ +project.lock.json +/MonoGame.Framework/MonoGame.Framework.Net.WindowsUniversal.project.lock.json +/MonoGame.Framework/MonoGame.Framework.WindowsUniversal.project.lock.json +artifacts/ + +# JetBrains Rider +.idea/ + +#Tooling +_ReSharper*/ +*.resharper +[Tt]est[Rr]esult* + +#Visual Studio Rebracer extension, allows the user to automatically change the style configuration by project +rebracer.xml + +#Subversion files +.svn + +# Office Temp Files +~$* + +#monodroid private beta +monodroid*.msi + +#Unix temporary files +*~ + +# Output docs +Documentation/Output/ + +# Protobuild Generated Files +*.speccache +*.ncrunchproject +*.ncrunchsolution + +#Mac Package Files +*.pkg +*.mpack +**/packages + +#Nuget Packages +**/*.nupkg + +#Zip files +*.zip + +Installers/MacOS/Scripts/Framework/postinstall +IDE/MonoDevelop/MonoDevelop.MonoGame/templates/Common/MonoGame.Framework.dll.config + +ThirdParty/* diff --git a/Libraries/SharpFont/Source/SharpFont/SharpFont.NetStandard.csproj b/Libraries/SharpFont/Source/SharpFont/SharpFont.NetStandard.csproj index 6f2368424..a4477adaa 100644 --- a/Libraries/SharpFont/Source/SharpFont/SharpFont.NetStandard.csproj +++ b/Libraries/SharpFont/Source/SharpFont/SharpFont.NetStandard.csproj @@ -1,45 +1,45 @@ - - - - netstandard2.1 - SharpFont - SharpFont - Cross-platform FreeType bindings for C# - Robmaister - SharpFont - - Copyright (c) Robert Rouhani 2012-2016 - AnyCPU;x64 - - - - TRACE;DEBUG;SHARPFONT_PORTABLE - true - - - - TRACE;DEBUG;SHARPFONT_PORTABLE - true - 1701;1702;3021 - - - - TRACE;SHARPFONT_PORTABLE - true - - - - TRACE;SHARPFONT_PORTABLE - true - 1701;1702;3021 - - - - - - - - - - - + + + + netstandard2.1 + SharpFont + SharpFont + Cross-platform FreeType bindings for C# + Robmaister + SharpFont + + Copyright (c) Robert Rouhani 2012-2016 + AnyCPU;x64 + + + + TRACE;DEBUG;SHARPFONT_PORTABLE + true + + + + TRACE;DEBUG;SHARPFONT_PORTABLE + true + 1701;1702;3021 + + + + TRACE;SHARPFONT_PORTABLE + true + + + + TRACE;SHARPFONT_PORTABLE + true + 1701;1702;3021 + + + + + + + + + + + diff --git a/Libraries/XNATypes/XNATypes.csproj b/Libraries/XNATypes/XNATypes.csproj index b3b90d794..57fd8b083 100644 --- a/Libraries/XNATypes/XNATypes.csproj +++ b/Libraries/XNATypes/XNATypes.csproj @@ -1,10 +1,10 @@ - - - - netstandard2.1 - AnyCPU;x64 - - - - - + + + + netstandard2.1 + AnyCPU;x64 + + + + + diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/common.props b/Libraries/webm_mem_playback/opus/win32/VS2015/common.props index 6c757d8b7..03cd45b0c 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/common.props +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/common.props @@ -1,82 +1,82 @@ - - - - - - $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\$(ProjectName)\ - Unicode - - - true - true - false - - - false - false - true - - - - Level3 - false - false - ..\..;..\..\include;..\..\silk;..\..\celt;..\..\win32;%(AdditionalIncludeDirectories) - HAVE_CONFIG_H;WIN32;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - false - false - - - Console - - - true - Console - - - - - Guard - ProgramDatabase - NoExtensions - false - true - false - Disabled - false - false - Disabled - MultiThreadedDebug - MultiThreadedDebugDLL - true - false - - - true - - - - - false - None - true - true - false - Speed - Fast - Precise - true - true - true - MaxSpeed - MultiThreaded - MultiThreadedDLL - 16Bytes - - - false - - - + + + + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + Unicode + + + true + true + false + + + false + false + true + + + + Level3 + false + false + ..\..;..\..\include;..\..\silk;..\..\celt;..\..\win32;%(AdditionalIncludeDirectories) + HAVE_CONFIG_H;WIN32;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + false + false + + + Console + + + true + Console + + + + + Guard + ProgramDatabase + NoExtensions + false + true + false + Disabled + false + false + Disabled + MultiThreadedDebug + MultiThreadedDebugDLL + true + false + + + true + + + + + false + None + true + true + false + Speed + Fast + Precise + true + true + true + MaxSpeed + MultiThreaded + MultiThreadedDLL + 16Bytes + + + false + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj b/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj index ae420d508..fc2241116 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj @@ -1,399 +1,399 @@ - - - - - DebugDLL_fixed - Win32 - - - DebugDLL_fixed - x64 - - - DebugDLL - Win32 - - - DebugDLL - x64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseDLL_fixed - Win32 - - - ReleaseDLL_fixed - x64 - - - ReleaseDLL - Win32 - - - ReleaseDLL - x64 - - - Release - Win32 - - - Release - x64 - - - - Win32Proj - opus - {219EC965-228A-1824-174D-96449D05F88A} - - - - StaticLibrary - v142 - - - DynamicLibrary - v142 - - - DynamicLibrary - v142 - - - StaticLibrary - v142 - - - DynamicLibrary - v142 - - - DynamicLibrary - v142 - - - StaticLibrary - v142 - - - DynamicLibrary - v142 - - - DynamicLibrary - v142 - - - StaticLibrary - v142 - - - DynamicLibrary - v142 - - - DynamicLibrary - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ..\..\silk\fixed;..\..\silk\float;%(AdditionalIncludeDirectories) - DLL_EXPORT;%(PreprocessorDefinitions) - FIXED_POINT;%(PreprocessorDefinitions) - /arch:IA32 %(AdditionalOptions) - - - /ignore:4221 %(AdditionalOptions) - - - "$(ProjectDir)..\..\win32\genversion.bat" "$(ProjectDir)..\..\win32\version.h" PACKAGE_VERSION - Generating version.h - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 4244;%(DisableSpecificWarnings) - - - - - - - - - - - - - - - false - - - false - - - true - - - - - - - true - - - true - - - false - - - - - - - + + + + + DebugDLL_fixed + Win32 + + + DebugDLL_fixed + x64 + + + DebugDLL + Win32 + + + DebugDLL + x64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseDLL_fixed + Win32 + + + ReleaseDLL_fixed + x64 + + + ReleaseDLL + Win32 + + + ReleaseDLL + x64 + + + Release + Win32 + + + Release + x64 + + + + Win32Proj + opus + {219EC965-228A-1824-174D-96449D05F88A} + + + + StaticLibrary + v142 + + + DynamicLibrary + v142 + + + DynamicLibrary + v142 + + + StaticLibrary + v142 + + + DynamicLibrary + v142 + + + DynamicLibrary + v142 + + + StaticLibrary + v142 + + + DynamicLibrary + v142 + + + DynamicLibrary + v142 + + + StaticLibrary + v142 + + + DynamicLibrary + v142 + + + DynamicLibrary + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ..\..\silk\fixed;..\..\silk\float;%(AdditionalIncludeDirectories) + DLL_EXPORT;%(PreprocessorDefinitions) + FIXED_POINT;%(PreprocessorDefinitions) + /arch:IA32 %(AdditionalOptions) + + + /ignore:4221 %(AdditionalOptions) + + + "$(ProjectDir)..\..\win32\genversion.bat" "$(ProjectDir)..\..\win32\version.h" PACKAGE_VERSION + Generating version.h + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4244;%(DisableSpecificWarnings) + + + + + + + + + + + + + + + false + + + false + + + true + + + + + + + true + + + true + + + false + + + + + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj.filters b/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj.filters index 97eb46551..47185c67d 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj.filters +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj.filters @@ -1,744 +1,744 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj b/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj index 7ad4b5e21..fcd971bb6 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj @@ -1,171 +1,171 @@ - - - - - DebugDLL_fixed - Win32 - - - DebugDLL_fixed - x64 - - - DebugDLL - Win32 - - - DebugDLL - x64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseDLL_fixed - Win32 - - - ReleaseDLL_fixed - x64 - - - ReleaseDLL - Win32 - - - ReleaseDLL - x64 - - - Release - Win32 - - - Release - x64 - - - - - {219ec965-228a-1824-174d-96449d05f88a} - - - - - - - {016C739D-6389-43BF-8D88-24B2BF6F620F} - Win32Proj - opus_demo - - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + DebugDLL_fixed + Win32 + + + DebugDLL_fixed + x64 + + + DebugDLL + Win32 + + + DebugDLL + x64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseDLL_fixed + Win32 + + + ReleaseDLL_fixed + x64 + + + ReleaseDLL + Win32 + + + ReleaseDLL + x64 + + + Release + Win32 + + + Release + x64 + + + + + {219ec965-228a-1824-174d-96449d05f88a} + + + + + + + {016C739D-6389-43BF-8D88-24B2BF6F620F} + Win32Proj + opus_demo + + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj.filters b/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj.filters index 2eb113ac8..dbcc8ae92 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj.filters +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj.filters @@ -1,22 +1,22 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj index 4ba7c8ae5..e428bd3f7 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj @@ -1,171 +1,171 @@ - - - - - DebugDLL_fixed - Win32 - - - DebugDLL_fixed - x64 - - - DebugDLL - Win32 - - - DebugDLL - x64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseDLL_fixed - Win32 - - - ReleaseDLL_fixed - x64 - - - ReleaseDLL - Win32 - - - ReleaseDLL - x64 - - - Release - Win32 - - - Release - x64 - - - - - - - - {219ec965-228a-1824-174d-96449d05f88a} - - - - {1D257A17-D254-42E5-82D6-1C87A6EC775A} - Win32Proj - test_opus_api - - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + DebugDLL_fixed + Win32 + + + DebugDLL_fixed + x64 + + + DebugDLL + Win32 + + + DebugDLL + x64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseDLL_fixed + Win32 + + + ReleaseDLL_fixed + x64 + + + ReleaseDLL + Win32 + + + ReleaseDLL + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + {219ec965-228a-1824-174d-96449d05f88a} + + + + {1D257A17-D254-42E5-82D6-1C87A6EC775A} + Win32Proj + test_opus_api + + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj.filters b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj.filters index 383d19f71..070c8ab01 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj.filters +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj.filters @@ -1,14 +1,14 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj index 8e4640094..cbf562183 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj @@ -1,171 +1,171 @@ - - - - - DebugDLL_fixed - Win32 - - - DebugDLL_fixed - x64 - - - DebugDLL - Win32 - - - DebugDLL - x64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseDLL_fixed - Win32 - - - ReleaseDLL_fixed - x64 - - - ReleaseDLL - Win32 - - - ReleaseDLL - x64 - - - Release - Win32 - - - Release - x64 - - - - - - - - {219ec965-228a-1824-174d-96449d05f88a} - - - - {8578322A-1883-486B-B6FA-E0094B65C9F2} - Win32Proj - test_opus_api - - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + DebugDLL_fixed + Win32 + + + DebugDLL_fixed + x64 + + + DebugDLL + Win32 + + + DebugDLL + x64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseDLL_fixed + Win32 + + + ReleaseDLL_fixed + x64 + + + ReleaseDLL + Win32 + + + ReleaseDLL + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + {219ec965-228a-1824-174d-96449d05f88a} + + + + {8578322A-1883-486B-B6FA-E0094B65C9F2} + Win32Proj + test_opus_api + + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj.filters b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj.filters index 3036a4e70..588637e83 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj.filters +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj.filters @@ -1,14 +1,14 @@ - - - - - {4a0dd677-931f-4728-afe5-b761149fc7eb} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - - - Source Files - - + + + + + {4a0dd677-931f-4728-afe5-b761149fc7eb} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj index 6804918a3..5a313c31d 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj @@ -1,172 +1,172 @@ - - - - - DebugDLL_fixed - Win32 - - - DebugDLL_fixed - x64 - - - DebugDLL - Win32 - - - DebugDLL - x64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseDLL_fixed - Win32 - - - ReleaseDLL_fixed - x64 - - - ReleaseDLL - Win32 - - - ReleaseDLL - x64 - - - Release - Win32 - - - Release - x64 - - - - - - - - - {219ec965-228a-1824-174d-96449d05f88a} - - - - {84DAA768-1A38-4312-BB61-4C78BB59E5B8} - Win32Proj - test_opus_api - - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + DebugDLL_fixed + Win32 + + + DebugDLL_fixed + x64 + + + DebugDLL + Win32 + + + DebugDLL + x64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseDLL_fixed + Win32 + + + ReleaseDLL_fixed + x64 + + + ReleaseDLL + Win32 + + + ReleaseDLL + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + {219ec965-228a-1824-174d-96449d05f88a} + + + + {84DAA768-1A38-4312-BB61-4C78BB59E5B8} + Win32Proj + test_opus_api + + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj.filters b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj.filters index 4ed3bb9e7..f04776380 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj.filters +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj.filters @@ -1,17 +1,17 @@ - - - - - {546c8d9a-103e-4f78-972b-b44e8d3c8aba} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - - - Source Files - - - Source Files - - + + + + + {546c8d9a-103e-4f78-972b-b44e8d3c8aba} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/genversion.bat b/Libraries/webm_mem_playback/opus/win32/genversion.bat index 1def7460b..aea557393 100644 --- a/Libraries/webm_mem_playback/opus/win32/genversion.bat +++ b/Libraries/webm_mem_playback/opus/win32/genversion.bat @@ -1,37 +1,37 @@ -@echo off - -setlocal enableextensions enabledelayedexpansion - -for /f %%v in ('cd "%~dp0.." ^&^& git status ^>NUL 2^>NUL ^&^& git describe --tags --match "v*" --dirty 2^>NUL') do set version=%%v - -if not "%version%"=="" set version=!version:~1! && goto :gotversion - -if exist "%~dp0..\package_version" goto :getversion - -echo Git cannot be found, nor can package_version. Generating unknown version. - -set version=unknown - -goto :gotversion - -:getversion - -for /f "delims== tokens=2" %%v in (%~dps0..\package_version) do set version=%%v -set version=!version:"=! - -:gotversion - -set version=!version: =! -set version_out=#define %~2 "%version%" - -echo %version_out%> "%~1_temp" - -echo n | comp "%~1_temp" "%~1" > NUL 2> NUL - -if not errorlevel 1 goto exit - -copy /y "%~1_temp" "%~1" - -:exit - -del "%~1_temp" +@echo off + +setlocal enableextensions enabledelayedexpansion + +for /f %%v in ('cd "%~dp0.." ^&^& git status ^>NUL 2^>NUL ^&^& git describe --tags --match "v*" --dirty 2^>NUL') do set version=%%v + +if not "%version%"=="" set version=!version:~1! && goto :gotversion + +if exist "%~dp0..\package_version" goto :getversion + +echo Git cannot be found, nor can package_version. Generating unknown version. + +set version=unknown + +goto :gotversion + +:getversion + +for /f "delims== tokens=2" %%v in (%~dps0..\package_version) do set version=%%v +set version=!version:"=! + +:gotversion + +set version=!version: =! +set version_out=#define %~2 "%version%" + +echo %version_out%> "%~1_temp" + +echo n | comp "%~1_temp" "%~1" > NUL 2> NUL + +if not errorlevel 1 goto exit + +copy /y "%~1_temp" "%~1" + +:exit + +del "%~1_temp" diff --git a/Libraries/webm_mem_playback/webm_mem_playback/webm-mem-playback.vcxproj b/Libraries/webm_mem_playback/webm_mem_playback/webm-mem-playback.vcxproj index 6e90faa56..5a3253ee6 100644 --- a/Libraries/webm_mem_playback/webm_mem_playback/webm-mem-playback.vcxproj +++ b/Libraries/webm_mem_playback/webm_mem_playback/webm-mem-playback.vcxproj @@ -1,148 +1,148 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {D0097438-DA4F-4E6D-87AC-7D99DDD276B2} - vpxmemplayback - 10.0 - webm_mem_playback - - - - DynamicLibrary - true - v142 - MultiByte - - - DynamicLibrary - false - v142 - true - MultiByte - - - DynamicLibrary - true - v142 - MultiByte - - - DynamicLibrary - false - v142 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - $(ProjectName)_$(Platform) - - - - Level3 - Disabled - true - true - ..\libwebm_x86_64_vs15;..\libvpx_x86_64_vs15;%(AdditionalIncludeDirectories) - - - %(AdditionalDependencies) - - - - - Level3 - Disabled - true - true - %(AdditionalIncludeDirectories) - MultiThreadedDebug - - - %(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - true - ..\libwebm_x86_64_vs15;..\libvpx_x86_64_vs15;%(AdditionalIncludeDirectories) - - - true - true - %(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - true - ..\libwebm_x86_vs19;..\libvpx_x64_vs15;..\opus\include;%(AdditionalIncludeDirectories) - MultiThreaded - Speed - - - true - true - ../libvpx_x64_vs15/$(Platform)/$(Configuration)/vpxmt.lib;../libwebm_x64_vs19/Release/libwebm.lib;../opus/win32/VS2015/x64/Release/opus.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {D0097438-DA4F-4E6D-87AC-7D99DDD276B2} + vpxmemplayback + 10.0 + webm_mem_playback + + + + DynamicLibrary + true + v142 + MultiByte + + + DynamicLibrary + false + v142 + true + MultiByte + + + DynamicLibrary + true + v142 + MultiByte + + + DynamicLibrary + false + v142 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + $(ProjectName)_$(Platform) + + + + Level3 + Disabled + true + true + ..\libwebm_x86_64_vs15;..\libvpx_x86_64_vs15;%(AdditionalIncludeDirectories) + + + %(AdditionalDependencies) + + + + + Level3 + Disabled + true + true + %(AdditionalIncludeDirectories) + MultiThreadedDebug + + + %(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + true + ..\libwebm_x86_64_vs15;..\libvpx_x86_64_vs15;%(AdditionalIncludeDirectories) + + + true + true + %(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + true + ..\libwebm_x86_vs19;..\libvpx_x64_vs15;..\opus\include;%(AdditionalIncludeDirectories) + MultiThreaded + Speed + + + true + true + ../libvpx_x64_vs15/$(Platform)/$(Configuration)/vpxmt.lib;../libwebm_x64_vs19/Release/libwebm.lib;../opus/win32/VS2015/x64/Release/opus.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + \ No newline at end of file