diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs index 7fab0116d..79abaed11 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs @@ -226,8 +226,15 @@ namespace Barotrauma if (SelectedConstruction != null && SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect))) { - //emulate a Select input to get the character to deselect the item server-side - keys[(int)InputType.Select].Hit = true; + if (GameMain.Client != null) + { + //emulate a Select input to get the character to deselect the item server-side + keys[(int)InputType.Select].Hit = true; + } + //reset focus to prevent us from accidentally interacting with another entity + focusedItem = null; + focusedCharacter = null; + findFocusedTimer = 0.2f; SelectedConstruction = null; } diff --git a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs index c61e3821d..f8d12f957 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs @@ -453,7 +453,10 @@ namespace Barotrauma.Lights //raster pattern on top of everything spriteBatch.Begin(blendState: BlendState.AlphaBlend, samplerState: SamplerState.LinearWrap); - spriteBatch.Draw(highlightRaster, new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), Color.White * 0.5f); + spriteBatch.Draw(highlightRaster, + new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), + new Rectangle(0, 0, (int)(HighlightMap.Width / currLightMapScale * 0.5f), (int)(HighlightMap.Height / currLightMapScale * 0.5f)), + Color.White * 0.5f); spriteBatch.End(); DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"]; diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index 97342cb7e..ae3a090d2 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs @@ -337,7 +337,24 @@ namespace Barotrauma var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.03f), paddedLeftPanel.RectTransform), TextManager.Get("ShowLighting")) { Selected = lightingEnabled, - OnSelected = (GUITickBox obj) => { lightingEnabled = obj.Selected; return true; } + OnSelected = (GUITickBox obj) => + { + lightingEnabled = obj.Selected; + if (lightingEnabled) + { + //turn off lights that are inside containers + foreach (Item item in Item.ItemList) + { + foreach (LightComponent lightComponent in item.GetComponents()) + { + lightComponent.Light.Color = item.Container != null || (item.body != null && !item.body.Enabled) ? + Color.Transparent : + lightComponent.LightColor; + } + } + } + return true; + } }; tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.03f), paddedLeftPanel.RectTransform), TextManager.Get("ShowWalls")) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs index ce68ecf63..42f9d6515 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs @@ -115,9 +115,13 @@ namespace Barotrauma SonarLabel = element.GetAttributeString("sonarlabel", ""); } - public AITarget(Entity e, float sightRange = 3000, float soundRange = 0) + public AITarget(Entity e, float sightRange = -1, float soundRange = 0) { Entity = e; + if (sightRange < 0) + { + sightRange = StaticSightRange; + } SightRange = sightRange; SoundRange = soundRange; List.Add(this); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs index 33b12d3e9..be09c3888 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs @@ -635,6 +635,7 @@ namespace Barotrauma var newLimb = GetAttackLimb(attackWorldPos, previousLimb); if (newLimb != null) { + // Attack with the new limb AttackingLimb = newLimb; } else @@ -659,6 +660,49 @@ namespace Barotrauma } } break; + case AIBehaviorAfterAttack.FallBackUntilCanAttack: + if (AttackingLimb.attack.SecondaryCoolDown <= 0) + { + // No (valid) secondary cooldown defined. + UpdateFallBack(attackWorldPos, deltaTime); + return; + } + else + { + if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0) + { + // Don't allow attacking when the attack target has just changed. + if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget) + { + UpdateFallBack(attackWorldPos, deltaTime); + return; + } + else + { + // If the secondary cooldown is defined and expired, check if we can switch the attack + var previousLimb = AttackingLimb; + var newLimb = GetAttackLimb(attackWorldPos, previousLimb); + if (newLimb != null) + { + // Attack with the new limb + AttackingLimb = newLimb; + } + else + { + // No new limb was found. + UpdateFallBack(attackWorldPos, deltaTime); + return; + } + } + } + else + { + // Cooldown not yet expired -> steer away from the target + UpdateFallBack(attackWorldPos, deltaTime); + return; + } + } + break; case AIBehaviorAfterAttack.FallBack: default: UpdateFallBack(attackWorldPos, deltaTime); @@ -784,20 +828,39 @@ namespace Barotrauma { AttackContext currentContext = Character.GetAttackContext(); var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity; - var limbs = Character.AnimController.Limbs - .Where(l => - l != ignoredLimb && - l.attack != null && - l.attack.CoolDownTimer <= 0 && - !l.IsSevered && - !l.IsStuck && - l.attack.IsValidContext(currentContext) && - l.attack.IsValidTarget(target) && - l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity) || !(target is Character))) - .OrderByDescending(l => l.attack.Priority) - .ThenBy(l => Vector2.Distance(l.WorldPosition, attackWorldPos)); - // TODO: priority should probably not override the distance -> use values instead of booleans - return limbs.FirstOrDefault(); + Limb selectedLimb = null; + float currentPriority = 0; + foreach (Limb limb in Character.AnimController.Limbs) + { + if (limb == ignoredLimb) { continue; } + if (limb.IsSevered || limb.IsStuck) { continue; } + var attack = limb.attack; + if (attack == null) { continue; } + if (attack.CoolDownTimer > 0) { continue; } + if (!attack.IsValidContext(currentContext)) { continue; } + if (!attack.IsValidTarget(target)) { continue; } + if (target is ISerializableEntity se && target is Character) + { + // TODO: allow conditionals of which matching any is enough instead of having to fulfill all + if (attack.Conditionals.Any(c => !c.Matches(se))) { continue; } + } + float priority = CalculatePriority(limb, attackWorldPos); + if (priority > currentPriority) + { + currentPriority = priority; + selectedLimb = limb; + } + } + return selectedLimb; + + float CalculatePriority(Limb limb, Vector2 attackPos) + { + float dist = Vector2.Distance(limb.WorldPosition, attackPos); + // The limb is ignored if the target is not close. Prevents character going in reverse if very far away from it. + // We also need a max value that is more than the actual range. + float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, limb.attack.Range * 3, dist)); + return (1 + limb.attack.Priority) * distanceFactor; + } } private void UpdateWallTarget() diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs b/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs index 955ba731b..0e84aa580 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs @@ -28,6 +28,7 @@ namespace Barotrauma public enum AIBehaviorAfterAttack { FallBack, + FallBackUntilCanAttack, PursueIfCanAttack, Pursue } @@ -188,7 +189,7 @@ namespace Barotrauma public readonly List Afflictions = new List(); /// - /// Only affects ai decision making. + /// Only affects ai decision making. All the conditionals has to be met in order to select the attack. TODO: allow to define conditionals using any (implemented in StatusEffect -> move from there to PropertyConditional?) /// public List Conditionals { get; private set; } = new List(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs index aac7bf9df..d9abe0511 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs @@ -162,6 +162,7 @@ namespace Barotrauma.Items.Components if (contained == null) continue; if (contained.TryInteract(character)) { + character.FocusedItem = contained; return false; } } @@ -178,6 +179,7 @@ namespace Barotrauma.Items.Components if (contained == null) continue; if (contained.TryInteract(picker)) { + picker.FocusedItem = contained; return true; } }