diff --git a/Barotrauma/BarotraumaClient/Barotrauma b/Barotrauma/BarotraumaClient/Barotrauma new file mode 100755 index 000000000..2a9280054 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Barotrauma @@ -0,0 +1,35 @@ +#!/bin/bash +# MonoKickstart Shell Script +# Written by Ethan "flibitijibibo" Lee + +# Move to script's directory +cd "`dirname "$0"`" + +# Get the system architecture +UNAME=`uname` +ARCH=`uname -m` + +# MonoKickstart picks the right libfolder, so just execute the right binary. +if [ "$UNAME" == "Darwin" ]; then + # ... Except on OSX. + export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:./osx/ + + # El Capitan is a total idiot and wipes this variable out, making the + # Steam overlay disappear. This sidesteps "System Integrity Protection" + # and resets the variable with Valve's own variable (they provided this + # fix by the way, thanks Valve!). Note that you will need to update your + # launch configuration to the script location, NOT just the app location + # (i.e. Kick.app/Contents/MacOS/Kick, not just Kick.app). + # -flibit + if [ "$STEAM_DYLD_INSERT_LIBRARIES" != "" ] && [ "$DYLD_INSERT_LIBRARIES" == "" ]; then + export DYLD_INSERT_LIBRARIES="$STEAM_DYLD_INSERT_LIBRARIES" + fi + + ./Barotrauma.bin.osx $@ +else + if [ "$ARCH" == "x86_64" ]; then + ./Barotrauma.bin.x86_64 $@ + else + ./Barotrauma.bin.x86 $@ + fi +fi diff --git a/Barotrauma/BarotraumaClient/Barotrauma.bin.osx b/Barotrauma/BarotraumaClient/Barotrauma.bin.osx new file mode 100644 index 000000000..98bf55fc7 Binary files /dev/null and b/Barotrauma/BarotraumaClient/Barotrauma.bin.osx differ diff --git a/Barotrauma/BarotraumaClient/Barotrauma.bin.x86 b/Barotrauma/BarotraumaClient/Barotrauma.bin.x86 new file mode 100755 index 000000000..c7c060f04 Binary files /dev/null and b/Barotrauma/BarotraumaClient/Barotrauma.bin.x86 differ diff --git a/Barotrauma/BarotraumaClient/Barotrauma.bin.x86_64 b/Barotrauma/BarotraumaClient/Barotrauma.bin.x86_64 new file mode 100755 index 000000000..47535d05b Binary files /dev/null and b/Barotrauma/BarotraumaClient/Barotrauma.bin.x86_64 differ diff --git a/Barotrauma/BarotraumaClient/ClientCode.projitems b/Barotrauma/BarotraumaClient/ClientCode.projitems index 1b79fd2eb..e2df0a544 100644 --- a/Barotrauma/BarotraumaClient/ClientCode.projitems +++ b/Barotrauma/BarotraumaClient/ClientCode.projitems @@ -43,8 +43,12 @@ - + + + + + diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index f0fa4331e..f0d80d9ee 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -193,6 +193,60 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 29dd488d3..f9fdba9c8 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -188,7 +188,62 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + diff --git a/Barotrauma/BarotraumaClient/Mono.Posix.dll b/Barotrauma/BarotraumaClient/Mono.Posix.dll new file mode 100755 index 000000000..6f4550923 Binary files /dev/null and b/Barotrauma/BarotraumaClient/Mono.Posix.dll differ diff --git a/Barotrauma/BarotraumaClient/Mono.Security.dll b/Barotrauma/BarotraumaClient/Mono.Security.dll new file mode 100755 index 000000000..a50cd152c Binary files /dev/null and b/Barotrauma/BarotraumaClient/Mono.Security.dll differ diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index b67af8559..5e2918897 100644 --- a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.8.9.10")] -[assembly: AssemblyFileVersion("0.8.9.10")] +[assembly: AssemblyVersion("0.8.10.0")] +[assembly: AssemblyFileVersion("0.8.10.0")] diff --git a/Barotrauma/BarotraumaClient/SharpFont.dll.config b/Barotrauma/BarotraumaClient/SharpFont.dll.config index 719e30c78..8129b80cb 100755 --- a/Barotrauma/BarotraumaClient/SharpFont.dll.config +++ b/Barotrauma/BarotraumaClient/SharpFont.dll.config @@ -1,6 +1,6 @@ - + diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index da396dad7..667d29d62 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs @@ -264,35 +264,17 @@ namespace Barotrauma partial void ImpactProjSpecific(float impact, Body body) { - float volume = Math.Min(impact - 3.0f, 1.0f); + float volume = MathHelper.Clamp(impact - 3.0f, 0.5f, 1.0f); - if (body.UserData is Limb && character.Stun <= 0f) + if (body.UserData is Limb limb && character.Stun <= 0f) { - Limb limb = (Limb)body.UserData; - if (impact > 3.0f && limb.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval) - { - limb.LastImpactSoundTime = (float)Timing.TotalTime; - if (!string.IsNullOrWhiteSpace(limb.HitSoundTag)) - { - SoundPlayer.PlaySound(limb.HitSoundTag, volume, impact * 100.0f, limb.WorldPosition, character.CurrentHull); - } - foreach (WearableSprite wearable in limb.WearingItems) - { - if (limb.type == wearable.Limb && !string.IsNullOrWhiteSpace(wearable.Sound)) - { - SoundPlayer.PlaySound(wearable.Sound, volume, impact * 100.0f, limb.WorldPosition, character.CurrentHull); - } - } - } + if (impact > 3.0f) { PlayImpactSound(limb); } } else if (body.UserData is Limb || body == Collider.FarseerBody) { - if (!character.IsRemotePlayer) + if (!character.IsRemotePlayer && impact > ImpactTolerance) { - if (impact > ImpactTolerance) - { - SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider); - } + SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider); } } if (Character.Controlled == character) @@ -301,6 +283,29 @@ namespace Barotrauma } } + public void PlayImpactSound(Limb limb) + { + limb.LastImpactSoundTime = (float)Timing.TotalTime; + if (!string.IsNullOrWhiteSpace(limb.HitSoundTag)) + { + bool inWater = limb.inWater; + if (character.CurrentHull != null && + character.CurrentHull.Surface > character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height && + limb.SimPosition.Y < ConvertUnits.ToSimUnits(character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height) + limb.body.GetMaxExtent()) + { + inWater = true; + } + SoundPlayer.PlaySound(inWater ? "footstep_water" : limb.HitSoundTag, limb.WorldPosition, hullGuess: character.CurrentHull); + } + foreach (WearableSprite wearable in limb.WearingItems) + { + if (limb.type == wearable.Limb && !string.IsNullOrWhiteSpace(wearable.Sound)) + { + SoundPlayer.PlaySound(wearable.Sound, limb.WorldPosition, hullGuess: character.CurrentHull); + } + } + } + partial void Splash(Limb limb, Hull limbHull) { //create a splash particle @@ -363,6 +368,8 @@ namespace Barotrauma partial void UpdateProjSpecific(float deltaTime) { + if (!character.Enabled || SimplePhysicsEnabled) { return; } + LimbJoints.ForEach(j => j.UpdateDeformations(deltaTime)); foreach (var deformation in SpriteDeformations) { @@ -393,7 +400,7 @@ namespace Barotrauma } } - partial void SeverLimbJointProjSpecific(LimbJoint limbJoint) + partial void SeverLimbJointProjSpecific(LimbJoint limbJoint, bool playSound = true) { foreach (Limb limb in new Limb[] { limbJoint.LimbA, limbJoint.LimbB }) { @@ -411,6 +418,11 @@ namespace Barotrauma character.CurrentHull?.AddDecal(character.BloodDecalName, limb.WorldPosition, MathHelper.Clamp(limb.Mass, 0.5f, 2.0f)); } } + + if (playSound) + { + SoundPlayer.PlayDamageSound("Gore", 1.0f, limbJoint.LimbA.body); + } } public virtual void Draw(SpriteBatch spriteBatch, Camera cam) @@ -428,14 +440,15 @@ namespace Barotrauma return; } - //foreach (Limb limb in Limbs) - //{ - // limb.Draw(spriteBatch, cam); - //} + Color? color = null; + if (character.ExternalHighlight) + { + color = Color.Lerp(Color.White, Color.OrangeRed, (float)Math.Sin(Timing.TotalTime * 3.5f)); + } for (int i = 0; i < limbs.Length; i++) { - inversedLimbDrawOrder[i].Draw(spriteBatch, cam); + inversedLimbDrawOrder[i].Draw(spriteBatch, cam, color); } LimbJoints.ForEach(j => j.Draw(spriteBatch)); } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Attack.cs b/Barotrauma/BarotraumaClient/Source/Characters/Attack.cs index 7d293d643..3b3626b65 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Attack.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Attack.cs @@ -50,7 +50,7 @@ namespace Barotrauma if (sound != null) { - SoundPlayer.PlaySound(sound.Sound, sound.Volume, sound.Range, worldPosition); + SoundPlayer.PlaySound(sound.Sound, worldPosition, sound.Volume, sound.Range); } } } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs index 4e611d6a6..2eff39aa6 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs @@ -33,6 +33,8 @@ namespace Barotrauma private List sounds; + public bool ExternalHighlight; + //the Character that the player is currently controlling private static Character controlled; @@ -109,6 +111,33 @@ namespace Barotrauma get { return gibEmitters; } } + public class ObjectiveEntity + { + public Entity Entity; + public Sprite Sprite; + public Color Color; + + public ObjectiveEntity(Entity entity, Sprite sprite, Color? color = null) + { + Entity = entity; + Sprite = sprite; + if (color.HasValue) + { + Color = color.Value; + } + else + { + Color = Color.White; + } + } + } + + private List activeObjectiveEntities = new List(); + public IEnumerable ActiveObjectiveEntities + { + get { return activeObjectiveEntities; } + } + partial void InitProjSpecific(XDocument doc) { soundInterval = doc.Root.GetAttributeFloat("soundinterval", 10.0f); @@ -728,15 +757,29 @@ namespace Barotrauma var matchingSoundsList = matchingSounds.ToList(); var selectedSound = matchingSoundsList[Rand.Int(matchingSoundsList.Count)]; - soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, selectedSound.Volume, selectedSound.Range, AnimController.WorldPosition, CurrentHull); + soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, AnimController.WorldPosition, selectedSound.Volume, selectedSound.Range, CurrentHull); soundTimer = soundInterval; } + public void AddActiveObjectiveEntity(Entity entity, Sprite sprite, Color? color = null) + { + if (activeObjectiveEntities.Any(aoe => aoe.Entity == entity)) return; + ObjectiveEntity objectiveEntity = new ObjectiveEntity(entity, sprite, color); + activeObjectiveEntities.Add(objectiveEntity); + } + + public void RemoveActiveObjectiveEntity(Entity entity) + { + ObjectiveEntity found = activeObjectiveEntities.Find(aoe => aoe.Entity == entity); + if (found == null) return; + activeObjectiveEntities.Remove(found); + } + partial void ImplodeFX() { Vector2 centerOfMass = AnimController.GetCenterOfMass(); - SoundPlayer.PlaySound("implode", 1.0f, 150.0f, WorldPosition); + SoundPlayer.PlaySound("implode", WorldPosition); for (int i = 0; i < 10; i++) { diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs index a2137e660..441ae6a6e 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs @@ -168,7 +168,12 @@ namespace Barotrauma DrawOrderIndicator(spriteBatch, cam, character, character.CurrentOrder, 1.0f); } } - + + foreach (Character.ObjectiveEntity objectiveEntity in character.ActiveObjectiveEntities) + { + DrawObjectiveIndicator(spriteBatch, cam, character, objectiveEntity, 1.0f); + } + foreach (Item brokenItem in brokenItems) { float dist = Vector2.Distance(character.WorldPosition, brokenItem.WorldPosition); @@ -371,5 +376,13 @@ namespace Barotrauma orderIndicatorCount[target] = orderIndicatorCount[target] + 1; } + + private static void DrawObjectiveIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Character.ObjectiveEntity objectiveEntity, float iconAlpha = 1.0f) + { + if (objectiveEntity == null) return; + + Vector2 drawPos = objectiveEntity.Entity.WorldPosition;// + Vector2.UnitX * objectiveEntity.Sprite.size.X * 1.5f; + GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, objectiveEntity.Sprite, objectiveEntity.Color * iconAlpha); + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs index 777bc7775..34ebcb336 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs @@ -96,6 +96,11 @@ namespace Barotrauma private const float UpdateDisplayedAfflictionsInterval = 0.5f; private List currentDisplayedAfflictions = new List(); + public bool MouseOnElement + { + get { return highlightedLimbIndex > -1 || GUI.MouseOn == dropItemArea; } + } + private static CharacterHealth openHealthWindow; public static CharacterHealth OpenHealthWindow { @@ -131,6 +136,17 @@ namespace Barotrauma } } + public GUIButton CPRButton + { + get { return cprButton; } + } + + public float HealthBarPulsateTimer + { + get { return healthBarPulsateTimer; } + set { healthBarPulsateTimer = MathHelper.Clamp(value, 0.0f, 10.0f); } + } + static CharacterHealth() { damageOverlay = new Sprite("Content/UI/damageOverlay.png", Vector2.Zero); @@ -172,18 +188,16 @@ namespace Barotrauma afflictionInfoContainer = new GUIListBox(new RectTransform(new Vector2(0.7f, 0.85f), paddedInfoFrame.RectTransform, Anchor.BottomLeft)); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.08f), paddedInfoFrame.RectTransform), TextManager.Get("SuitableTreatments"), textAlignment: Alignment.TopRight); - lowSkillIndicator = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.07f), paddedInfoFrame.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.0f, 0.08f) }, - TextManager.Get("LowMedicalSkillWarning"), Color.Orange, textAlignment: Alignment.Center, font: GUI.SmallFont, wrap: true) + lowSkillIndicator = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), paddedInfoFrame.RectTransform, Anchor.TopRight), + TextManager.Get("LowMedicalSkillWarning"), Color.Orange, textAlignment: Alignment.TopRight, font: GUI.SmallFont, wrap: true) { Visible = false }; - recommendedTreatmentContainer = new GUIListBox(new RectTransform(new Vector2(0.28f, 0.5f), paddedInfoFrame.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.0f, 0.15f) }) - { - Spacing = 10 - }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), paddedInfoFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.05f) }, TextManager.Get("SuitableTreatments"), textAlignment: Alignment.BottomRight); + + recommendedTreatmentContainer = new GUIListBox(new RectTransform(new Vector2(0.28f, 0.5f), paddedInfoFrame.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.0f, 0.12f) }); dropItemArea = new GUIFrame(new RectTransform(new Vector2(0.28f, 0.3f), paddedInfoFrame.RectTransform, Anchor.BottomRight) - { RelativeOffset = new Vector2(0.0f, 0.0f) }, style: null) + { RelativeOffset = new Vector2(0.02f, 0.0f) }, style: null) { ToolTip = TextManager.Get("HealthItemUseTip") }; @@ -584,9 +598,18 @@ namespace Barotrauma var affliction = GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None) .OrderByDescending(a => a.DamagePerSecond) .ThenByDescending(a => a.Strength).FirstOrDefault(); - var limbHealth = GetMathingLimbHealth(affliction); - if (limbHealth != null) + if (affliction.DamagePerSecond > 0 || affliction.Strength > 0) { + var limbHealth = GetMathingLimbHealth(affliction); + if (limbHealth != null) + { + selectedLimbIndex = limbHealths.IndexOf(limbHealth); + } + } + else + { + // If no affliction is critical, select the limb which has most damage. + var limbHealth = limbHealths.OrderByDescending(l => l.TotalDamage).FirstOrDefault(); selectedLimbIndex = limbHealths.IndexOf(limbHealth); } } @@ -928,7 +951,7 @@ namespace Barotrauma { afflictionInfoContainer.Content.ClearChildren(); recommendedTreatmentContainer.Content.ClearChildren(); - + float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel("medical"); //random variance is 200% when the skill is 0 @@ -1023,14 +1046,15 @@ namespace Barotrauma { ItemPrefab item = MapEntityPrefab.Find(name: null, identifier: treatment.Key, showErrorMessages: false) as ItemPrefab; if (item == null) continue; - int slotSize = (int)(recommendedTreatmentContainer.Content.Rect.Width * 0.8f); + int slotSize = (int)(recommendedTreatmentContainer.Content.Rect.Width * 0.5f); - var itemSlot = new GUIButton(new RectTransform(new Point(slotSize), recommendedTreatmentContainer.Content.RectTransform, Anchor.TopCenter), - text: "", style: "InventorySlotSmall") + var itemSlot = new GUIFrame(new RectTransform(new Point(recommendedTreatmentContainer.Content.Rect.Width, slotSize), recommendedTreatmentContainer.Content.RectTransform, Anchor.TopCenter), + style: "InnerGlow") { - UserData = item + UserData = item, + CanBeFocused = false }; - itemSlot.Color = ToolBox.GradientLerp(treatment.Value, Color.Red, Color.White, Color.LightGreen); + itemSlot.Color = ToolBox.GradientLerp(treatment.Value, Color.Red, Color.Orange, Color.LightGreen); Sprite itemSprite = item.InventoryIcon ?? item.sprite; Color itemColor = itemSprite == item.sprite ? item.SpriteColor : item.InventoryIconColor; @@ -1414,6 +1438,8 @@ namespace Barotrauma { foreach (Limb limb in Character.AnimController.Limbs) { + if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; } + limb.BurnOverlayStrength = 0.0f; limb.DamageOverlayStrength = 0.0f; if (limbHealths[limb.HealthIndex].Afflictions.Count == 0) continue; diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs index 0db66467c..63beecb56 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs @@ -292,10 +292,10 @@ namespace Barotrauma if (!string.IsNullOrWhiteSpace(damageModifier.DamageSound)) { damageSoundType = damageModifier.DamageSound; - SoundPlayer.PlayDamageSound(damageSoundType, Math.Max(damage, bleedingDamage), WorldPosition); break; } } + SoundPlayer.PlayDamageSound(damageSoundType, Math.Max(damage, bleedingDamage), WorldPosition); } // Always spawn damage particles diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index 697d8f31d..a3b0fdc51 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -346,7 +346,7 @@ namespace Barotrauma } })); - commands.Add(new Command("mainmenuscreen|mainmenu|menu", "mainmenu/menu: Go to the main menu.", (string[] args) => + commands.Add(new Command("mainmenu|menu", "mainmenu/menu: Go to the main menu.", (string[] args) => { GameMain.GameSession = null; @@ -359,12 +359,16 @@ namespace Barotrauma GameMain.MainMenuScreen.Select(); })); - commands.Add(new Command("gamescreen|game", "gamescreen/game: Go to the \"in-game\" view.", (string[] args) => + commands.Add(new Command("game", "gamescreen/game: Go to the \"in-game\" view.", (string[] args) => { + if (Screen.Selected == GameMain.SubEditorScreen) + { + NewMessage("WARNING: Switching directly from the submarine editor to the game view may cause bugs and crashes. Use with caution.", Color.Orange); + } GameMain.GameScreen.Select(); })); - commands.Add(new Command("editsubscreen|editsub|subeditor", "editsub/subeditor: Switch to the submarine editor.", (string[] args) => + commands.Add(new Command("editsubs|subeditor", "editsubs/subeditor: Switch to the Submarine Editor to create or edit submarines.", (string[] args) => { if (args.Length > 0) { @@ -373,23 +377,27 @@ namespace Barotrauma GameMain.SubEditorScreen.Select(); })); - commands.Add(new Command("editparticles|particleeditor", "", (string[] args) => + commands.Add(new Command("editparticles|particleeditor", "editparticles/particleeditor: Switch to the Particle Editor to edit particle effects.", (string[] args) => { GameMain.ParticleEditorScreen.Select(); })); - commands.Add(new Command("editlevels|editlevel|leveleditor", "", (string[] args) => + commands.Add(new Command("editlevels|leveleditor", "editlevels/leveleditor: Switch to the Level Editor to edit levels.", (string[] args) => { GameMain.LevelEditorScreen.Select(); })); - commands.Add(new Command("editsprites|editsprite|spriteeditor|spriteedit", "", (string[] args) => + commands.Add(new Command("editsprites|spriteeditor", "editsprites/spriteeditor: Switch to the Sprite Editor to edit the source rects and origins of sprites.", (string[] args) => { GameMain.SpriteEditorScreen.Select(); })); - commands.Add(new Command("charactereditor|editcharacter|editcharacters|editanimation|editanimations|animedit|animationeditor|animeditor|animationedit", "charactereditor: Edit characters, animations, ragdolls....", (string[] args) => + commands.Add(new Command("editcharacters|charactereditor", "editcharacters/charactereditor: Switch to the Character Editor to edit/create the ragdolls and animations of characters.", (string[] args) => { + if (Screen.Selected == GameMain.GameScreen) + { + NewMessage("WARNING: Switching between the character editor and the game view may cause odd behaviour or bugs. Use with caution.", Color.Orange); + } GameMain.CharacterEditorScreen.Select(); })); @@ -555,6 +563,37 @@ namespace Barotrauma } })); + commands.Add(new Command("resetselected", "Reset selected items and structures to prefabs. Only applicable in the subeditor.", args => + { + if (Screen.Selected == GameMain.SubEditorScreen) + { + foreach (MapEntity entity in MapEntity.SelectedList) + { + if (entity is Item item) + { + item.Reset(); + } + else if (entity is Structure structure) + { + structure.Reset(); + } + } + foreach (MapEntity entity in MapEntity.SelectedList) + { + if (entity is Item item) + { + item.CreateEditingHUD(); + break; + } + else if (entity is Structure structure) + { + structure.CreateEditingHUD(); + break; + } + } + } + })); + commands.Add(new Command("alpha", "Change the alpha (as bytes from 0 to 255) of the selected item/structure instances. Applied only in the subeditor.", (string[] args) => { if (Screen.Selected == GameMain.SubEditorScreen) @@ -1057,8 +1096,8 @@ namespace Barotrauma List lines = new List(); foreach (MapEntityPrefab me in MapEntityPrefab.List) { - lines.Add("" + me.Name + ""); - lines.Add("" + me.Description + ""); + lines.Add("" + me.Name + ""); + lines.Add("" + me.Description + ""); } File.WriteAllLines(filePath, lines); })); @@ -1488,7 +1527,7 @@ namespace Barotrauma character.AnimController.ResetRagdoll(); }, isCheat: true)); - commands.Add(new Command("reloadwearables|reloadlimbs", "Reloads the sprites of all limbs and wearable sprites (clothing) of the controlled character. Provide id or name if you want to target another character.", args => + commands.Add(new Command("reloadwearables", "Reloads the sprites of all limbs and wearable sprites (clothing) of the controlled character. Provide id or name if you want to target another character.", args => { var character = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, true); if (character == null) @@ -1499,6 +1538,26 @@ namespace Barotrauma ReloadWearables(character); }, isCheat: true)); + commands.Add(new Command("loadwearable", "Force select certain variant for the selected character.", args => + { + var character = Character.Controlled; + if (character == null) + { + ThrowError("Not controlling any character."); + return; + } + if (args.Length == 0) + { + ThrowError("No arguments provided! Give an index number for the variant starting from 1."); + return; + } + if (int.TryParse(args[0], out int variant)) + { + ReloadWearables(character, variant); + } + + }, isCheat: true)); + commands.Add(new Command("reloadsprite|reloadsprites", "Reloads the sprites of the selected item(s)/structure(s) (hovering over or selecting in the subeditor) or the controlled character. Can also reload sprites by entity id or by the name attribute (sprite element). Example 1: reloadsprite id itemid. Example 2: reloadsprite name \"Sprite name\"", args => { if (Screen.Selected is SpriteEditorScreen) @@ -1646,7 +1705,7 @@ namespace Barotrauma }, isCheat: true)); } - private static void ReloadWearables(Character character) + private static void ReloadWearables(Character character, int variant = 0) { foreach (var limb in character.AnimController.Limbs) { @@ -1655,11 +1714,17 @@ namespace Barotrauma limb.DeformSprite?.Sprite.ReloadTexture(); foreach (var wearable in limb.WearingItems) { + if (variant > 0 && wearable.Variant > 0) + { + wearable.Variant = variant; + } + wearable.RefreshPath(); wearable.Sprite.ReloadXML(); wearable.Sprite.ReloadTexture(); } foreach (var wearable in limb.OtherWearables) { + wearable.RefreshPath(); wearable.Sprite.ReloadXML(); wearable.Sprite.ReloadTexture(); } diff --git a/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs b/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs index 5dc29999c..7a63b7cb6 100644 --- a/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs +++ b/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs @@ -7,7 +7,7 @@ using System.Xml.Linq; namespace Barotrauma { - public class ScalableFont + public class ScalableFont : IDisposable { private static List FontList = new List(); private static Library Lib = null; @@ -30,7 +30,7 @@ namespace Barotrauma set { size = value; - if (graphicsDevice!=null) RenderAtlas(graphicsDevice, charRanges, texDims, baseChar); + if (graphicsDevice != null) RenderAtlas(graphicsDevice, charRanges, texDims, baseChar); } } @@ -279,7 +279,7 @@ namespace Barotrauma if (text[i] == '\n') { currentLineX = 0.0f; - retVal.Y += baseHeight * 18 / 10; + retVal.Y += baseHeight * 1.8f; continue; } uint charIndex = text[i]; @@ -302,5 +302,15 @@ namespace Barotrauma } return retVal; } + + public void Dispose() + { + FontList.Remove(this); + foreach (Texture2D texture in textures) + { + texture.Dispose(); + } + textures.Clear(); + } } } diff --git a/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs index 64fecb33b..ff3cd1dae 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/ChatBox.cs @@ -18,15 +18,27 @@ namespace Barotrauma private GUITextBox inputBox; private GUIButton toggleButton; - - private GUIButton radioButton; - + private Point screenResolution; private bool isSinglePlayer; public bool IsSinglePlayer => isSinglePlayer; - private bool toggleOpen = true; + private bool _toggleOpen = true; + public bool ToggleOpen + { + get { return _toggleOpen; } + set + { + if (_toggleOpen == value) { return; } + _toggleOpen = GameMain.Config.ChatOpen = value; + foreach (GUIComponent child in ToggleButton.Children) + { + child.SpriteEffects = _toggleOpen == (HUDLayoutSettings.ChatBoxAlignment == Alignment.Right) ? + SpriteEffects.FlipHorizontally : SpriteEffects.None; + } + } + } private float openState; private float prevUIScale; @@ -46,12 +58,7 @@ namespace Barotrauma { get { return guiFrame; } } - - public GUIButton RadioButton - { - get { return radioButton; } - } - + public GUITextBox InputBox { get { return inputBox; } @@ -81,12 +88,7 @@ namespace Barotrauma toggleButton.OnClicked += (GUIButton btn, object userdata) => { - toggleOpen = !toggleOpen; - foreach (GUIComponent child in btn.Children) - { - child.SpriteEffects = toggleOpen == (HUDLayoutSettings.ChatBoxAlignment == Alignment.Right) ? - SpriteEffects.FlipHorizontally : SpriteEffects.None; - } + ToggleOpen = !ToggleOpen; return true; }; @@ -100,30 +102,8 @@ namespace Barotrauma { gui.Text = ""; }; - - radioButton = new GUIButton(new RectTransform(new Vector2(0.1f, 2.0f), inputBox.RectTransform, - HUDLayoutSettings.ChatBoxAlignment == Alignment.Right ? Anchor.BottomRight : Anchor.BottomLeft, - HUDLayoutSettings.ChatBoxAlignment == Alignment.Right ? Pivot.TopRight : Pivot.TopLeft), - style: null); - new GUIImage(new RectTransform(Vector2.One, radioButton.RectTransform), radioIcon, scaleToFit: true); - radioButton.OnClicked = (GUIButton btn, object userData) => - { - if (inputBox.Selected) - { - inputBox.Text = ""; - inputBox.Deselect(); - } - else - { - inputBox.Select(); - var radioItem = Character.Controlled?.Inventory?.Items.FirstOrDefault(i => i?.GetComponent() != null); - if (radioItem != null && Character.Controlled.HasEquippedItem(radioItem) && radioItem.GetComponent().CanTransmit()) - { - inputBox.Text = "r; "; - } - } - return true; - }; + + ToggleOpen = GameMain.Config.ChatOpen; } public bool TypingChatMessage(GUITextBox textBox, string text) @@ -228,7 +208,7 @@ namespace Barotrauma chatBox.UpdateScrollBarSize(); - if (!toggleOpen) + if (!ToggleOpen) { var popupMsg = new GUIFrame(new RectTransform(Vector2.One, guiFrame.RectTransform), style: "GUIToolTip") { @@ -313,9 +293,7 @@ namespace Barotrauma prevUIScale = GUI.Scale; } - - - if (toggleOpen || (inputBox != null && inputBox.Selected)) + if (ToggleOpen || (inputBox != null && inputBox.Selected)) { openState += deltaTime * 5.0f; //delete all popup messages when the chatbox is open @@ -359,7 +337,6 @@ namespace Barotrauma } openState = MathHelper.Clamp(openState, 0.0f, 1.0f); int hiddenBoxOffset = guiFrame.Rect.Width + toggleButton.Rect.Width; - if (radioButton != null) hiddenBoxOffset += (int)(radioButton.Rect.Width * 1.5f); guiFrame.RectTransform.AbsoluteOffset = new Point((int)MathHelper.SmoothStep(hiddenBoxOffset * (HUDLayoutSettings.ChatBoxAlignment == Alignment.Left ? -1 : 1), 0, openState), 0); } diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs index 87ccc8154..bb18cab55 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs @@ -129,6 +129,8 @@ namespace Barotrauma get { return pauseMenuOpen; } } + public static bool PreventPauseMenuToggle = false; + public static Color ScreenOverlayColor { get; @@ -1413,6 +1415,7 @@ namespace Barotrauma public static void TogglePauseMenu() { if (Screen.Selected == GameMain.MainMenuScreen) return; + if (PreventPauseMenuToggle) return; settingsMenuOpen = false; @@ -1546,9 +1549,9 @@ namespace Barotrauma if (GameMain.GameSession != null) { - if (ContextualTutorial.Initialized && GameMain.GameSession.GameMode is SinglePlayerCampaign) + if (Tutorial.Initialized) { - ((SinglePlayerCampaign)GameMain.GameSession.GameMode).ContextualTutorial.Stop(); + ((TutorialMode)GameMain.GameSession.GameMode).Tutorial.Stop(); } if (GameSettings.SendUserStatistics) @@ -1559,6 +1562,8 @@ namespace Barotrauma } GameMain.GameSession = null; } + + GUIMessageBox.CloseAll(); GameMain.MainMenuScreen.Select(); @@ -1566,7 +1571,7 @@ namespace Barotrauma } /// - /// Displays a message at the center of the screen, automatically preventing overlapping with other centered messages + /// Displays a message at the center of the screen, automatically preventing overlapping with other centered messages. TODO: Allow to show messages at the middle of the screen (instead of the top center). /// public static void AddMessage(string message, Color color, float? lifeTime = null, bool playSound = true, ScalableFont font = null) { diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIButton.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIButton.cs index 7917d904f..c4746fa69 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIButton.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIButton.cs @@ -168,6 +168,11 @@ namespace Barotrauma if (frame != null) frame.ApplyStyle(style); } + public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, Vector2? flashRectInflate = null) + { + Frame.Flash(color, flashDuration, useRectangleFlash, flashRectInflate); + } + protected override void Draw(SpriteBatch spriteBatch) { //do nothing diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs index c836b1f41..6f56b0654 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs @@ -121,11 +121,19 @@ namespace Barotrauma protected Color selectedColor; protected Color pressedColor; + private CoroutineHandle pulsateCoroutine; + protected ComponentState state; protected Color flashColor; protected float flashDuration = 1.5f; + private bool useRectangleFlash; + public float FlashTimer + { + get { return flashTimer; } + } protected float flashTimer; + private Vector2 flashRectInflate; public bool IgnoreLayoutGroups; @@ -261,6 +269,8 @@ namespace Barotrauma set { pressedColor = value; } } + public bool ExternalHighlight = false; + private RectTransform rectTransform; public RectTransform RectTransform { @@ -435,11 +445,21 @@ namespace Barotrauma int flashCycleCount = (int)Math.Max(flashDuration, 1); float flashCycleDuration = flashDuration / flashCycleCount; + Rectangle flashRect = Rect; + flashRect.Inflate(flashRectInflate.X, flashRectInflate.Y); + //MathHelper.Pi * 0.8f -> the curve goes from 144 deg to 0, //i.e. quickly bumps up from almost full brightness to full and then fades out - GUI.UIGlow.Draw(spriteBatch, - rect, - flashColor * (float)Math.Sin(flashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f)); + if (!useRectangleFlash) + { + GUI.UIGlow.Draw(spriteBatch, + flashRect, + flashColor * (float)Math.Sin(flashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f)); + } + else + { + GUI.DrawRectangle(spriteBatch, flashRect, flashColor * (float)Math.Sin(flashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), true); + } } } @@ -487,9 +507,11 @@ namespace Barotrauma color = new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, a); } - public virtual void Flash(Color? color = null, float flashDuration = 1.5f) + public virtual void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, Vector2? flashRectInflate = null) { flashTimer = flashDuration; + this.flashRectInflate = flashRectInflate ?? Vector2.Zero; + this.useRectangleFlash = useRectangleFlash; this.flashDuration = flashDuration; flashColor = (color == null) ? Color.Red : (Color)color; } @@ -507,9 +529,7 @@ namespace Barotrauma while (t < duration) { t += CoroutineManager.DeltaTime; - SetAlpha(MathHelper.Lerp(startA, to, t / duration)); - yield return CoroutineStatus.Running; } @@ -523,6 +543,28 @@ namespace Barotrauma yield return CoroutineStatus.Success; } + public void Pulsate(Vector2 startScale, Vector2 endScale, float duration) + { + if (CoroutineManager.IsCoroutineRunning(pulsateCoroutine)) + { + return; + } + pulsateCoroutine = CoroutineManager.StartCoroutine(DoPulsate(startScale, endScale, duration), "Pulsate" + ToString()); + } + + private IEnumerable DoPulsate(Vector2 startScale, Vector2 endScale, float duration) + { + float t = 0.0f; + while (t < duration) + { + t += CoroutineManager.DeltaTime; + RectTransform.LocalScale = Vector2.Lerp(startScale, endScale, (float)Math.Sin(t / duration * MathHelper.Pi)); + yield return CoroutineStatus.Running; + } + RectTransform.LocalScale = startScale; + yield return CoroutineStatus.Success; + } + public virtual void ApplyStyle(GUIComponentStyle style) { if (style == null) return; diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs index 049ea780e..528ce8e2b 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIListBox.cs @@ -511,10 +511,8 @@ namespace Barotrauma pos = 0; totalSize += child.Rect.Width + spacing; } - else - { - pos += child.Rect.Height + spacing; - } + pos += child.Rect.Height + spacing; + if (child == children.Last()) { totalSize += child.Rect.Width + spacing; @@ -527,10 +525,7 @@ namespace Barotrauma pos = 0; totalSize += child.Rect.Height + spacing; } - else - { - pos += child.Rect.Width + spacing; - } + pos += child.Rect.Width + spacing; if (child == children.Last()) { diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs index 5d1c333c4..0c091c992 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs @@ -38,35 +38,25 @@ namespace Barotrauma public GUIMessageBox(string headerText, string text, string[] buttons, int width = DefaultWidth, int height = 0, Alignment textAlignment = Alignment.TopLeft, string tag = "") : base(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "") { - int headerHeight = 30; - InnerFrame = new GUIFrame(new RectTransform(new Point(width, height), RectTransform, Anchor.Center) { IsFixedSize = false }, style: null); GUI.Style.Apply(InnerFrame, "", this); Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 }; Tag = tag; - - if (height == 0) - { - string wrappedText = ToolBox.WrapText(text, Content.Rect.Width, GUI.Font); - string[] lines = wrappedText.Split('\n'); - foreach (string line in lines) - { - height += (int)GUI.Font.MeasureString(line).Y; - } - height += string.IsNullOrWhiteSpace(headerText) ? 220 : 220 - headerHeight; - } - InnerFrame.RectTransform.NonScaledSize = new Point(InnerFrame.Rect.Width, height); - + Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, textAlignment: Alignment.Center, wrap: true); - GUI.Style.Apply(Header, "", this); + GUI.Style.Apply(Header, "", this); + Header.RectTransform.MinSize = new Point(0, Header.Rect.Height); if (!string.IsNullOrWhiteSpace(text)) { Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true); GUI.Style.Apply(Text, "", this); + Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize = + new Point(Text.Rect.Width, Text.Rect.Height); + Text.RectTransform.IsFixedSize = true; } var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), Content.RectTransform, Anchor.BottomCenter, maxSize: new Point(1000, 50)), @@ -75,7 +65,22 @@ namespace Barotrauma AbsoluteSpacing = 5, IgnoreLayoutGroups = true }; - + buttonContainer.RectTransform.NonScaledSize = buttonContainer.RectTransform.MinSize = buttonContainer.RectTransform.MaxSize = + new Point(buttonContainer.Rect.Width, (int)(30 * GUI.Scale)); + buttonContainer.RectTransform.IsFixedSize = true; + + if (height == 0) + { + height += Header.Rect.Height + Content.AbsoluteSpacing; + height += (Text == null ? 0 : Text.Rect.Height) + Content.AbsoluteSpacing; + height += buttonContainer.Rect.Height; + + InnerFrame.RectTransform.NonScaledSize = + new Point(InnerFrame.Rect.Width, (int)Math.Max(height / Content.RectTransform.RelativeSize.Y, height + 50)); + Content.RectTransform.NonScaledSize = + new Point(Content.Rect.Width, height); + } + Buttons = new List(buttons.Length); for (int i = 0; i < buttons.Length; i++) { diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs index 2b3317682..1227ee15e 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Xml.Linq; @@ -9,6 +10,8 @@ namespace Barotrauma { private Dictionary componentStyles; + private XElement configElement; + public ScalableFont Font { get; private set; } public ScalableFont SmallFont { get; private set; } public ScalableFont LargeFont { get; private set; } @@ -26,6 +29,8 @@ namespace Barotrauma { componentStyles = new Dictionary(); + GameMain.Instance.OnResolutionChanged += () => { RescaleFonts(); }; + XDocument doc; try { @@ -37,29 +42,11 @@ namespace Barotrauma DebugConsole.ThrowError("Loading style \"" + file + "\" failed", e); return; } - + configElement = doc.Root; foreach (XElement subElement in doc.Root.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { - case "font": - Font = new ScalableFont(subElement, graphicsDevice); - break; - case "smallfont": - SmallFont = new ScalableFont(subElement, graphicsDevice); - break; - case "largefont": - LargeFont = new ScalableFont(subElement, graphicsDevice); - break; - case "objectivetitle": - ObjectiveTitleFont = new ScalableFont(subElement, graphicsDevice); - break; - case "objectivename": - ObjectiveNameFont = new ScalableFont(subElement, graphicsDevice); - break; - case "videotitle": - VideoTitleFont = new ScalableFont(subElement, graphicsDevice); - break; case "cursor": CursorSprite = new Sprite(subElement); break; @@ -69,6 +56,24 @@ namespace Barotrauma case "focusindicator": FocusIndicator = new SpriteSheet(subElement); break; + case "font": + Font = LoadFont(subElement, graphicsDevice); + break; + case "smallfont": + SmallFont = LoadFont(subElement, graphicsDevice); + break; + case "largefont": + LargeFont = LoadFont(subElement, graphicsDevice); + break; + case "objectivetitle": + ObjectiveTitleFont = LoadFont(subElement, graphicsDevice); + break; + case "objectivename": + ObjectiveNameFont = LoadFont(subElement, graphicsDevice); + break; + case "videotitle": + VideoTitleFont = LoadFont(subElement, graphicsDevice); + break; default: GUIComponentStyle componentStyle = new GUIComponentStyle(subElement); componentStyles.Add(subElement.Name.ToString().ToLowerInvariant(), componentStyle); @@ -77,6 +82,54 @@ namespace Barotrauma } } + private void RescaleFonts() + { + foreach (XElement subElement in configElement.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "font": + Font.Size = GetFontSize(subElement); + break; + case "smallfont": + SmallFont.Size = GetFontSize(subElement); + break; + case "largefont": + LargeFont.Size = GetFontSize(subElement); + break; + case "objectivetitle": + ObjectiveTitleFont.Size = GetFontSize(subElement); + break; + case "objectivename": + ObjectiveNameFont.Size = GetFontSize(subElement); + break; + case "videotitle": + VideoTitleFont.Size = GetFontSize(subElement); + break; + } + } + } + + private ScalableFont LoadFont(XElement element, GraphicsDevice graphicsDevice) + { + string file = element.GetAttributeString("file", ""); + uint size = GetFontSize(element); + return new ScalableFont(file, size, graphicsDevice); + } + + private uint GetFontSize(XElement element) + { + foreach (XElement subElement in element.Elements()) + { + Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue)); + if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y) + { + return (uint)subElement.GetAttributeInt("size", 14); + } + } + return 14; + } + public GUIComponentStyle GetComponentStyle(string name) { componentStyles.TryGetValue(name.ToLowerInvariant(), out GUIComponentStyle style); diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUITextBlock.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUITextBlock.cs index e90fed400..07d09a5dd 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUITextBlock.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUITextBlock.cs @@ -27,6 +27,11 @@ namespace Barotrauma private bool overflowClipActive; public bool OverflowClip; + public bool OverflowClipActive + { + get { return overflowClipActive; } + } + private float textDepth; public Vector2 TextOffset { get; set; } @@ -88,6 +93,7 @@ namespace Barotrauma public Vector2 TextPos { get { return textPos; } + set { textPos = value; } } public float TextScale @@ -327,7 +333,7 @@ namespace Barotrauma { spriteBatch.End(); spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; - spriteBatch.Begin(SpriteSortMode.Deferred); + spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable); } if (OutlineColor.A * currColor.A > 0.0f) GUI.DrawRectangle(spriteBatch, rect, OutlineColor * (currColor.A / 255.0f), false); diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs index 5e24b6e9e..886e5aa2c 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUITextBox.cs @@ -118,11 +118,17 @@ namespace Barotrauma get { return maxTextLength; } set { - textBlock.OverflowClip = true; + textBlock.OverflowClip = value != null; maxTextLength = value; } } + public bool OverflowClip + { + get { return textBlock.OverflowClip; } + set { textBlock.OverflowClip = value; } + } + public override bool Enabled { get { return enabled; } @@ -318,7 +324,7 @@ namespace Barotrauma for (int i = 0; i <= textBlock.Text.Length; i++) { Vector2 textSize = Font.MeasureString(textBlock.Text.Substring(0, i)); - Vector2 indexPos = new Vector2(textSize.X + textBlock.Padding.X, textSize.Y + textBlock.Padding.Y); + Vector2 indexPos = new Vector2(textSize.X + textBlock.Padding.X, textSize.Y + textBlock.Padding.Y) + textBlock.TextPos - textBlock.Origin; //DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke); positions.Add(new Tuple(textBlock.Rect.Location.ToVector2() + indexPos, i)); } @@ -359,9 +365,9 @@ namespace Barotrauma OnDeselected?.Invoke(this, Keys.None); } - public override void Flash(Color? color = null, float flashDuration = 1.5f) + public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, Vector2? flashRectOffset = null) { - textBlock.Flash(color, flashDuration); + textBlock.Flash(color, flashDuration, useRectangleFlash, flashRectOffset); } protected override void Update(float deltaTime) @@ -405,9 +411,22 @@ namespace Barotrauma { isSelecting = PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift); } - + if (CaretEnabled) { + if (textBlock.OverflowClipActive) + { + if (CaretScreenPos.X < Rect.X + textBlock.Padding.X) + { + textBlock.TextPos = new Vector2(textBlock.TextPos.X + ((Rect.X + textBlock.Padding.X) - CaretScreenPos.X), textBlock.TextPos.Y); + CalculateCaretPos(); + } + else if (CaretScreenPos.X > Rect.Right - textBlock.Padding.Z) + { + textBlock.TextPos = new Vector2(textBlock.TextPos.X - (CaretScreenPos.X - (Rect.Right - textBlock.Padding.Z)), textBlock.TextPos.Y); + CalculateCaretPos(); + } + } caretTimer += deltaTime; caretVisible = ((caretTimer * 1000.0f) % 1000) < 500; if (caretVisible && caretPosDirty) @@ -415,7 +434,7 @@ namespace Barotrauma CalculateCaretPos(); } } - + if (GUI.KeyboardDispatcher.Subscriber == this) { state = ComponentState.Selected; @@ -534,15 +553,7 @@ namespace Barotrauma public void ReceiveTextInput(char inputChar) { - if (selectedCharacters > 0) - { - RemoveSelectedText(); - } - if (SetText(Text.Insert(CaretIndex, inputChar.ToString()))) - { - CaretIndex = Math.Min(Text.Length, CaretIndex + 1); - OnTextChanged?.Invoke(this, Text); - } + ReceiveTextInput(inputChar.ToString()); } public void ReceiveTextInput(string input) @@ -551,10 +562,16 @@ namespace Barotrauma { RemoveSelectedText(); } + Vector2 textPos = textBlock.TextPos; + bool wasOverflowClipActive = textBlock.OverflowClipActive; if (SetText(Text.Insert(CaretIndex, input))) { CaretIndex = Math.Min(Text.Length, CaretIndex + input.Length); OnTextChanged?.Invoke(this, Text); + if (textBlock.OverflowClipActive && wasOverflowClipActive && !MathUtils.NearlyEqual(textBlock.TextPos, textPos)) + { + textBlock.TextPos = textPos + Vector2.UnitX * Font.MeasureString(input).X; + } } } diff --git a/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs b/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs index bb327ac69..ea9adc6f3 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs @@ -10,9 +10,10 @@ namespace Barotrauma { class VideoPlayer { + public bool IsPlaying; + private Video currentVideo; private string filePath; - private bool isPlaying; private GUIFrame background, videoFrame, textFrame; private GUITextBlock title, textContent, objectiveTitle, objectiveText; @@ -24,12 +25,14 @@ namespace Barotrauma private Point scaledVideoResolution; private readonly int borderSize = 20; - private readonly Point buttonSize = new Point(160, 50); + private readonly Point buttonSize = new Point(120, 30); private readonly int titleHeight = 30; private readonly int objectiveFrameHeight = 60; private readonly int textHeight = 25; - public struct TextSettings + private bool useTextOnRightSide = false; + + public class TextSettings { public string Text; public int Width; @@ -41,7 +44,7 @@ namespace Barotrauma } } - public struct VideoSettings + public class VideoSettings { public string File; @@ -62,7 +65,14 @@ namespace Barotrauma background = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas, Anchor.Center), "InnerFrame", backgroundColor); videoFrame = new GUIFrame(new RectTransform(Point.Zero, background.RectTransform, Anchor.Center, Pivot.Center), "SonarFrame"); - textFrame = new GUIFrame(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.CenterLeft, Pivot.CenterLeft), "TextFrame"); + if (useTextOnRightSide) + { + textFrame = new GUIFrame(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.CenterLeft, Pivot.CenterLeft), "TextFrame"); + } + else + { + textFrame = new GUIFrame(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), "TextFrame"); + } videoView = new GUICustomComponent(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.Center), (spriteBatch, guiCustomComponent) => { DrawVideo(spriteBatch, guiCustomComponent.Rect); }); title = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft), string.Empty, font: GUI.VideoTitleFont, textColor: new Color(253, 174, 0), textAlignment: Alignment.Left); @@ -70,7 +80,7 @@ namespace Barotrauma textContent = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft), string.Empty, font: GUI.Font, textAlignment: Alignment.TopLeft); objectiveTitle = new GUITextBlock(new RectTransform(new Vector2(1f, 0f), textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.ObjectiveTitleFont, textAlignment: Alignment.CenterRight, textColor: Color.White); - objectiveTitle.Text = TextManager.Get("NewObjective"); + objectiveTitle.Text = TextManager.Get("Tutorial.NewObjective"); objectiveText = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.ObjectiveNameFont, textColor: new Color(4, 180, 108), textAlignment: Alignment.CenterRight); objectiveTitle.Visible = objectiveText.Visible = false; @@ -78,12 +88,12 @@ namespace Barotrauma public void Play() { - isPlaying = true; + IsPlaying = true; } public void Stop() { - isPlaying = false; + IsPlaying = false; if (currentVideo == null) return; currentVideo.Dispose(); currentVideo = null; @@ -99,13 +109,6 @@ namespace Barotrauma public void Update() { if (currentVideo == null) return; - - if (PlayerInput.KeyHit(Keys.Enter) || PlayerInput.KeyHit(Keys.Escape)) - { - DisposeVideo(null, null); - return; - } - if (currentVideo.IsPlaying) return; currentVideo.Dispose(); @@ -115,7 +118,7 @@ namespace Barotrauma public void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0) { - if (!isPlaying) return; + if (!IsPlaying) return; background.AddToGUIUpdateList(ignoreChildren, order); } @@ -139,7 +142,7 @@ namespace Barotrauma currentVideo = CreateVideo(scaledVideoResolution); title.Text = TextManager.Get(contentId); - textContent.Text = textSettings.Text; + textContent.Text = textSettings != null ? textSettings.Text : string.Empty; objectiveText.Text = objective; AdjustFrames(videoSettings, textSettings); @@ -165,7 +168,8 @@ namespace Barotrauma title.TextScale = textContent.TextScale = objectiveText.TextScale = objectiveTitle.TextScale = GUI.Scale; int scaledBorderSize = (int)(borderSize * GUI.Scale); - int scaledTextWidth = (int)(textSettings.Width * GUI.Scale); + int scaledTextWidth = 0; + if (textSettings != null) scaledTextWidth = useTextOnRightSide ? (int)(textSettings.Width * GUI.Scale) : scaledVideoResolution.X / 2; int scaledTitleHeight = (int)(titleHeight * GUI.Scale); int scaledTextHeight = (int)(textHeight * GUI.Scale); int scaledObjectiveFrameHeight = (int)(objectiveFrameHeight * GUI.Scale); @@ -180,13 +184,21 @@ namespace Barotrauma title.RectTransform.NonScaledSize += new Point(scaledTextWidth, scaledTitleHeight); title.RectTransform.AbsoluteOffset = new Point((int)(5 * GUI.Scale), (int)(10 * GUI.Scale)); - if (!string.IsNullOrEmpty(textSettings.Text)) + if (textSettings != null && !string.IsNullOrEmpty(textSettings.Text)) { textSettings.Text = ToolBox.WrapText(textSettings.Text, scaledTextWidth, GUI.Font); int wrappedHeight = textSettings.Text.Split('\n').Length * scaledTextHeight; textFrame.RectTransform.NonScaledSize += new Point(scaledTextWidth + scaledBorderSize, wrappedHeight + scaledBorderSize + scaledButtonSize.Y + scaledTitleHeight); - textFrame.RectTransform.AbsoluteOffset = new Point(scaledVideoResolution.X + scaledBorderSize * 2, 0); + + if (useTextOnRightSide) + { + textFrame.RectTransform.AbsoluteOffset = new Point(scaledVideoResolution.X + scaledBorderSize * 2, 0); + } + else + { + textFrame.RectTransform.AbsoluteOffset = new Point(0, scaledVideoResolution.Y + scaledBorderSize * 2); + } textContent.RectTransform.NonScaledSize += new Point(scaledTextWidth, wrappedHeight); textContent.RectTransform.AbsoluteOffset = new Point(0, scaledBorderSize + scaledTitleHeight); @@ -209,22 +221,41 @@ namespace Barotrauma objectiveTitle.Visible = objectiveText.Visible = false; } - int totalFrameWidth = videoFrame.Rect.Width + textFrame.Rect.Width + scaledBorderSize * 2; - int xOffset = videoFrame.Rect.Width / 2 + scaledBorderSize - (videoFrame.Rect.Width / 2 - textFrame.Rect.Width / 2); - - - videoFrame.RectTransform.AbsoluteOffset = new Point(-xOffset, (int)(50 * GUI.Scale)); - if (okButton != null) { textFrame.RemoveChild(okButton); okButton = null; } - okButton = new GUIButton(new RectTransform(scaledButtonSize, textFrame.RectTransform, Anchor.BottomRight, Pivot.BottomRight) { AbsoluteOffset = new Point(scaledBorderSize, scaledBorderSize) }, TextManager.Get("OK")) + if (textSettings != null) { - OnClicked = DisposeVideo - }; + if (useTextOnRightSide) + { + int totalFrameWidth = videoFrame.Rect.Width + textFrame.Rect.Width + scaledBorderSize * 2; + int xOffset = videoFrame.Rect.Width / 2 + scaledBorderSize - (videoFrame.Rect.Width / 2 - textFrame.Rect.Width / 2); + videoFrame.RectTransform.AbsoluteOffset = new Point(-xOffset, (int)(50 * GUI.Scale)); + } + else + { + int totalFrameHeight = videoFrame.Rect.Height + textFrame.Rect.Height + scaledBorderSize * 2; + int yOffset = videoFrame.Rect.Height / 2 + scaledBorderSize - (videoFrame.Rect.Height / 2 - textFrame.Rect.Height / 2); + videoFrame.RectTransform.AbsoluteOffset = new Point(0, -yOffset); + } + + okButton = new GUIButton(new RectTransform(scaledButtonSize, textFrame.RectTransform, Anchor.BottomRight, Pivot.BottomRight) { AbsoluteOffset = new Point(scaledBorderSize, scaledBorderSize) }, TextManager.Get("OK")) + { + OnClicked = DisposeVideo + }; + } + else + { + videoFrame.RectTransform.AbsoluteOffset = new Point(0, (int)(100 * GUI.Scale)); + + okButton = new GUIButton(new RectTransform(scaledButtonSize, videoFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft) { AbsoluteOffset = new Point(scaledBorderSize, scaledBorderSize) }, TextManager.Get("Back")) + { + OnClicked = DisposeVideo + }; + } } private Video CreateVideo(Point resolution) @@ -245,7 +276,7 @@ namespace Barotrauma private void DrawVideo(SpriteBatch spriteBatch, Rectangle rect) { - if (!isPlaying) return; + if (!IsPlaying) return; spriteBatch.Draw(currentVideo.GetTexture(), rect, Color.White); } diff --git a/Barotrauma/BarotraumaClient/Source/GameMain.cs b/Barotrauma/BarotraumaClient/Source/GameMain.cs index 10fa40667..b2e3fa53a 100644 --- a/Barotrauma/BarotraumaClient/Source/GameMain.cs +++ b/Barotrauma/BarotraumaClient/Source/GameMain.cs @@ -159,6 +159,15 @@ namespace Barotrauma public GameMain() { +#if !DEBUG && OSX + // Use a separate path for content that's editable due to macOS's .app bundles crashing when edited during runtime + string macPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library/Barotrauma"); + Directory.SetCurrentDirectory(macPath); + Content.RootDirectory = macPath + "/Content"; +#else + Content.RootDirectory = "Content"; +#endif + GraphicsDeviceManager = new GraphicsDeviceManager(this); Window.Title = "Barotrauma"; @@ -169,7 +178,6 @@ namespace Barotrauma GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window); - Content.RootDirectory = "Content"; PerformanceCounter = new PerformanceCounter(); @@ -318,8 +326,16 @@ namespace Barotrauma SoundManager.SetCategoryGainMultiplier("voip", Config.VoiceChatVolume); if (Config.EnableSplashScreen) { - (TitleScreen as LoadingScreen).SplashScreen = new Video(base.GraphicsDevice, SoundManager, "Content/splashscreen.mp4", 1280, 720); - } + try + { + (TitleScreen as LoadingScreen).SplashScreen = new Video(base.GraphicsDevice, SoundManager, "Content/splashscreen.mp4", 1280, 720); + } + catch (Exception e) + { + Config.EnableSplashScreen = false; + DebugConsole.ThrowError("Playing the splash screen failed.", e); + } + } GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice); DebugConsole.Init(); @@ -602,8 +618,12 @@ namespace Barotrauma { ((GUIMessageBox)GUIMessageBox.VisibleBox).Close(); } + else if (Tutorial.Initialized && Tutorial.ContentRunning) + { + (GameMain.GameSession.GameMode as TutorialMode).Tutorial.CloseActiveContentGUI(); + } else if ((Character.Controlled?.SelectedConstruction == null || !Character.Controlled.SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null)) - && Inventory.SelectedSlot == null && CharacterHealth.OpenHealthWindow == null) + && Inventory.SelectedSlot == null && CharacterHealth.OpenHealthWindow == null) { // Otherwise toggle pausing, unless another window/interface is open. GUI.TogglePauseMenu(); @@ -611,7 +631,7 @@ namespace Barotrauma } GUI.ClearUpdateList(); - paused = (DebugConsole.IsOpen || GUI.PauseMenuOpen || GUI.SettingsMenuOpen || ContextualTutorial.ContentRunning) && + paused = (DebugConsole.IsOpen || GUI.PauseMenuOpen || GUI.SettingsMenuOpen || Tutorial.ContentRunning) && (NetworkMember == null || !NetworkMember.GameStarted); Screen.Selected.AddToGUIUpdateList(); @@ -630,9 +650,9 @@ namespace Barotrauma { Screen.Selected.Update(Timing.Step); } - else if (ContextualTutorial.Initialized && ContextualTutorial.ContentRunning && GameSession.GameMode is SinglePlayerCampaign) + else if (Tutorial.Initialized && Tutorial.ContentRunning) { - (GameSession.GameMode as SinglePlayerCampaign).ContextualTutorial.Update((float)Timing.Step); + (GameSession.GameMode as TutorialMode).Update((float)Timing.Step); } if (NetworkMember != null) @@ -697,6 +717,85 @@ namespace Barotrauma PerformanceCounter.DrawTimeGraph.Update(sw.ElapsedTicks / (float)TimeSpan.TicksPerMillisecond); } + public void ShowCampaignDisclaimer(Action onContinue) + { + var msgBox = new GUIMessageBox(TextManager.Get("CampaignDisclaimerTitle"), TextManager.Get("CampaignDisclaimerText"), + new string[] { TextManager.Get("CampaignRoadMapTitle"), TextManager.Get("OK") }); + + msgBox.Buttons[0].OnClicked = (btn, userdata) => + { + var roadMap = new GUIMessageBox(TextManager.Get("CampaignRoadMapTitle"), TextManager.Get("CampaignRoadMapText"), + new string[] { TextManager.Get("Back"), TextManager.Get("OK") }); + roadMap.Buttons[0].OnClicked += roadMap.Close; + roadMap.Buttons[0].OnClicked += (_, __) => { ShowCampaignDisclaimer(onContinue); return true; }; + roadMap.Buttons[1].OnClicked += roadMap.Close; + roadMap.Buttons[1].OnClicked += (_, __) => { onContinue?.Invoke(); return true; }; + return true; + }; + msgBox.Buttons[0].OnClicked += msgBox.Close; + msgBox.Buttons[1].OnClicked += msgBox.Close; + msgBox.Buttons[1].OnClicked += (_, __) => { onContinue?.Invoke(); return true; }; + + Config.CampaignDisclaimerShown = true; + Config.SaveNewPlayerConfig(); + } + + public void ShowEditorDisclaimer() + { + var msgBox = new GUIMessageBox(TextManager.Get("EditorDisclaimerTitle"), TextManager.Get("EditorDisclaimerText")); + var linkHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), msgBox.Content.RectTransform)) { Stretch = true, RelativeSpacing = 0.025f }; + linkHolder.RectTransform.MaxSize = new Point(int.MaxValue, linkHolder.Rect.Height); + List> links = new List>() + { + new Pair(TextManager.Get("EditorDisclaimerWikiLink"),TextManager.Get("EditorDisclaimerWikiUrl")), + new Pair(TextManager.Get("EditorDisclaimerDiscordLink"),TextManager.Get("EditorDisclaimerDiscordUrl")), + new Pair(TextManager.Get("EditorDisclaimerForumLink"),TextManager.Get("EditorDisclaimerForumUrl")), + }; + foreach (var link in links) + { + new GUIButton(new RectTransform(new Vector2(1.0f, 0.2f), linkHolder.RectTransform), link.First, style: "MainMenuGUIButton", textAlignment: Alignment.Left) + { + UserData = link.Second, + OnClicked = (btn, userdata) => + { + Process.Start(userdata as string); + return true; + } + }; + } + + msgBox.InnerFrame.RectTransform.MinSize = new Point(0, + msgBox.InnerFrame.Rect.Height + linkHolder.Rect.Height + msgBox.Content.AbsoluteSpacing * 2 + 10); + Config.EditorDisclaimerShown = true; + Config.SaveNewPlayerConfig(); + } + + // ToDo: Move texts/links to localization, when possible. + public void ShowBugReporter() + { + var msgBox = new GUIMessageBox("", ""); + var linkHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), msgBox.Content.RectTransform)) { Stretch = true, RelativeSpacing = 0.05f }; + + List> links = new List>() + { + new Pair("Barotrauma Feedback Form","https://barotraumagame.com/feedback"), + new Pair("Github Issue Form (Needs account)","https://github.com/Regalis11/Barotrauma/issues/new?template=bug_report.md") + }; + foreach (var link in links) + { + new GUIButton(new RectTransform(new Vector2(1.0f, 0.2f), linkHolder.RectTransform), link.First, style: "MainMenuGUIButton", textAlignment: Alignment.Left) + { + UserData = link.Second, + OnClicked = (btn, userdata) => + { + Process.Start(userdata as string); + msgBox.Close(); + return true; + } + }; + } + } + static bool waitForKeyHit = true; public CoroutineHandle ShowLoading(IEnumerable loader, bool waitKeyHit = true) { diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index 32af88417..8d1b7f83a 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -4,7 +4,6 @@ using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; using System.Linq; @@ -41,18 +40,33 @@ namespace Barotrauma private bool toggleCrewAreaOpen = true; private int characterInfoWidth; - private ChatBox chatBox; + /// + /// Present only in single player games. In multiplayer. The chatbox is found from GameSession.Client. + /// + public ChatBox ChatBox { get; private set; } private float prevUIScale; private GUIComponent orderTargetFrame, orderTargetFrameShadow; + public bool AllowCharacterSwitch = true; + public bool ToggleCrewAreaOpen { get { return toggleCrewAreaOpen; } - set { toggleCrewAreaOpen = value; } + set + { + if (toggleCrewAreaOpen == value) { return; } + toggleCrewAreaOpen = GameMain.Config.CrewMenuOpen = value; + foreach (GUIComponent child in toggleCrewButton.Children) + { + child.SpriteEffects = toggleCrewAreaOpen ? SpriteEffects.None : SpriteEffects.FlipHorizontally; + } + } } + public List OrderOptionButtons = new List(); + #endregion #region Constructors @@ -93,11 +107,7 @@ namespace Barotrauma "", style: "UIToggleButton"); toggleCrewButton.OnClicked += (GUIButton btn, object userdata) => { - toggleCrewAreaOpen = !toggleCrewAreaOpen; - foreach (GUIComponent child in btn.Children) - { - child.SpriteEffects = toggleCrewAreaOpen ? SpriteEffects.None : SpriteEffects.FlipHorizontally; - } + ToggleCrewAreaOpen = !ToggleCrewAreaOpen; return true; }; @@ -125,7 +135,7 @@ namespace Barotrauma if (isSinglePlayer) { - chatBox = new ChatBox(guiFrame, isSinglePlayer: true) + ChatBox = new ChatBox(guiFrame, isSinglePlayer: true) { OnEnterMessage = (textbox, text) => { @@ -158,7 +168,7 @@ namespace Barotrauma } }; - chatBox.InputBox.OnTextChanged += chatBox.TypingChatMessage; + ChatBox.InputBox.OnTextChanged += ChatBox.TypingChatMessage; } var reports = Order.PrefabList.FindAll(o => o.TargetAllCharacters && o.SymbolSprite != null); @@ -208,6 +218,8 @@ namespace Barotrauma screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); prevUIScale = GUI.Scale; + + ToggleCrewAreaOpen = GameMain.Config.CrewMenuOpen; } @@ -465,7 +477,10 @@ namespace Barotrauma orderButtonFrame.RectTransform; var btn = new GUIButton(new RectTransform(new Point(iconSize, iconSize), btnParent, Anchor.CenterLeft), - style: null); + style: null) + { + UserData = order + }; new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow") { @@ -557,6 +572,7 @@ namespace Barotrauma /// public bool CharacterClicked(GUIComponent component, object selection) { + if (!AllowCharacterSwitch) { return false; } Character character = selection as Character; if (character == null || character.IsDead || character.IsUnconscious) return false; SelectCharacter(character); @@ -646,7 +662,11 @@ namespace Barotrauma } if (string.IsNullOrEmpty(text)) { return; } - chatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender)); + if (sender != null) + { + GameMain.GameSession.CrewManager.SetCharacterSpeaking(sender); + } + ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender)); } private WifiComponent GetHeadset(Character character, bool requireEquipped) @@ -697,15 +717,19 @@ namespace Barotrauma soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally"); } - public void SetPlayerSpeaking(Client client) + public void SetClientSpeaking(Client client) { - if (client?.Character == null) { return; } + if (client?.Character != null) { SetCharacterSpeaking(client.Character); } + } - var playerFrame = characterListBox.Content.FindChild(client.Character)?.FindChild(client.Character); + public void SetCharacterSpeaking(Character character) + { + var playerFrame = characterListBox.Content.FindChild(character)?.FindChild(character); if (playerFrame == null) { return; } var soundIcon = playerFrame.FindChild("soundicon"); soundIcon.Color = new Color(soundIcon.Color, 1.0f); } + #endregion /// @@ -722,7 +746,7 @@ namespace Barotrauma if (IsSinglePlayer) { orderGiver.Speak( - order.GetChatMessage("", orderGiver.CurrentHull?.RoomName, givingOrderToSelf: character == orderGiver), ChatMessageType.Order); + order.GetChatMessage("", orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver), ChatMessageType.Order); } else { @@ -739,7 +763,7 @@ namespace Barotrauma if (IsSinglePlayer) { orderGiver?.Speak( - order.GetChatMessage(character.Name, orderGiver.CurrentHull?.RoomName, givingOrderToSelf: character == orderGiver, orderOption: option), null); + order.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option), null); } else if (orderGiver != null) { @@ -892,9 +916,12 @@ namespace Barotrauma if (Character.Controlled == null) return false; SetCharacterOrder(character, userData as Order, option, Character.Controlled); orderTargetFrame = null; + OrderOptionButtons.Clear(); return true; } }; + + OrderOptionButtons.Add(optionButton); } } @@ -927,9 +954,13 @@ namespace Barotrauma if (Character.Controlled == null) return false; SetCharacterOrder(character, userData as Order, option, Character.Controlled); orderTargetFrame = null; + OrderOptionButtons.Clear(); return true; } }; + + OrderOptionButtons.Add(optionButton); + //lines between the order buttons if (i < order.Options.Length - 1) { @@ -945,6 +976,24 @@ namespace Barotrauma color: matchingItems.Count > 1 ? Color.Black * 0.9f : Color.Black * 0.7f); } + public void HighlightOrderButton(Character character, string orderAiTag, Color color, Vector2? flashRectInflate = null) + { + var order = Order.PrefabList.Find(o => o.AITag == orderAiTag); + if (order == null) + { + DebugConsole.ThrowError("Could not find an order with the AI tag \"" + orderAiTag + "\".\n" + Environment.StackTrace); + return; + } + var characterElement = characterListBox.Content.FindChild(character); + GUIButton orderBtn = characterElement.FindChild(order, recursive: true) as GUIButton; + if (orderBtn.Frame.FlashTimer <= 0) + { + orderBtn.Flash(color, 1.5f, false, flashRectInflate); + } + + //orderBtn.Pulsate(Vector2.One, Vector2.One * 2.0f, 1.5f); + } + #region Updating and drawing the UI private void DrawMiniMapOverlay(SpriteBatch spriteBatch, GUICustomComponent container) @@ -1002,6 +1051,7 @@ namespace Barotrauma public void SelectNextCharacter() { + if (!AllowCharacterSwitch) { return; } if (GameMain.IsMultiplayer) { return; } if (characters.None()) { return; } SelectCharacter(characters[TryAdjustIndex(1)]); @@ -1009,6 +1059,7 @@ namespace Barotrauma public void SelectPreviousCharacter() { + if (!AllowCharacterSwitch) { return; } if (GameMain.IsMultiplayer) { return; } if (characters.None()) { return; } SelectCharacter(characters[TryAdjustIndex(-1)]); @@ -1016,6 +1067,7 @@ namespace Barotrauma private void SelectCharacter(Character character) { + if (!AllowCharacterSwitch) { return; } //make the previously selected character wait in place for some time //(so they don't immediately start idling and walking away from their station) if (Character.Controlled?.AIController?.ObjectiveManager != null) @@ -1056,24 +1108,24 @@ namespace Barotrauma } if (GUI.DisableHUD || GUI.DisableUpperHUD) return; - if (chatBox != null) + if (ChatBox != null) { - chatBox.Update(deltaTime); - chatBox.InputBox.Visible = Character.Controlled != null; + ChatBox.Update(deltaTime); + ChatBox.InputBox.Visible = Character.Controlled != null; - if (!DebugConsole.IsOpen && chatBox.InputBox.Visible) + if (!DebugConsole.IsOpen && ChatBox.InputBox.Visible) { - if (PlayerInput.KeyHit(InputType.Chat) && !chatBox.InputBox.Selected) + if (PlayerInput.KeyHit(InputType.Chat) && !ChatBox.InputBox.Selected) { - chatBox.GUIFrame.Flash(Color.DarkGreen, 0.5f); - chatBox.InputBox.Select(); + ChatBox.GUIFrame.Flash(Color.DarkGreen, 0.5f); + ChatBox.InputBox.Select(); } - if (PlayerInput.KeyHit(InputType.RadioChat) && !chatBox.InputBox.Selected) + if (PlayerInput.KeyHit(InputType.RadioChat) && !ChatBox.InputBox.Selected) { - chatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f); - chatBox.InputBox.Select(); - chatBox.InputBox.Text = "r; "; + ChatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f); + ChatBox.InputBox.Select(); + ChatBox.InputBox.Text = "r; "; } } } @@ -1143,7 +1195,7 @@ namespace Barotrauma crewArea.RectTransform.AbsoluteOffset = Vector2.SmoothStep(new Vector2(-crewArea.Rect.Width, 0), new Vector2(toggleCrewButton.Rect.Width, 0), crewAreaOpenState).ToPoint(); - crewAreaOpenState = toggleCrewAreaOpen ? + crewAreaOpenState = ToggleCrewAreaOpen ? Math.Min(crewAreaOpenState + deltaTime * 2.0f, 1.0f) : Math.Max(crewAreaOpenState - deltaTime * 2.0f, 0.0f); @@ -1156,7 +1208,7 @@ namespace Barotrauma { Character.Controlled.SelectedConstruction = null; } - toggleCrewAreaOpen = !toggleCrewAreaOpen; + ToggleCrewAreaOpen = !ToggleCrewAreaOpen; } UpdateReports(deltaTime); @@ -1284,7 +1336,7 @@ namespace Barotrauma { reportButtonFrame.Visible = true; - var reportButtonParent = chatBox ?? GameMain.Client.ChatBox; + var reportButtonParent = ChatBox ?? GameMain.Client.ChatBox; reportButtonFrame.RectTransform.AbsoluteOffset = new Point( Math.Min(reportButtonParent.GUIFrame.Rect.X, reportButtonParent.ToggleButton.Rect.X) - reportButtonFrame.Rect.Width - (int)(10 * GUI.Scale), reportButtonParent.GUIFrame.Rect.Y); diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs index aab0cdf65..bb18197ea 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/MultiPlayerCampaign.cs @@ -135,6 +135,8 @@ namespace Barotrauma msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex); + msg.Write(PurchasedHullRepairs); + msg.Write(PurchasedItemRepairs); msg.Write((UInt16)CargoManager.PurchasedItems.Count); foreach (PurchasedItem pi in CargoManager.PurchasedItems) @@ -159,6 +161,8 @@ namespace Barotrauma UInt16 endWatchmanID = msg.ReadUInt16(); int money = msg.ReadInt32(); + bool purchasedHullRepairs = msg.ReadBoolean(); + bool purchasedItemRepairs = msg.ReadBoolean(); UInt16 purchasedItemCount = msg.ReadUInt16(); List purchasedItems = new List(); @@ -217,6 +221,8 @@ namespace Barotrauma campaign.endWatchmanID = endWatchmanID; campaign.Money = money; + campaign.PurchasedHullRepairs = purchasedHullRepairs; + campaign.PurchasedItemRepairs = purchasedItemRepairs; campaign.CargoManager.SetPurchasedItems(purchasedItems); if (myCharacterInfo != null) diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/SinglePlayerCampaign.cs index b4a7414e3..ed7b01f1b 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/SinglePlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/SinglePlayerCampaign.cs @@ -65,11 +65,6 @@ namespace Barotrauma endTimer = 5.0f; isRunning = true; CrewManager.InitSinglePlayerRound(); - - if (ContextualTutorial.Initialized) - { - ContextualTutorial.Start(); - } } public bool TryHireCharacter(Location location, CharacterInfo characterInfo) @@ -175,11 +170,6 @@ namespace Barotrauma base.Update(deltaTime); - if (ContextualTutorial.Initialized) - { - ContextualTutorial.Update(deltaTime); - } - if (!GUI.DisableHUD && !GUI.DisableUpperHUD) { endRoundButton.UpdateManually(deltaTime); @@ -440,12 +430,6 @@ namespace Barotrauma new XAttribute("cheatsenabled", CheatsEnabled)); CrewManager.Save(modeElement); Map.Save(modeElement); - - if (ContextualTutorial.Initialized) - { - ContextualTutorial.SavePartiallyComplete(modeElement); - } - element.Add(modeElement); } } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs index c06fc83f9..0a0677484 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs @@ -38,13 +38,13 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(4.0f); - infoBox = CreateInfoFrame("Use WASD to move and the mouse to look around"); + infoBox = CreateInfoFrame("", "Use WASD to move and the mouse to look around"); yield return new WaitForSeconds(5.0f); //----------------------------------- - infoBox = CreateInfoFrame("Open the door at your right side by highlighting the button next to it with your cursor and pressing E"); + infoBox = CreateInfoFrame("", "Open the door at your right side by highlighting the button next to it with your cursor and pressing E"); Door tutorialDoor = Item.ItemList.Find(i => i.HasTag("tutorialdoor")).GetComponent(); @@ -57,7 +57,7 @@ namespace Barotrauma.Tutorials //----------------------------------- - infoBox = CreateInfoFrame("Hold W or S to walk up or down stairs. Use shift to run.", true); + infoBox = CreateInfoFrame("", "Hold W or S to walk up or down stairs. Use shift to run.", hasButton: true); while (infoBox != null) { @@ -66,7 +66,7 @@ namespace Barotrauma.Tutorials //----------------------------------- - infoBox = CreateInfoFrame("At the moment the submarine has no power, which means that crucial systems such as the oxygen generator or the engine aren't running. Let's fix this: go to the upper left corner of the submarine, where you'll find a nuclear reactor."); + infoBox = CreateInfoFrame("", "At the moment the submarine has no power, which means that crucial systems such as the oxygen generator or the engine aren't running. Let's fix this: go to the upper left corner of the submarine, where you'll find a nuclear reactor."); Reactor reactor = Item.ItemList.Find(i => i.HasTag("tutorialreactor")).GetComponent(); //reactor.MeltDownTemp = 20000.0f; @@ -76,21 +76,21 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("The reactor requires fuel rods to generate power. You can grab one from the steel cabinet by walking next to it and pressing E."); + infoBox = CreateInfoFrame("", "The reactor requires fuel rods to generate power. You can grab one from the steel cabinet by walking next to it and pressing E."); while (Controlled.SelectedConstruction == null || Controlled.SelectedConstruction.Prefab.Identifier != "steelcabinet") { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Pick up one of the fuel rods either by double-clicking or dragging and dropping it into your inventory."); + infoBox = CreateInfoFrame("", "Pick up one of the fuel rods either by double-clicking or dragging and dropping it into your inventory."); while (!HasItem("fuelrod")) { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Select the reactor by walking next to it and pressing E."); + infoBox = CreateInfoFrame("", "Select the reactor by walking next to it and pressing E."); while (Controlled.SelectedConstruction != reactor.Item) { @@ -98,14 +98,14 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(0.5f); - infoBox = CreateInfoFrame("Load the fuel rod into the reactor by dropping it into any of the 5 slots."); + infoBox = CreateInfoFrame("", "Load the fuel rod into the reactor by dropping it into any of the 5 slots."); while (reactor.AvailableFuel <= 0.0f) { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("The reactor is now fueled up. Try turning it on by increasing the fission rate."); + infoBox = CreateInfoFrame("", "The reactor is now fueled up. Try turning it on by increasing the fission rate."); while (reactor.FissionRate <= 0.0f) { @@ -113,8 +113,8 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(0.5f); - infoBox = CreateInfoFrame("The reactor core has started generating heat, which in turn generates power for the submarine. The power generation is very low at the moment," - + " because the reactor is set to shut itself down when the temperature rises above 500 degrees Celsius. You can adjust the temperature limit by changing the \"Shutdown Temperature\" in the control panel.", true); + infoBox = CreateInfoFrame("", "The reactor core has started generating heat, which in turn generates power for the submarine. The power generation is very low at the moment," + + " because the reactor is set to shut itself down when the temperature rises above 500 degrees Celsius. You can adjust the temperature limit by changing the \"Shutdown Temperature\" in the control panel.", hasButton: true); //TODO: reimplement /*while (infoBox != null) @@ -146,7 +146,7 @@ namespace Barotrauma.Tutorials }*/ yield return new WaitForSeconds(0.5f); - infoBox = CreateInfoFrame("That's the basics of operating the reactor! Now that there's power available for the engines, it's time to get the submarine moving. " + infoBox = CreateInfoFrame("", "That's the basics of operating the reactor! Now that there's power available for the engines, it's time to get the submarine moving. " + "Deselect the reactor by pressing E and head to the command room at the right edge of the vessel."); Steering steering = Item.ItemList.Find(i => i.HasTag("tutorialsteering")).GetComponent(); @@ -159,7 +159,7 @@ namespace Barotrauma.Tutorials CoroutineManager.StartCoroutine(KeepReactorRunning(reactor)); - infoBox = CreateInfoFrame("Select the navigation terminal by walking next to it and pressing E."); + infoBox = CreateInfoFrame("", "Select the navigation terminal by walking next to it and pressing E."); while (Controlled.SelectedConstruction != steering.Item) { @@ -167,7 +167,7 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(0.5f); - infoBox = CreateInfoFrame("There seems to be something wrong with the navigation terminal." + + infoBox = CreateInfoFrame("", "There seems to be something wrong with the navigation terminal." + " There's nothing on the monitor, so it's probably out of power. The reactor must still be" + " running or the lights would've gone out, so it's most likely a problem with the wiring." + " Deselect the terminal by pressing E to start checking the wiring."); @@ -178,7 +178,7 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(1.0f); - infoBox = CreateInfoFrame("You need a screwdriver to check the wiring of the terminal." + infoBox = CreateInfoFrame("", "You need a screwdriver to check the wiring of the terminal." + " Equip a screwdriver by pulling it to either of the slots with a hand symbol, and then use it on the terminal by left clicking."); while (Controlled.SelectedConstruction != steering.Item || @@ -188,7 +188,7 @@ namespace Barotrauma.Tutorials } - infoBox = CreateInfoFrame("Here you can see all the wires connected to the terminal. Apparently there's no wire" + infoBox = CreateInfoFrame("", "Here you can see all the wires connected to the terminal. Apparently there's no wire" + " going into the to the power connection - that's why the monitor isn't working." + " You should find a piece of wire to connect it. Try searching some of the cabinets scattered around the sub."); @@ -197,7 +197,7 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Head back to the navigation terminal to fix the wiring."); + infoBox = CreateInfoFrame("", "Head back to the navigation terminal to fix the wiring."); PowerTransfer junctionBox = Item.ItemList.Find(i => i != null && i.HasTag("tutorialjunctionbox")).GetComponent(); @@ -210,7 +210,7 @@ namespace Barotrauma.Tutorials if (Controlled.SelectedItems.FirstOrDefault(i => i != null && i.GetComponent() != null) == null) { - infoBox = CreateInfoFrame("Equip the wire by dragging it to one of the slots with a hand symbol."); + infoBox = CreateInfoFrame("", "Equip the wire by dragging it to one of the slots with a hand symbol."); while (Controlled.SelectedItems.FirstOrDefault(i => i != null && i.GetComponent() != null) == null) { @@ -218,7 +218,7 @@ namespace Barotrauma.Tutorials } } - infoBox = CreateInfoFrame("You can see the equipped wire at the middle of the connection panel. Drag it to the power connector."); + infoBox = CreateInfoFrame("", "You can see the equipped wire at the middle of the connection panel. Drag it to the power connector."); var steeringConnection = steering.Item.Connections.Find(c => c.Name.Contains("power")); @@ -228,7 +228,7 @@ namespace Barotrauma.Tutorials } - infoBox = CreateInfoFrame("Now you have to connect the other end of the wire to a power source. " + infoBox = CreateInfoFrame("", "Now you have to connect the other end of the wire to a power source. " + "The junction box in the room just below the command room should do."); while (Controlled.SelectedConstruction != null) @@ -238,7 +238,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(2.0f); - infoBox = CreateInfoFrame("You can now move the other end of the wire around, and attach it on the wall by left clicking or " + infoBox = CreateInfoFrame("", "You can now move the other end of the wire around, and attach it on the wall by left clicking or " + "remove the previous attachment by right clicking. Or if you don't care for neatly laid out wiring, you can just " + "run it straight to the junction box."); @@ -247,14 +247,14 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Connect the wire to the junction box by pulling it to the power connection, the same way you did with the navigation terminal."); + infoBox = CreateInfoFrame("", "Connect the wire to the junction box by pulling it to the power connection, the same way you did with the navigation terminal."); while (sonar.Voltage < 0.1f) { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Great! Now we should be able to get moving."); + infoBox = CreateInfoFrame("", "Great! Now we should be able to get moving."); while (Controlled.SelectedConstruction != steering.Item) @@ -262,7 +262,7 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("You can take a look at the area around the sub by selecting the \"Active Sonar\" checkbox."); + infoBox = CreateInfoFrame("", "You can take a look at the area around the sub by selecting the \"Active Sonar\" checkbox."); while (!sonar.IsActive) { @@ -270,7 +270,7 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(0.5f); - infoBox = CreateInfoFrame("The blue rectangle in the middle is the submarine, and the flickering shapes outside it are the walls of an underwater cavern. " + infoBox = CreateInfoFrame("", "The blue rectangle in the middle is the submarine, and the flickering shapes outside it are the walls of an underwater cavern. " + "Try moving the submarine by clicking somewhere on the monitor and dragging the pointer to the direction you want to go to."); while (steering.TargetVelocity == Vector2.Zero && steering.TargetVelocity.Length() < 50.0f) @@ -279,15 +279,15 @@ namespace Barotrauma.Tutorials } yield return new WaitForSeconds(4.0f); - infoBox = CreateInfoFrame("The submarine moves up and down by pumping water in and out of the two ballast tanks at the bottom of the submarine. " - + "The engine at the back of the sub moves it forwards and backwards.", true); + infoBox = CreateInfoFrame("", "The submarine moves up and down by pumping water in and out of the two ballast tanks at the bottom of the submarine. " + + "The engine at the back of the sub moves it forwards and backwards.", hasButton: true); while (infoBox != null) { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Steer the submarine downwards, heading further into the cavern."); + infoBox = CreateInfoFrame("", "Steer the submarine downwards, heading further into the cavern."); while (Submarine.MainSub.WorldPosition.Y > 32000.0f) { @@ -303,7 +303,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1.0f); - infoBox = CreateInfoFrame("Uh-oh... Something enormous just appeared on the sonar."); + infoBox = CreateInfoFrame("", "Uh-oh... Something enormous just appeared on the sonar."); List windows = new List(); foreach (Structure s in Structure.WallList) @@ -370,7 +370,7 @@ namespace Barotrauma.Tutorials var capacitor2 = Item.ItemList.Find(i => i.HasTag("capacitor1")).GetComponent(); CoroutineManager.StartCoroutine(KeepEnemyAway(moloch, new PowerContainer[] { capacitor1, capacitor2 })); - infoBox = CreateInfoFrame("The hull has been breached! Close all the doors to the command room to stop the water from flooding the entire sub!"); + infoBox = CreateInfoFrame("", "The hull has been breached! Close all the doors to the command room to stop the water from flooding the entire sub!"); Door commandDoor1 = Item.ItemList.Find(i => i.HasTag("commanddoor1")).GetComponent(); Door commandDoor2 = Item.ItemList.Find(i => i.HasTag("commanddoor2")).GetComponent(); @@ -385,7 +385,7 @@ namespace Barotrauma.Tutorials } - infoBox = CreateInfoFrame("You should quickly find yourself a diving mask or a diving suit. " + + infoBox = CreateInfoFrame("", "You should quickly find yourself a diving mask or a diving suit. " + "There are some in the room next to the airlock."); bool divingMaskSelected = false; @@ -395,7 +395,7 @@ namespace Barotrauma.Tutorials if (!divingMaskSelected && Controlled.FocusedItem != null && Controlled.FocusedItem.Prefab.Identifier == "divingsuit") { - infoBox = CreateInfoFrame("There can only be one item in each inventory slot, so you need to take off " + infoBox = CreateInfoFrame("", "There can only be one item in each inventory slot, so you need to take off " + "the jumpsuit if you wish to wear a diving suit."); divingMaskSelected = true; @@ -406,13 +406,13 @@ namespace Barotrauma.Tutorials if (HasItem("divingmask")) { - infoBox = CreateInfoFrame("The diving mask will let you breathe underwater, but it won't protect from the water pressure outside the sub. " + + infoBox = CreateInfoFrame("", "The diving mask will let you breathe underwater, but it won't protect from the water pressure outside the sub. " + "It should be fine for the situation at hand, but you still need to find an oxygen tank and drag it into the same slot as the mask." + "You should grab one or two from one of the cabinets."); } else if (HasItem("divingsuit")) { - infoBox = CreateInfoFrame("In addition to letting you breathe underwater, the suit will protect you from the water pressure outside the sub " + + infoBox = CreateInfoFrame("", "In addition to letting you breathe underwater, the suit will protect you from the water pressure outside the sub " + "(unlike the diving mask). However, you still need to drag an oxygen tank into the same slot as the suit to supply oxygen. " + "You should grab one or two from one of the cabinets."); } @@ -424,7 +424,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(5.0f); - infoBox = CreateInfoFrame("Now you should stop the creature attacking the submarine before it does any more damage. Head to the railgun room at the upper right corner of the sub."); + infoBox = CreateInfoFrame("", "Now you should stop the creature attacking the submarine before it does any more damage. Head to the railgun room at the upper right corner of the sub."); var railGun = Item.ItemList.Find(i => i.GetComponent() != null); @@ -433,7 +433,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1.0f); } - infoBox = CreateInfoFrame("The railgun requires a large power surge to fire. The reactor can't provide a surge large enough, so we need to use the " + infoBox = CreateInfoFrame("", "The railgun requires a large power surge to fire. The reactor can't provide a surge large enough, so we need to use the " + " supercapacitors in the railgun room. The capacitors need to be charged first; select them and crank up the recharge rate."); while (capacitor1.RechargeSpeed < 0.5f && capacitor2.RechargeSpeed < 0.5f) @@ -441,7 +441,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1.0f); } - infoBox = CreateInfoFrame("The capacitors take some time to recharge, so now is a good " + + infoBox = CreateInfoFrame("", "The capacitors take some time to recharge, so now is a good " + "time to head to the room below and load some shells for the railgun."); @@ -452,7 +452,7 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Grab one of the shells. You can load it by selecting the railgun loader and dragging the shell to. " + infoBox = CreateInfoFrame("", "Grab one of the shells. You can load it by selecting the railgun loader and dragging the shell to. " + "one of the free slots. You need two hands to carry a shell, so make sure you don't have anything else in either hand."); while (loader.Item.ContainedItems.FirstOrDefault(i => i != null && i.Prefab.Identifier == "railgunshell") == null) @@ -465,7 +465,7 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("Now we're ready to shoot! Select the railgun controller."); + infoBox = CreateInfoFrame("", "Now we're ready to shoot! Select the railgun controller."); while (Controlled.SelectedConstruction == null || Controlled.SelectedConstruction.Prefab.Identifier != "railguncontroller") { @@ -474,7 +474,7 @@ namespace Barotrauma.Tutorials moloch.AnimController.SetPosition(ConvertUnits.ToSimUnits(Controlled.WorldPosition + Vector2.UnitY * 600.0f)); - infoBox = CreateInfoFrame("Use the right mouse button to aim and wait for the creature to come closer. When you're ready to shoot, " + infoBox = CreateInfoFrame("", "Use the right mouse button to aim and wait for the creature to come closer. When you're ready to shoot, " + "press the left mouse button."); while (!moloch.IsDead) @@ -490,7 +490,7 @@ namespace Barotrauma.Tutorials Submarine.MainSub.GodMode = false; - infoBox = CreateInfoFrame("The creature has died. Now you should fix the damages in the control room: " + + infoBox = CreateInfoFrame("", "The creature has died. Now you should fix the damages in the control room: " + "Grab a welding tool from the closet in the railgun room."); while (!HasItem("weldingtool")) @@ -498,7 +498,7 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("The welding tool requires fuel to work. Grab a welding fuel tank and attach it to the tool " + + infoBox = CreateInfoFrame("", "The welding tool requires fuel to work. Grab a welding fuel tank and attach it to the tool " + "by dragging it into the same slot."); do @@ -511,7 +511,7 @@ namespace Barotrauma.Tutorials } while (true); - infoBox = CreateInfoFrame("You can aim with the tool using the right mouse button and weld using the left button. " + + infoBox = CreateInfoFrame("", "You can aim with the tool using the right mouse button and weld using the left button. " + "Head to the command room to fix the leaks there."); do @@ -531,7 +531,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(1.0f); } while (broken); - infoBox = CreateInfoFrame("The hull is fixed now, but there's still quite a bit of water inside the sub. It should be pumped out " + infoBox = CreateInfoFrame("", "The hull is fixed now, but there's still quite a bit of water inside the sub. It should be pumped out " + "using the bilge pump in the room at the bottom of the submarine."); Pump pump = Item.ItemList.Find(i => i.HasTag("tutorialpump")).GetComponent(); @@ -541,10 +541,10 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("The two pumps inside the ballast tanks " + infoBox = CreateInfoFrame("", "The two pumps inside the ballast tanks " + "are connected straight to the navigation terminal and can't be manually controlled unless you mess with their wiring, " + "so you should only use the pump in the middle room to pump out the water. Select it, turn it on and adjust the pumping speed " + - "to start pumping water out.", true); + "to start pumping water out.", hasButton: true); while (infoBox != null) { @@ -562,8 +562,8 @@ namespace Barotrauma.Tutorials { brokenMsgShown = true; - infoBox = CreateInfoFrame("Looks like the pump isn't getting any power. The water must have short-circuited some of the junction " - +"boxes. You can check which boxes are broken by selecting them."); + infoBox = CreateInfoFrame("", "Looks like the pump isn't getting any power. The water must have short-circuited some of the junction " + + "boxes. You can check which boxes are broken by selecting them."); while (true) { @@ -573,8 +573,8 @@ namespace Barotrauma.Tutorials { brokenBox = Controlled.SelectedConstruction; - infoBox = CreateInfoFrame("Here's our problem: this junction box is broken. Luckily engineers are adept at fixing electrical devices - " - +"you just need to find a spare wire and click the \"Fix\"-button to repair the box."); + infoBox = CreateInfoFrame("", "Here's our problem: this junction box is broken. Luckily engineers are adept at fixing electrical devices - " + + "you just need to find a spare wire and click the \"Fix\"-button to repair the box."); break; } @@ -590,7 +590,7 @@ namespace Barotrauma.Tutorials if (pump.Voltage < pump.MinVoltage) { - infoBox = CreateInfoFrame("The pump is still not running. Check if there are more broken junction boxes between the pump and the reactor."); + infoBox = CreateInfoFrame("", "The pump is still not running. Check if there are more broken junction boxes between the pump and the reactor."); } brokenBox = null; } @@ -598,14 +598,14 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("The pump is up and running. Wait for the water to be drained out."); + infoBox = CreateInfoFrame("", "The pump is up and running. Wait for the water to be drained out."); while (pump.Item.CurrentHull.WaterVolume > 1000.0f) { yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } - infoBox = CreateInfoFrame("That was all there is to this tutorial! Now you should be able to handle " + + infoBox = CreateInfoFrame("", "That was all there is to this tutorial! Now you should be able to handle " + "most of the basic tasks on board the submarine."); Completed = true; diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/CaptainTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/CaptainTutorial.cs new file mode 100644 index 000000000..00ae9403c --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/CaptainTutorial.cs @@ -0,0 +1,271 @@ +using System.Collections.Generic; +using System.Xml.Linq; +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; + +namespace Barotrauma.Tutorials +{ + class CaptainTutorial : ScenarioTutorial + { + // Room 1 + private float shakeTimer = 1f; + private float shakeAmount = 20f; + + // Room 2 + private MotionSensor captain_equipmentObjectiveSensor; + private ItemContainer captain_equipmentCabinet; + private Door captain_firstDoor; + private LightComponent captain_firstDoorLight; + + // Room 3 + private Character captain_medic; + private MotionSensor captain_medicObjectiveSensor; + private Vector2 captain_medicSpawnPos; + private Door tutorial_submarineDoor; + private LightComponent tutorial_submarineDoorLight; + + // Submarine + private MotionSensor captain_enteredSubmarineSensor; + private Steering captain_navConsole; + private CustomInterface captain_navConsoleCustomInterface; + private Sonar captain_sonar; + private Item captain_statusMonitor; + private Character captain_security; + private Character captain_mechanic; + private Character captain_engineer; + private Reactor tutorial_submarineReactor; + private Door tutorial_lockedDoor_1; + private Door tutorial_lockedDoor_2; + + // Variables + private Character captain; + private string radioSpeakerName; + private Sprite captain_steerIcon; + private Color captain_steerIconColor; + + public CaptainTutorial(XElement element) : base(element) + { + } + + public override void Start() + { + base.Start(); + + captain = Character.Controlled; + radioSpeakerName = TextManager.Get("Tutorial.Radio.Watchman"); + GameMain.GameSession.CrewManager.AllowCharacterSwitch = false; + + var revolver = captain.Inventory.FindItemByIdentifier("revolver"); + revolver.Unequip(captain); + captain.Inventory.RemoveItem(revolver); + + var captainscap = captain.Inventory.FindItemByIdentifier("captainscap"); + captainscap.Unequip(captain); + captain.Inventory.RemoveItem(captainscap); + + var captainsuniform = captain.Inventory.FindItemByIdentifier("captainsuniform"); + captainsuniform.Unequip(captain); + captain.Inventory.RemoveItem(captainsuniform); + + var steerOrder = Order.PrefabList.Find(order => order.AITag == "steer"); + captain_steerIcon = steerOrder.SymbolSprite; + captain_steerIconColor = steerOrder.Color; + + // Room 2 + captain_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("captain_equipmentobjectivesensor")).GetComponent(); + captain_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("captain_equipmentcabinet")).GetComponent(); + captain_firstDoor = Item.ItemList.Find(i => i.HasTag("captain_firstdoor")).GetComponent(); + captain_firstDoorLight = Item.ItemList.Find(i => i.HasTag("captain_firstdoorlight")).GetComponent(); + + SetDoorAccess(captain_firstDoor, captain_firstDoorLight, true); + + // Room 3 + captain_medicObjectiveSensor = Item.ItemList.Find(i => i.HasTag("captain_medicobjectivesensor")).GetComponent(); + captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition; + tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); + tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); + var medicInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "medicaldoctor")); + captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor"); + captain_medic.GiveJobItems(null); + captain_medic.CanSpeak = captain_medic.AIController.Enabled = false; + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false); + + // Submarine + captain_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("captain_enteredsubmarinesensor")).GetComponent(); + tutorial_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent(); + captain_navConsole = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); + captain_navConsoleCustomInterface = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); + captain_sonar = captain_navConsole.Item.GetComponent(); + captain_statusMonitor = Item.ItemList.Find(i => i.HasTag("captain_statusmonitor")); + + tutorial_submarineReactor.CanBeSelected = false; + tutorial_submarineReactor.IsActive = tutorial_submarineReactor.AutoTemp = false; + + tutorial_lockedDoor_1 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_1")).GetComponent(); + tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent(); + SetDoorAccess(tutorial_lockedDoor_1, null, false); + SetDoorAccess(tutorial_lockedDoor_2, null, false); + + var mechanicInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "mechanic")); + captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic"); + captain_mechanic.GiveJobItems(); + + var securityInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "securityofficer")); + captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer"); + captain_security.GiveJobItems(); + + var engineerInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "engineer")); + captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer"); + captain_engineer.GiveJobItems(); + + captain_mechanic.CanSpeak = captain_security.CanSpeak = captain_engineer.CanSpeak = false; + captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = false; + } + + public override IEnumerable UpdateState() + { + while (GameMain.Instance.LoadingScreenOpen) yield return null; + + // Room 1 + while (shakeTimer > 0.0f) // Wake up, shake + { + shakeTimer -= 0.1f; + GameMain.GameScreen.Cam.Shake = shakeAmount; + yield return new WaitForSeconds(0.1f); + } + + // Room 2 + do { yield return null; } while (!captain_firstDoor.IsOpen); + captain_medic.AIController.Enabled = true; + + // Room 3 + do { yield return null; } while (!captain_medicObjectiveSensor.MotionDetected); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(captain_medic.Info.DisplayName, TextManager.Get("Captain.Radio.Medic"), ChatMessageType.Radio, null); + yield return new WaitForSeconds(2f); + GameMain.GameSession.CrewManager.ToggleCrewAreaOpen = true; + GameMain.GameSession.CrewManager.AddCharacter(captain_medic); + TriggerTutorialSegment(0); + do + { + yield return null; + GameMain.GameSession.CrewManager.HighlightOrderButton(captain_medic, "follow", highlightColor, new Vector2(5, 5)); + } + while (!HasOrder(captain_medic, "follow")); + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); + RemoveCompletedObjective(segments[0]); + + // Submarine + do { yield return null; } while (!captain_enteredSubmarineSensor.MotionDetected); + yield return new WaitForSeconds(3f); + captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = true; + TriggerTutorialSegment(1); + GameMain.GameSession.CrewManager.AddCharacter(captain_mechanic); + do + { + yield return null; + GameMain.GameSession.CrewManager.HighlightOrderButton(captain_mechanic, "repairsystems", highlightColor, new Vector2(5, 5)); + HighlightOrderOption("jobspecific"); + } + while (!HasOrder(captain_mechanic, "repairsystems", "jobspecific")); + RemoveCompletedObjective(segments[1]); + yield return new WaitForSeconds(2f); + TriggerTutorialSegment(2); + GameMain.GameSession.CrewManager.AddCharacter(captain_security); + do + { + yield return null; + GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5)); + HighlightOrderOption("fireatwill"); + } + while (!HasOrder(captain_security, "operateweapons", "fireatwill")); + RemoveCompletedObjective(segments[2]); + yield return new WaitForSeconds(4f); + TriggerTutorialSegment(3); + GameMain.GameSession.CrewManager.AddCharacter(captain_engineer); + do + { + yield return null; + GameMain.GameSession.CrewManager.HighlightOrderButton(captain_engineer, "operatereactor", highlightColor, new Vector2(5, 5)); + HighlightOrderOption("powerup"); + } + while (!HasOrder(captain_engineer, "operatereactor", "powerup")); + RemoveCompletedObjective(segments[3]); + tutorial_submarineReactor.CanBeSelected = true; + do { yield return null; } while (!tutorial_submarineReactor.IsActive); // Wait until reactor on + TriggerTutorialSegment(4); + while (ContentRunning) yield return null; + captain.AddActiveObjectiveEntity(captain_navConsole.Item, captain_steerIcon, captain_steerIconColor); + SetHighlight(captain_navConsole.Item, true); + SetHighlight(captain_sonar.Item, true); + SetHighlight(captain_statusMonitor, true); + do + { + captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); + yield return new WaitForSeconds(1.0f); + } while (Submarine.MainSub.DockedTo.Count > 0); + RemoveCompletedObjective(segments[4]); + yield return new WaitForSeconds(2f); + TriggerTutorialSegment(5); // Navigate to destination + do + { + if (IsSelectedItem(captain_navConsole.Item)) + { + if (captain_sonar.ActiveTickBox.Box.FlashTimer <= 0) + { + captain_sonar.ActiveTickBox.Box.Flash(highlightColor, 1.5f, false, new Vector2(2.5f, 2.5f)); + //captain_sonar.ActiveTickBox.Box.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.5f); + } + } + yield return null; + } while (!captain_sonar.IsActive); + do { yield return null; } while (Vector2.Distance(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) > 4000f); + RemoveCompletedObjective(segments[5]); + yield return new WaitForSeconds(4f); + TriggerTutorialSegment(6); // Docking + do + { + captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); + yield return new WaitForSeconds(1.0f); + } while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0); + RemoveCompletedObjective(segments[6]); + yield return new WaitForSeconds(3f); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Captain.Radio.Complete").Replace("[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null); + SetHighlight(captain_navConsole.Item, false); + SetHighlight(captain_sonar.Item, false); + SetHighlight(captain_statusMonitor, false); + captain.RemoveActiveObjectiveEntity(captain_navConsole.Item); + + CoroutineManager.StartCoroutine(TutorialCompleted()); + } + + private void HighlightOrderOption(string option) + { + if (GameMain.GameSession.CrewManager.OrderOptionButtons.Count == 0) return; + var order = GameMain.GameSession.CrewManager.OrderOptionButtons[0].UserData as Order; + + int orderIndex = 0; + for (int i = 0; i < GameMain.GameSession.CrewManager.OrderOptionButtons.Count; i++) + { + if (orderIndex >= order.Options.Length) + { + orderIndex = 0; + } + if (order.Options[orderIndex] == option) + { + if (GameMain.GameSession.CrewManager.OrderOptionButtons[i].Frame.FlashTimer <= 0) + { + GameMain.GameSession.CrewManager.OrderOptionButtons[i].Frame.Flash(highlightColor); + } + } + + orderIndex++; + } + } + + private bool IsSelectedItem(Item item) + { + return captain?.SelectedConstruction == item; + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs index d8ac5bc80..a05c6a835 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs @@ -1,25 +1,20 @@ -using System.Collections.Generic; +/*using System.Collections.Generic; using System.Xml.Linq; using System; using Microsoft.Xna.Framework; using Barotrauma.Items.Components; using System.Linq; -using Microsoft.Xna.Framework.Input; namespace Barotrauma.Tutorials { class ContextualTutorial : Tutorial { + public ContextualTutorial(XElement element) : base(element) + { + //Name = "ContextualTutorial"; + } + public static bool Selected = false; - public static bool ContentRunning = false; - public static bool Initialized = false; - - private enum ContentTypes { None = 0, Video = 1, TextOnly = 2 }; - - private TutorialSegment activeSegment; - private List segments; - - private VideoPlayer videoPlayer; private Steering navConsole; private Reactor reactor; @@ -33,83 +28,24 @@ namespace Barotrauma.Tutorials private List> characterTimeOnSonar; private float requiredTimeOnSonar = 5f; - private bool started = false; - private string playableContentPath; - private float tutorialTimer; private bool disableTutorialOnDeficiencyFound = true; - private GUIFrame holderFrame, objectiveFrame; - private List activeObjectives = new List(); - private string objectiveTranslated; - private float floodTutorialTimer = 0.0f; private const float floodTutorialDelay = 2.0f; private float medicalTutorialTimer = 0.0f; private const float medicalTutorialDelay = 2.0f; - private Point screenResolution; - private float prevUIScale; - - private class TutorialSegment - { - public string Id; - public string Objective; - public ContentTypes ContentType; - public XElement TextContent; - public XElement VideoContent; - public bool IsTriggered; - public GUIButton ReplayButton; - public GUITextBlock LinkedTitle, LinkedText; - - public TutorialSegment(XElement config) - { - Id = config.GetAttributeString("id", "Missing ID"); - Objective = TextManager.Get(config.GetAttributeString("objective", string.Empty), true); - Enum.TryParse(config.GetAttributeString("contenttype", "None"), true, out ContentType); - IsTriggered = config.GetAttributeBool("istriggered", false); - - switch (ContentType) - { - case ContentTypes.None: - break; - case ContentTypes.Video: - VideoContent = config.Element("Video"); - TextContent = config.Element("Text"); - break; - case ContentTypes.TextOnly: - TextContent = config.Element("Text"); - break; - } - } - } - - public ContextualTutorial(XElement element) : base(element) - { - playableContentPath = element.GetAttributeString("playablecontentpath", ""); - segments = new List(); - - foreach (var segment in element.Elements("Segment")) - { - segments.Add(new TutorialSegment(segment)); - } - - Name = "ContextualTutorial"; - } - public override void Initialize() { + base.Initialize(); + for (int i = 0; i < segments.Count; i++) { segments[i].IsTriggered = false; } - if (Initialized) return; - Initialized = true; - - base.Initialize(); - videoPlayer = new VideoPlayer(); characterTimeOnSonar = new List>(); } @@ -167,10 +103,7 @@ namespace Barotrauma.Tutorials base.Start(); injuredMember = null; - activeObjectives.Clear(); - objectiveTranslated = TextManager.Get("Objective"); - CreateObjectiveFrame(); - activeSegment = null; + activeContentSegment = null; tutorialTimer = floodTutorialTimer = medicalTutorialTimer = 0.0f; subStartingPosition = Vector2.Zero; characterTimeOnSonar.Clear(); @@ -183,10 +116,10 @@ namespace Barotrauma.Tutorials #if DEBUG if (reactor == null || navConsole == null || sonar == null) { - infoBox = CreateInfoFrame("Submarine not compatible with the tutorial:" + infoBox = CreateInfoFrame("Error", "Submarine not compatible with the tutorial:" + "\nReactor - " + (reactor != null ? "OK" : "Tag 'reactor' not found") + "\nNavigation Console - " + (navConsole != null ? "OK" : "Tag 'command' not found") - + "\nSonar - " + (sonar != null ? "OK" : "Not found under Navigation Console"), true); + + "\nSonar - " + (sonar != null ? "OK" : "Not found under Navigation Console"), hasButton: true); CoroutineManager.StartCoroutine(WaitForErrorClosed()); return; } @@ -222,62 +155,15 @@ namespace Barotrauma.Tutorials } #endif - public void Stop() + public override void Stop() { - started = ContentRunning = Initialized = false; - videoPlayer.Remove(); - videoPlayer = null; + base.Stop(); characterTimeOnSonar = null; } - private void CreateObjectiveFrame() - { - holderFrame = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center)); - objectiveFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ObjectiveAnchor, holderFrame.RectTransform), style: null); - - for (int i = 0; i < activeObjectives.Count; i++) - { - CreateObjectiveGUI(activeObjectives[i], i); - } - - screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); - prevUIScale = GUI.Scale; - } - - public override void AddToGUIUpdateList() - { - if (videoPlayer != null) - { - videoPlayer.AddToGUIUpdateList(order: 100); - } - - if (GUI.DisableHUD) return; - if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale) - { - CreateObjectiveFrame(); - } - - if (objectiveFrame != null && activeObjectives.Count > 0) - { - objectiveFrame.AddToGUIUpdateList(order: -1); - } - base.AddToGUIUpdateList(); - } - public override void Update(float deltaTime) { - if (videoPlayer != null) - { - videoPlayer.Update(); - } - - if (infoBox != null) - { - if (PlayerInput.KeyHit(Keys.Enter) || PlayerInput.KeyHit(Keys.Escape)) - { - CloseInfoFrame(null, null); - } - } + base.Update(deltaTime); if (!started || ContentRunning) return; @@ -285,93 +171,12 @@ namespace Barotrauma.Tutorials for (int i = 0; i < segments.Count; i++) { - if (segments[i].IsTriggered || activeObjectives.Contains(segments[i])) continue; + if (segments[i].IsTriggered || HasObjective(segments[i])) continue; if (CheckContextualTutorials(i, deltaTime)) // Found a relevant tutorial, halt finding new ones { break; } } - - for (int i = 0; i < activeObjectives.Count; i++) - { - CheckActiveObjectives(activeObjectives[i], deltaTime); - } - } - - private void ClosePreTextAndTriggerVideoCallback() - { - videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(activeSegment.VideoContent), new VideoPlayer.TextSettings(activeSegment.VideoContent), activeSegment.Id, true, activeSegment.Objective, CurrentSegmentStopCallback); - } - - private void CurrentSegmentStopCallback() - { - if (!string.IsNullOrEmpty(activeSegment.Objective)) - { - AddNewObjective(activeSegment); - } - - activeSegment = null; - ContentRunning = false; - } - - private void AddNewObjective(TutorialSegment segment) - { - activeObjectives.Add(segment); - CreateObjectiveGUI(segment, activeObjectives.Count - 1); - } - - private void CreateObjectiveGUI(TutorialSegment segment, int index) - { - Point replayButtonSize = new Point((int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).X * GUI.Scale), (int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).Y * 1.45f * GUI.Scale)); - - segment.ReplayButton = new GUIButton(new RectTransform(replayButtonSize, objectiveFrame.RectTransform, Anchor.TopRight, Pivot.TopRight) { AbsoluteOffset = new Point(0, (replayButtonSize.Y + (int)(20f * GUI.Scale)) * index) }, style: null); - segment.ReplayButton.OnClicked += (GUIButton btn, object userdata) => - { - ReplaySegmentVideo(segment); - return true; - }; - - int yOffset = (int)((GUI.ObjectiveNameFont.MeasureString(objectiveTranslated).Y / 2f + 5) * GUI.Scale); - segment.LinkedTitle = new GUITextBlock(new RectTransform(new Point(replayButtonSize.X, yOffset), segment.ReplayButton.RectTransform, Anchor.Center, Pivot.BottomCenter) { AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }, objectiveTranslated, textColor: Color.White, font: GUI.ObjectiveTitleFont, textAlignment: Alignment.CenterRight); - segment.LinkedText = new GUITextBlock(new RectTransform(new Point(replayButtonSize.X, yOffset), segment.ReplayButton.RectTransform, Anchor.Center, Pivot.TopCenter) { AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }, segment.Objective, textColor: new Color(4, 180, 108), font: GUI.ObjectiveNameFont, textAlignment: Alignment.CenterRight); - - segment.LinkedTitle.TextScale = segment.LinkedText.TextScale = GUI.Scale; - - segment.LinkedTitle.Color = segment.LinkedTitle.HoverColor = segment.LinkedTitle.PressedColor = segment.LinkedTitle.SelectedColor = Color.Transparent; - segment.LinkedText.Color = segment.LinkedText.HoverColor = segment.LinkedText.PressedColor = segment.LinkedText.SelectedColor = Color.Transparent; - segment.ReplayButton.Color = segment.ReplayButton.HoverColor = segment.ReplayButton.PressedColor = segment.ReplayButton.SelectedColor = Color.Transparent; - } - - private void RemoveCompletedObjective(TutorialSegment objective) - { - objective.IsTriggered = true; - - int checkMarkHeight = (int)(objective.ReplayButton.Rect.Height * 1.2f); - int checkMarkWidth = (int)(checkMarkHeight * 0.93f); - - Color color = new Color(4, 180, 108); - RectTransform rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), objective.ReplayButton.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft); - rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - 5, 0); - GUIImage checkmark = new GUIImage(rectTA, "CheckMark"); - checkmark.Color = color; - - RectTransform rectTB = new RectTransform(new Vector2(1.1f, .8f), objective.LinkedText.RectTransform, Anchor.Center, Pivot.Center); - GUIImage stroke = new GUIImage(rectTB, "Stroke"); - stroke.Color = color; - - CoroutineManager.StartCoroutine(WaitForObjectiveEnd(objective)); - } - - private IEnumerable WaitForObjectiveEnd(TutorialSegment objective) - { - yield return new WaitForSeconds(2.0f); - objectiveFrame.RemoveChild(objective.ReplayButton); - activeObjectives.Remove(objective); - - for (int i = 0; i < activeObjectives.Count; i++) - { - activeObjectives[i].ReplayButton.RectTransform.AbsoluteOffset = new Point(0, (activeObjectives[i].ReplayButton.Rect.Height + 20) * i); - } } private bool CheckContextualTutorials(int index, float deltaTime) @@ -526,17 +331,7 @@ namespace Barotrauma.Tutorials return true; } - private bool HasObjective(string objectiveName) - { - for (int i = 0; i < activeObjectives.Count; i++) - { - if (activeObjectives[i].Id == objectiveName) return true; - } - - return false; - } - - private void CheckActiveObjectives(TutorialSegment objective, float deltaTime) + protected override void CheckActiveObjectives(TutorialSegment objective, float deltaTime) { switch(objective.Id) { @@ -704,50 +499,9 @@ namespace Barotrauma.Tutorials return characterTimeOnSonar.Find(ct => ct.Second >= requiredTimeOnSonar && !ct.First.IsDead) != null; } - private void TriggerTutorialSegment(int index, params object[] args) + protected override void TriggerTutorialSegment(int index, params object[] args) { - Inventory.draggingItem = null; - ContentRunning = true; - activeSegment = segments[index]; - - string tutorialText = TextManager.GetFormatted(activeSegment.TextContent.GetAttributeString("tag", ""), true, args); - string objectiveText = string.Empty; - - if (!string.IsNullOrEmpty(activeSegment.Objective)) - { - if (args.Length == 0) - { - objectiveText = activeSegment.Objective; - } - else - { - objectiveText = string.Format(activeSegment.Objective, args); - } - - activeSegment.Objective = objectiveText; - } - else - { - activeSegment.IsTriggered = true; // Complete at this stage only if no related objective - } - - switch (activeSegment.ContentType) - { - case ContentTypes.None: - break; - case ContentTypes.Video: - infoBox = CreateInfoFrame(TextManager.Get(activeSegment.Id), tutorialText, - activeSegment.TextContent.GetAttributeInt("width", 300), - activeSegment.TextContent.GetAttributeInt("height", 80), - activeSegment.TextContent.GetAttributeString("anchor", "Center"), true, ClosePreTextAndTriggerVideoCallback); - break; - case ContentTypes.TextOnly: - infoBox = CreateInfoFrame(TextManager.Get(activeSegment.Id), tutorialText, - activeSegment.TextContent.GetAttributeInt("width", 300), - activeSegment.TextContent.GetAttributeInt("height", 80), - activeSegment.TextContent.GetAttributeString("anchor", "Center"), true, CurrentSegmentStopCallback); - break; - } + base.TriggerTutorialSegment(index, args); for (int i = 0; i < segments.Count; i++) { @@ -757,17 +511,10 @@ namespace Barotrauma.Tutorials CoroutineManager.StartCoroutine(WaitToStop()); // Completed } - private void ReplaySegmentVideo(TutorialSegment segment) - { - if (ContentRunning) return; - ContentRunning = true; - videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false); - } - private IEnumerable WaitToStop() { while (ContentRunning) yield return null; Stop(); } } -} +}*/ diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/DoctorTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/DoctorTutorial.cs new file mode 100644 index 000000000..8ad220618 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/DoctorTutorial.cs @@ -0,0 +1,422 @@ +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace Barotrauma.Tutorials +{ + class DoctorTutorial : ScenarioTutorial + { + // Room 1 + private float shakeTimer = 1f; + private float shakeAmount = 20f; + + private string radioSpeakerName; + private Character doctor; + + private ItemContainer doctor_suppliesCabinet; + private ItemContainer doctor_medBayCabinet; + private Character patient1, patient2; + private List subPatients = new List(); + private Hull startRoom; + private Hull medBay; + + private Door doctor_firstDoor; + private Door doctor_secondDoor; + private Door doctor_thirdDoor; + private Door tutorial_upperFinalDoor; + private Door tutorial_lockedDoor_2; + + private LightComponent doctor_firstDoorLight; + private LightComponent doctor_secondDoorLight; + private LightComponent doctor_thirdDoorLight; + private Door tutorial_submarineDoor; + private LightComponent tutorial_submarineDoorLight; + + // Variables + private Color doctor_iconColor = new Color(178, 118, 139); + + public DoctorTutorial(XElement element) : base(element) + { + } + public override void Start() + { + base.Start(); + + radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); + doctor = Character.Controlled; + + doctor_suppliesCabinet = Item.ItemList.Find(i => i.HasTag("doctor_suppliescabinet"))?.GetComponent(); + doctor_medBayCabinet = Item.ItemList.Find(i => i.HasTag("doctor_medbaycabinet"))?.GetComponent(); + + var patientHull1 = Hull.hullList.Find(h => h.RoomName == "Waiting room" && h.Submarine == doctor.Submarine); + var patientHull2 = Hull.hullList.Find(h => h.RoomName == "Airlock" && h.Submarine == doctor.Submarine); + medBay = Hull.hullList.Find(h => h.RoomName == "Med bay" && h.Submarine == doctor.Submarine); + + var assistantInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "assistant")); + patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1"); + patient1.GiveJobItems(null); + patient1.CanSpeak = false; + patient1.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 45.0f) }, stun: 0, playSound: false); + patient1.AIController.Enabled = false; + + assistantInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "assistant")); + patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2"); + patient2.GiveJobItems(null); + patient2.CanSpeak = false; + patient2.AIController.Enabled = false; + + var mechanicInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "engineer")); + var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3"); + subPatient1.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false); + subPatients.Add(subPatient1); + + var securityInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "securityofficer")); + var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3"); + subPatient2.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false); + subPatients.Add(subPatient2); + + var engineerInfo = new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "engineer")); + var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3"); + subPatient3.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false); + subPatients.Add(subPatient3); + + doctor_firstDoor = Item.ItemList.Find(i => i.HasTag("doctor_firstdoor")).GetComponent(); + doctor_secondDoor = Item.ItemList.Find(i => i.HasTag("doctor_seconddoor")).GetComponent(); + doctor_thirdDoor = Item.ItemList.Find(i => i.HasTag("doctor_thirddoor")).GetComponent(); + tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent(); + doctor_firstDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_firstdoorlight")).GetComponent(); + doctor_secondDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_seconddoorlight")).GetComponent(); + doctor_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_thirddoorlight")).GetComponent(); + SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, false); + SetDoorAccess(doctor_secondDoor, doctor_secondDoorLight, false); + SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, false); + tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); + tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false); + tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent(); + SetDoorAccess(tutorial_lockedDoor_2, null, true); + + + foreach (var patient in subPatients) + { + patient.CanSpeak = false; + patient.AIController.Enabled = false; + patient.GiveJobItems(); + } + + Item reactorItem = Item.ItemList.Find(i => i.Submarine == Submarine.MainSub && i.GetComponent() != null); + reactorItem.GetComponent().AutoTemp = true; + } + + public override IEnumerable UpdateState() + { + while (GameMain.Instance.LoadingScreenOpen) yield return null; + + // explosions and radio messages ------------------------------------------------------ + + yield return new WaitForSeconds(3.0f); + + //SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition); + //// Room 1 + //while (shakeTimer > 0.0f) // Wake up, shake + //{ + // shakeTimer -= 0.1f; + // GameMain.GameScreen.Cam.Shake = shakeAmount; + // yield return new WaitForSeconds(0.1f); + //} + //yield return new WaitForSeconds(2.5f); + //GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null); + + //yield return new WaitForSeconds(2.5f); + + doctor.SetStun(1.5f); + var explosion = new Explosion(range: 100, force: 10, damage: 0, structureDamage: 0); + explosion.DisableParticles(); + GameMain.GameScreen.Cam.Shake = shakeAmount; + explosion.Explode(Character.Controlled.WorldPosition - Vector2.UnitX * 25, null); + SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition - Vector2.UnitX * 25); + + yield return new WaitForSeconds(0.5f); + + doctor.DamageLimb( + Character.Controlled.WorldPosition, + doctor.AnimController.GetLimb(LimbType.Torso), + new List { new Affliction(AfflictionPrefab.InternalDamage, 10.0f) }, + stun: 3.0f, playSound: true, attackImpulse: 0.0f); + + shakeTimer = 0.5f; + while (shakeTimer > 0.0f) // Wake up, shake + { + shakeTimer -= 0.1f; + GameMain.GameScreen.Cam.Shake = shakeAmount; + yield return new WaitForSeconds(0.1f); + } + + yield return new WaitForSeconds(3.0f); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.KnockedDown"), ChatMessageType.Radio, null); + + // first tutorial segment, get medical supplies ------------------------------------------------------ + + yield return new WaitForSeconds(1.5f); + SetHighlight(doctor_suppliesCabinet.Item, true); + + /*while (doctor.CurrentHull != doctor_suppliesCabinet.Item.CurrentHull) + { + yield return new WaitForSeconds(2.0f); + }*/ + + TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Use), GameMain.Config.KeyBind(InputType.Deselect)); // Medical supplies objective + + do + { + for (int i = 0; i < doctor_suppliesCabinet.Inventory.Items.Length; i++) + { + if (doctor_suppliesCabinet.Inventory.Items[i] != null) + { + HighlightInventorySlot(doctor_suppliesCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + if (doctor.SelectedConstruction == doctor_suppliesCabinet.Item) + { + for (int i = 0; i < doctor.Inventory.slots.Length; i++) + { + if (doctor.Inventory.Items[i] == null) HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + yield return null; + } while (doctor.Inventory.FindItemByIdentifier("antidama1") == null); // Wait until looted + yield return new WaitForSeconds(1.0f); + + SetHighlight(doctor_suppliesCabinet.Item, false); + RemoveCompletedObjective(segments[0]); + + yield return new WaitForSeconds(1.0f); + + // 2nd tutorial segment, treat self ------------------------------------------------------------------------- + + TriggerTutorialSegment(1, GameMain.Config.KeyBind(InputType.Health)); // Open health interface + while (CharacterHealth.OpenHealthWindow == null) + { + yield return new WaitForSeconds(1.0f); + } + RemoveCompletedObjective(segments[1]); + + TriggerTutorialSegment(2); //Treat self + while (doctor.CharacterHealth.GetAfflictionStrength("damage") > 0.01f) + { + if (CharacterHealth.OpenHealthWindow == null) + { + doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f; + } + else + { + HighlightInventorySlot(doctor.Inventory, "antidama1", highlightColor, .5f, .5f, 0f); + } + + yield return null; + } + + RemoveCompletedObjective(segments[2]); + SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, true); + + while (CharacterHealth.OpenHealthWindow != null) + { + yield return new WaitForSeconds(1.0f); + } + + // treat patient -------------------------------------------------------------------------------------------- + + //patient 1 requests first aid + patient1.CanSpeak = true; + var newOrder = new Order(Order.PrefabList.Find(o => o.AITag == "requestfirstaid"), patient1.CurrentHull, null); + GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime); + patient1.Speak(newOrder.GetChatMessage("", patient1.CurrentHull?.RoomName, givingOrderToSelf: false), ChatMessageType.Order); + patient1.AIController.Enabled = true; + + while (doctor.CurrentHull != patient1.CurrentHull) + { + yield return new WaitForSeconds(1.0f); + } + yield return new WaitForSeconds(0.0f); + + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.AssistantBurns"), ChatMessageType.Radio, null); + GameMain.GameSession.CrewManager.AllowCharacterSwitch = false; + GameMain.GameSession.CrewManager.AddCharacter(doctor); + GameMain.GameSession.CrewManager.AddCharacter(patient1); + GameMain.GameSession.CrewManager.ToggleCrewAreaOpen = true; + + yield return new WaitForSeconds(3.0f); + TriggerTutorialSegment(3); // Get the patient to medbay + + while (patient1.CurrentOrder == null || patient1.CurrentOrder.AITag != "follow") + { + GameMain.GameSession.CrewManager.HighlightOrderButton(patient1, "follow", highlightColor, new Vector2(5, 5)); + yield return null; + } + + SetDoorAccess(doctor_secondDoor, doctor_secondDoorLight, true); + + while (patient1.CurrentHull != medBay) + { + yield return new WaitForSeconds(1.0f); + } + RemoveCompletedObjective(segments[3]); + SetHighlight(doctor_medBayCabinet.Item, true); + SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, true); + + yield return new WaitForSeconds(2.0f); + + TriggerTutorialSegment(4, GameMain.Config.KeyBind(InputType.Health)); // treat burns + + do + { + for (int i = 0; i < 3; i++) + { + if (doctor_medBayCabinet.Inventory.Items[i] != null) + { + HighlightInventorySlot(doctor_medBayCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + if (doctor.SelectedConstruction == doctor_medBayCabinet.Item) + { + for (int i = 0; i < doctor.Inventory.slots.Length; i++) + { + if (doctor.Inventory.Items[i] == null) HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + yield return null; + } while (doctor.Inventory.FindItemByIdentifier("antibleeding1") == null); // Wait until looted + SetHighlight(doctor_medBayCabinet.Item, false); + SetHighlight(patient1, true); + + while (patient1.CharacterHealth.GetAfflictionStrength("burn") > 0.01f) + { + if (CharacterHealth.OpenHealthWindow == null) + { + doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f; + } + else + { + HighlightInventorySlot(doctor.Inventory, "antibleeding1", highlightColor, .5f, .5f, 0f); + } + yield return null; + + } + RemoveCompletedObjective(segments[4]); + SetHighlight(patient1, false); + yield return new WaitForSeconds(1.0f); + + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.AssistantBurnsHealed"), ChatMessageType.Radio, null); + + // treat unconscious patient ------------------------------------------------------ + + //patient calls for help + patient2.CanSpeak = true; + newOrder = new Order(Order.PrefabList.Find(o => o.AITag == "requestfirstaid"), patient2.CurrentHull, null); + GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime); + patient2.Speak(newOrder.GetChatMessage("", patient1.CurrentHull?.RoomName, givingOrderToSelf: false), ChatMessageType.Order); + patient2.AIController.Enabled = true; + patient2.Oxygen = -50; + CoroutineManager.StartCoroutine(KeepPatientAlive(patient2), "KeepPatient2Alive"); + + /*while (doctor.CurrentHull != patient2.CurrentHull) + { + yield return new WaitForSeconds(1.0f); + }*/ + do { yield return null; } while (!tutorial_upperFinalDoor.IsOpen); + yield return new WaitForSeconds(2.0f); + + TriggerTutorialSegment(5, GameMain.Config.KeyBind(InputType.Health)); // perform CPR + SetHighlight(patient2, true); + while (patient2.IsUnconscious) + { + if (CharacterHealth.OpenHealthWindow != null && doctor.AnimController.Anim != AnimController.Animation.CPR) + { + CharacterHealth.OpenHealthWindow.CPRButton.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.0f); + CharacterHealth.OpenHealthWindow.CPRButton.Flash(); + } + yield return null; + } + RemoveCompletedObjective(segments[5]); + SetHighlight(patient2, false); + CoroutineManager.StopCoroutines("KeepPatient2Alive"); + + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); + + while (doctor.Submarine != Submarine.MainSub) + { + yield return new WaitForSeconds(1.0f); + } + yield return new WaitForSeconds(5.0f); + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.EnteredSub"), ChatMessageType.Radio, null); + + yield return new WaitForSeconds(3.0f); + TriggerTutorialSegment(6, GameMain.Config.KeyBind(InputType.Health)); // give treatment to anyone in need + + foreach (var patient in subPatients) + { + patient.CanSpeak = true; + patient.AIController.Enabled = true; + SetHighlight(patient, true); + } + subPatients[2].Oxygen = -50; + CoroutineManager.StartCoroutine(KeepPatientAlive(subPatients[2]), "KeepPatient3Alive"); + + double subEnterTime = Timing.TotalTime; + + bool[] patientCalledHelp = new bool[] { false, false, false }; + while (subPatients.Any(p => p.Vitality < p.MaxVitality * 0.9f && !p.IsDead)) + { + for (int i = 0; i < subPatients.Count; i++) + { + //make patients call for help to make sure the player finds them + //(within 1 minute intervals of entering the sub) + if (!patientCalledHelp[i] && Timing.TotalTime > subEnterTime + 60 * (i + 1)) + { + newOrder = new Order(Order.PrefabList.Find(o => o.AITag == "requestfirstaid"), subPatients[i].CurrentHull, null); + GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime); + + string message = newOrder.GetChatMessage("", subPatients[i].CurrentHull?.RoomName, givingOrderToSelf: false); + if (subPatients[i].CanSpeak) + { + subPatients[i].Speak(message, ChatMessageType.Order); + } + else + { + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, message, ChatMessageType.Radio, null); + } + patientCalledHelp[i] = true; + } + + if (subPatients[i].ExternalHighlight && subPatients[i].Vitality >= subPatients[i].MaxVitality * 0.9f) + { + SetHighlight(subPatients[i], false); + } + } + yield return new WaitForSeconds(1.0f); + } + RemoveCompletedObjective(segments[6]); + foreach (var patient in subPatients) + { + SetHighlight(patient, false); + } + + // END TUTORIAL + CoroutineManager.StartCoroutine(TutorialCompleted()); + } + + public IEnumerable KeepPatientAlive(Character patient) + { + while (patient != null && !patient.Removed) + { + patient.Oxygen = Math.Max(patient.Oxygen, -50); + yield return null; + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/EngineerTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/EngineerTutorial.cs new file mode 100644 index 000000000..f230542a1 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/EngineerTutorial.cs @@ -0,0 +1,567 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; + +namespace Barotrauma.Tutorials +{ + class EngineerTutorial : ScenarioTutorial + { + // Other tutorial items + private LightComponent tutorial_securityFinalDoorLight; + private LightComponent tutorial_mechanicFinalDoorLight; + private Steering tutorial_submarineSteering; + + // Room 1 + private float shakeTimer = 1f; + private float shakeAmount = 20f; + + // Room 2 + private MotionSensor engineer_equipmentObjectiveSensor; + private ItemContainer engineer_equipmentCabinet; + private Door engineer_firstDoor; + private LightComponent engineer_firstDoorLight; + + // Room 3 + private MotionSensor engineer_reactorObjectiveSensor; + private Powered tutorial_oxygenGenerator; + private Reactor engineer_reactor; + private Door engineer_secondDoor; + private LightComponent engineer_secondDoorLight; + + // Room 4 + private MotionSensor engineer_repairJunctionBoxObjectiveSensor; + private Item engineer_brokenJunctionBox; + private Door engineer_thirdDoor; + private LightComponent engineer_thirdDoorLight; + + // Room 5 + private MotionSensor engineer_disconnectedJunctionBoxObjectiveSensor; + private PowerTransfer[] engineer_disconnectedJunctionBoxes; + private ConnectionPanel[] engineer_disconnectedConnectionPanels; + private Item engineer_wire_1; + private Powered engineer_lamp_1; + private Item engineer_wire_2; + private Powered engineer_lamp_2; + private Door engineer_fourthDoor; + private LightComponent engineer_fourthDoorLight; + + // Room 6 + private Pump engineer_workingPump; + private Door tutorial_lockedDoor_1; + + // Submarine + private Door tutorial_submarineDoor; + private LightComponent tutorial_submarineDoorLight; + private MotionSensor tutorial_enteredSubmarineSensor; + private Item engineer_submarineJunctionBox_1; + private Item engineer_submarineJunctionBox_2; + private Item engineer_submarineJunctionBox_3; + private Reactor engineer_submarineReactor; + + // Variables + private string radioSpeakerName; + private Character engineer; + private int[] reactorLoads = new int[5] { 1500, 3000, 2000, 5000, 3500 }; + private float reactorLoadChangeTime = 2f; + private float reactorLoadError = 200f; + private bool reactorOperatedProperly; + private const float waterVolumeBeforeOpening = 15f; + private Sprite engineer_repairIcon; + private Color engineer_repairIconColor; + private Sprite engineer_reactorIcon; + private Color engineer_reactorIconColor; + private bool wiringActive = false; + + public EngineerTutorial(XElement element) : base(element) + { + + } + + public override void Start() + { + base.Start(); + + radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); + engineer = Character.Controlled; + + var toolbox = engineer.Inventory.FindItemByIdentifier("toolbox"); + toolbox.Unequip(engineer); + engineer.Inventory.RemoveItem(toolbox); + + var repairOrder = Order.PrefabList.Find(order => order.AITag == "repairsystems"); + engineer_repairIcon = repairOrder.SymbolSprite; + engineer_repairIconColor = repairOrder.Color; + + var reactorOrder = Order.PrefabList.Find(order => order.AITag == "operatereactor"); + engineer_reactorIcon = reactorOrder.SymbolSprite; + engineer_reactorIconColor = reactorOrder.Color; + + // Other tutorial items + tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent(); + tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent(); + tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); + + tutorial_submarineSteering.CanBeSelected = false; + foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components) + { + ic.CanBeSelected = false; + } + + SetDoorAccess(null, tutorial_securityFinalDoorLight, false); + SetDoorAccess(null, tutorial_mechanicFinalDoorLight, false); + + // Room 2 + engineer_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_equipmentobjectivesensor")).GetComponent(); + engineer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("engineer_equipmentcabinet")).GetComponent(); + engineer_firstDoor = Item.ItemList.Find(i => i.HasTag("engineer_firstdoor")).GetComponent(); + engineer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_firstdoorlight")).GetComponent(); + + SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, false); + + // Room 3 + engineer_reactorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_reactorobjectivesensor")).GetComponent(); + tutorial_oxygenGenerator = Item.ItemList.Find(i => i.HasTag("tutorial_oxygengenerator")).GetComponent(); + engineer_reactor = Item.ItemList.Find(i => i.HasTag("engineer_reactor")).GetComponent(); + engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity; + engineer_reactor.FuelConsumptionRate = 0.0f; + engineer_reactor.OnOffSwitch.BarScroll = 1f; + reactorOperatedProperly = false; + + engineer_secondDoor = Item.ItemList.Find(i => i.HasTag("engineer_seconddoor")).GetComponent(); ; + engineer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_seconddoorlight")).GetComponent(); + + SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, false); + + // Room 4 + engineer_repairJunctionBoxObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_repairjunctionboxobjectivesensor")).GetComponent(); + engineer_brokenJunctionBox = Item.ItemList.Find(i => i.HasTag("engineer_brokenjunctionbox")); + engineer_thirdDoor = Item.ItemList.Find(i => i.HasTag("engineer_thirddoor")).GetComponent(); + engineer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_thirddoorlight")).GetComponent(); + + engineer_brokenJunctionBox.Indestructible = false; + engineer_brokenJunctionBox.Condition = 0f; + + SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, false); + + // Room 5 + engineer_disconnectedJunctionBoxObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_disconnectedjunctionboxobjectivesensor")).GetComponent(); + + engineer_disconnectedJunctionBoxes = new PowerTransfer[4]; + engineer_disconnectedConnectionPanels = new ConnectionPanel[4]; + + for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) + { + engineer_disconnectedJunctionBoxes[i] = Item.ItemList.Find(item => item.HasTag($"engineer_disconnectedjunctionbox_{i + 1}")).GetComponent(); + engineer_disconnectedConnectionPanels[i] = engineer_disconnectedJunctionBoxes[i].Item.GetComponent(); + engineer_disconnectedConnectionPanels[i].Locked = false; + + for (int j = 0; j < engineer_disconnectedJunctionBoxes[i].PowerConnections.Count; j++) + { + foreach (Wire wire in engineer_disconnectedJunctionBoxes[i].PowerConnections[j].Wires) + { + if (wire == null) continue; + wire.Locked = true; + } + } + } + + engineer_wire_1 = Item.ItemList.Find(i => i.HasTag("engineer_wire_1")); + engineer_wire_2 = Item.ItemList.Find(i => i.HasTag("engineer_wire_2")); + engineer_lamp_1 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_1")).GetComponent(); + engineer_lamp_2 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_2")).GetComponent(); + engineer_fourthDoor = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoor")).GetComponent(); + engineer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoorlight")).GetComponent(); + SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, false); + + // Room 6 + engineer_workingPump = Item.ItemList.Find(i => i.HasTag("engineer_workingpump")).GetComponent(); + engineer_workingPump.Item.CurrentHull.WaterVolume += engineer_workingPump.Item.CurrentHull.Volume; + engineer_workingPump.IsActive = true; + tutorial_lockedDoor_1 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_1")).GetComponent(); + SetDoorAccess(tutorial_lockedDoor_1, null, true); + + // Submarine + tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); + tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); + + tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent(); + engineer_submarineJunctionBox_1 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_1")); + engineer_submarineJunctionBox_2 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_2")); + engineer_submarineJunctionBox_3 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_3")); + engineer_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent(); + engineer_submarineReactor.IsActive = engineer_submarineReactor.AutoTemp = false; + + engineer_submarineJunctionBox_1.Indestructible = false; + engineer_submarineJunctionBox_1.Condition = 0f; + engineer_submarineJunctionBox_2.Indestructible = false; + engineer_submarineJunctionBox_2.Condition = 0f; + engineer_submarineJunctionBox_3.Indestructible = false; + engineer_submarineJunctionBox_3.Condition = 0f; + } + + public override IEnumerable UpdateState() + { + while (GameMain.Instance.LoadingScreenOpen) yield return null; + + // Room 1 + SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition); + while (shakeTimer > 0.0f) // Wake up, shake + { + shakeTimer -= 0.1f; + GameMain.GameScreen.Cam.Shake = shakeAmount; + yield return new WaitForSeconds(0.1f); + } + + //// Remove + //for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) + //{ + // SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, true); + //} + //do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump + //CheckGhostWires(); + //// Remove + + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.WakeUp"), ChatMessageType.Radio, null); + SetHighlight(engineer_equipmentCabinet.Item, true); + + // Room 2 + do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected); + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null); + yield return new WaitForSeconds(0.5f); + TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Deselect)); // Retrieve equipment + bool firstSlotRemoved = false; + bool secondSlotRemoved = false; + bool thirdSlotRemoved = false; + bool fourthSlotRemoved = false; + do + { + if (IsSelectedItem(engineer_equipmentCabinet.Item)) + { + if (!firstSlotRemoved) + { + HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f); + if (engineer_equipmentCabinet.Inventory.Items[0] == null) firstSlotRemoved = true; + } + + if (!secondSlotRemoved) + { + HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f); + if (engineer_equipmentCabinet.Inventory.Items[1] == null) secondSlotRemoved = true; + } + + if (!thirdSlotRemoved) + { + HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f); + if (engineer_equipmentCabinet.Inventory.Items[2] == null) thirdSlotRemoved = true; + } + + if (!fourthSlotRemoved) + { + HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 3, highlightColor, .5f, .5f, 0f); + if (engineer_equipmentCabinet.Inventory.Items[2] == null) fourthSlotRemoved = true; + } + + for (int i = 0; i < engineer.Inventory.slots.Length; i++) + { + if (engineer.Inventory.Items[i] == null) HighlightInventorySlot(engineer.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + + yield return null; + } while (!engineer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted + RemoveCompletedObjective(segments[0]); + SetHighlight(engineer_equipmentCabinet.Item, false); + SetHighlight(engineer_reactor.Item, true); + SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, true); + + // Room 3 + do { yield return null; } while (!IsSelectedItem(engineer_reactor.Item)); + yield return new WaitForSeconds(0.5f); + TriggerTutorialSegment(1); + do + { + if (IsSelectedItem(engineer_reactor.Item)) + { + engineer_reactor.AutoTempSlider.BarScrollValue = 1.0f; + if (engineer_reactor.OnOffSwitch.FlashTimer <= 0) + { + engineer_reactor.OnOffSwitch.Flash(highlightColor, 1.5f, false); + } + } + yield return null; + } while (engineer_reactor.OnOffSwitch.BarScroll > 0.45f); + do + { + if (IsSelectedItem(engineer_reactor.Item) && engineer_reactor.Item.OwnInventory.slots != null) + { + engineer_reactor.AutoTempSlider.BarScrollValue = 1.0f; + HighlightInventorySlot(engineer.Inventory, "fuelrod", highlightColor, 0.5f, 0.5f, 0f); + + for (int i = 0; i < engineer_reactor.Item.OwnInventory.slots.Length; i++) + { + HighlightInventorySlot(engineer_reactor.Item.OwnInventory, i, highlightColor, 0.5f, 0.5f, 0f); + } + } + yield return null; + } while (engineer_reactor.AvailableFuel == 0); + CoroutineManager.StartCoroutine(ReactorOperatedProperly()); + do + { + if (IsSelectedItem(engineer_reactor.Item)) + { + engineer_reactor.AutoTempSlider.BarScrollValue = 1.0f; + if (engineer_reactor.FissionRateScrollBar.FlashTimer <= 0) + { + engineer_reactor.FissionRateScrollBar.Flash(highlightColor, 1.5f); + } + + if (engineer_reactor.TurbineOutputScrollBar.FlashTimer <= 0) + { + engineer_reactor.TurbineOutputScrollBar.Flash(highlightColor, 1.5f); + } + } + yield return null; + } while (!reactorOperatedProperly); + yield return new WaitForSeconds(2f); + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.ReactorStable"), ChatMessageType.Radio, null); + do + { + if (IsSelectedItem(engineer_reactor.Item)) + { + if (engineer_reactor.AutoTempSlider.FlashTimer <= 0) + { + engineer_reactor.AutoTempSlider.Flash(highlightColor, 1.5f, false, new Vector2(10, 10)); + } + } + yield return null; + } while (!engineer_reactor.AutoTemp); + + float wait = 1.5f; + do + { + yield return new WaitForSeconds(0.1f); + wait -= 0.1f; + engineer_reactor.AutoTempSlider.BarScrollValue = 0.0f; + } while (wait > 0.0f); + engineer.SelectedConstruction = null; + engineer_reactor.CanBeSelected = false; + RemoveCompletedObjective(segments[1]); + SetHighlight(engineer_reactor.Item, false); + SetHighlight(engineer_brokenJunctionBox, true); + SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, true); + + // Room 4 + do { yield return null; } while (!engineer_secondDoor.IsOpen); + yield return new WaitForSeconds(1f); + TriggerTutorialSegment(2, GameMain.Config.KeyBind(InputType.Select)); // Repair the junction box + do { yield return null; } while (!engineer_brokenJunctionBox.IsFullCondition); // Wait until repaired + SetHighlight(engineer_brokenJunctionBox, false); + RemoveCompletedObjective(segments[2]); + SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true); + for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) + { + SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, true); + } + + // Room 5 + do { yield return null; } while (!engineer_thirdDoor.IsOpen); + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.FaultyWiring"), ChatMessageType.Radio, null); + yield return new WaitForSeconds(2f); + TriggerTutorialSegment(3, GameMain.Config.KeyBind(InputType.Use), GameMain.Config.KeyBind(InputType.Deselect)); // Connect the junction boxes + do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump + CheckGhostWires(); + for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) + { + SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, false); + } + RemoveCompletedObjective(segments[3]); + do { yield return null; } while (engineer_workingPump.Item.CurrentHull.WaterPercentage > waterVolumeBeforeOpening); // Wait until drained + wiringActive = false; + SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, true); + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.ChangeOfPlans"), ChatMessageType.Radio, null); + + // Submarine + do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected); + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Submarine"), ChatMessageType.Radio, null); + yield return new WaitForSeconds(2f); + TriggerTutorialSegment(4); // Repair junction box + while (ContentRunning) yield return null; + SetHighlight(engineer_submarineJunctionBox_1, true); + SetHighlight(engineer_submarineJunctionBox_2, true); + SetHighlight(engineer_submarineJunctionBox_3, true); + engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_1, engineer_repairIcon, engineer_repairIconColor); + engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_2, engineer_repairIcon, engineer_repairIconColor); + engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_3, engineer_repairIcon, engineer_repairIconColor); + // Remove highlights when each individual machine is repaired + do { CheckJunctionBoxHighlights(); yield return null; } while (!engineer_submarineJunctionBox_1.IsFullCondition || !engineer_submarineJunctionBox_2.IsFullCondition || !engineer_submarineJunctionBox_3.IsFullCondition); + CheckJunctionBoxHighlights(); + RemoveCompletedObjective(segments[4]); + TriggerTutorialSegment(5); // Powerup reactor + SetHighlight(engineer_submarineReactor.Item, true); + engineer.AddActiveObjectiveEntity(engineer_submarineReactor.Item, engineer_reactorIcon, engineer_reactorIconColor); + do { yield return null; } while (!IsReactorPoweredUp(engineer_submarineReactor)); // Wait until ~matches load + engineer.RemoveActiveObjectiveEntity(engineer_submarineReactor.Item); + SetHighlight(engineer_submarineReactor.Item, false); + RemoveCompletedObjective(segments[5]); + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Complete"), ChatMessageType.Radio, null); + + CoroutineManager.StartCoroutine(TutorialCompleted()); + } + + public override void Update(float deltaTime) + { + base.Update(deltaTime); + + if (wiringActive) + { + for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) + { + for (int j = 0; j < engineer_disconnectedJunctionBoxes[i].PowerConnections.Count; j++) + { + engineer_disconnectedJunctionBoxes[i].PowerConnections[j].UpdateFlashTimer(deltaTime); + } + } + } + } + + private bool IsSelectedItem(Item item) + { + return engineer?.SelectedConstruction == item; + } + + private IEnumerable ReactorOperatedProperly() + { + float timer; + + for (int i = 0; i < reactorLoads.Length; i++) + { + timer = reactorLoadChangeTime; + tutorial_oxygenGenerator.PowerConsumption = reactorLoads[i]; + while (timer > 0) + { + yield return new WaitForSeconds(0.1f); + if (IsReactorPoweredUp(engineer_reactor)) + { + timer -= 0.1f; + } + } + } + + reactorOperatedProperly = true; + } + + private void CheckGhostWires() + { + if (engineer_wire_1 != null && engineer_lamp_1.Voltage > engineer_lamp_1.MinVoltage) + { + engineer_wire_1.Remove(); + engineer_wire_1 = null; + } + + if (engineer_wire_2 != null && engineer_lamp_2.Voltage > engineer_lamp_2.MinVoltage) + { + engineer_wire_2.Remove(); + engineer_wire_2 = null; + } + } + + private void HandleJunctionBoxWiringHighlights() + { + Item selected = engineer.SelectedConstruction; + + if (!engineer.HasEquippedItem("screwdriver")) + { + HighlightInventorySlot(engineer.Inventory, "screwdriver", highlightColor, 0.5f, 0.5f, 0f); + } + + int selectedIndex = -1; + + if (selected != null) + { + for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) + { + if (selected == engineer_disconnectedJunctionBoxes[i].Item) + { + selectedIndex = i; + break; + } + } + } + + wiringActive = selectedIndex != -1; + + if (!engineer.HasEquippedItem("wire")) + { + HighlightInventorySlotWithTag(engineer.Inventory, "wire", highlightColor, 0.5f, 0.5f, 0f); + } + else + { + if (!wiringActive) return; + for (int i = 0; i < engineer_disconnectedConnectionPanels[selectedIndex].Connections.Count; i++) + { + var connection = engineer_disconnectedConnectionPanels[selectedIndex].Connections[i]; + if (connection.IsPower && connection.FlashTimer <= 0) + { + foreach (Wire wire in engineer_disconnectedConnectionPanels[selectedIndex].Connections[i].Wires) + { + if (wire == null) continue; + if (!wire.Locked) + { + return; + } + } + + connection.Flash(highlightColor); + } + } + } + } + + private void CheckJunctionBoxHighlights() + { + if (engineer_submarineJunctionBox_1.IsFullCondition && engineer_submarineJunctionBox_1.ExternalHighlight) + { + SetHighlight(engineer_submarineJunctionBox_1, false); + engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_1); + } + if (engineer_submarineJunctionBox_2.IsFullCondition && engineer_submarineJunctionBox_2.ExternalHighlight) + { + SetHighlight(engineer_submarineJunctionBox_2, false); + engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_2); + } + if (engineer_submarineJunctionBox_3.IsFullCondition && engineer_submarineJunctionBox_3.ExternalHighlight) + { + SetHighlight(engineer_submarineJunctionBox_3, false); + engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3); + } + } + + private bool IsReactorPoweredUp(Reactor reactor) + { + float load = 0.0f; + List connections = reactor.Item.Connections; + if (connections != null && connections.Count > 0) + { + foreach (Connection connection in connections) + { + if (!connection.IsPower) continue; + foreach (Connection recipient in connection.Recipients) + { + if (!(recipient.Item is Item it)) continue; + + PowerTransfer pt = it.GetComponent(); + if (pt == null) continue; + + load = Math.Max(load, pt.PowerLoad); + } + } + } + + return Math.Abs(load + reactor.CurrPowerConsumption) < reactorLoadError; + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/MechanicTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/MechanicTutorial.cs new file mode 100644 index 000000000..ec272ba98 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/MechanicTutorial.cs @@ -0,0 +1,592 @@ +using System.Collections.Generic; +using System.Xml.Linq; +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; + +namespace Barotrauma.Tutorials +{ + class MechanicTutorial : ScenarioTutorial + { + // Other tutorial items + private LightComponent tutorial_securityFinalDoorLight; + private Door tutorial_upperFinalDoor; + private Steering tutorial_submarineSteering; + + // Room 1 + private float shakeTimer = 1f; + private float shakeAmount = 20f; + private Door mechanic_firstDoor; + private LightComponent mechanic_firstDoorLight; + + // Room 2 + private MotionSensor mechanic_equipmentObjectiveSensor; + private ItemContainer mechanic_equipmentCabinet; + private Door mechanic_secondDoor; + private LightComponent mechanic_secondDoorLight; + + // Room 3 + private MotionSensor mechanic_weldingObjectiveSensor; + private Pump mechanic_workingPump; + private Door mechanic_thirdDoor; + private LightComponent mechanic_thirdDoorLight; + private Structure mechanic_brokenWall_1; + private Hull mechanic_brokenhull_1; + private MotionSensor mechanic_ladderSensor; + + // Room 4 + private MotionSensor mechanic_craftingObjectiveSensor; + private Deconstructor mechanic_deconstructor; + private Fabricator mechanic_fabricator; + private ItemContainer mechanic_craftingCabinet; + private Door mechanic_fourthDoor; + private LightComponent mechanic_fourthDoorLight; + + // Room 5 + private MotionSensor mechanic_fireSensor; + private DummyFireSource mechanic_fire; + private Door mechanic_fifthDoor; + private LightComponent mechanic_fifthDoorLight; + + // Room 6 + private MotionSensor mechanic_divingSuitObjectiveSensor; + private ItemContainer mechanic_divingSuitContainer; + private ItemContainer mechanic_oxygenContainer; + private Door tutorial_mechanicFinalDoor; + private LightComponent tutorial_mechanicFinalDoorLight; + + // Room 7 + private Pump mechanic_brokenPump; + private Structure mechanic_brokenWall_2; + private Hull mechanic_brokenhull_2; + private Door tutorial_submarineDoor; + private LightComponent tutorial_submarineDoorLight; + + // Submarine + private MotionSensor tutorial_enteredSubmarineSensor; + private Engine mechanic_submarineEngine; + private Pump mechanic_ballastPump_1; + private Pump mechanic_ballastPump_2; + + // Variables + private const float waterVolumeBeforeOpening = 15f; + private string radioSpeakerName; + private Character mechanic; + private Sprite mechanic_repairIcon; + private Color mechanic_repairIconColor; + + public MechanicTutorial(XElement element) : base(element) + { + + } + + public override void Start() + { + base.Start(); + + radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); + mechanic = Character.Controlled; + + var toolbox = mechanic.Inventory.FindItemByIdentifier("toolbox"); + toolbox.Unequip(mechanic); + mechanic.Inventory.RemoveItem(toolbox); + + var crowbar = mechanic.Inventory.FindItemByIdentifier("crowbar"); + crowbar.Unequip(mechanic); + mechanic.Inventory.RemoveItem(crowbar); + + var repairOrder = Order.PrefabList.Find(order => order.AITag == "repairsystems"); + mechanic_repairIcon = repairOrder.SymbolSprite; + mechanic_repairIconColor = repairOrder.Color; + + // Other tutorial items + tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent(); + tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent(); + tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); + + tutorial_submarineSteering.CanBeSelected = false; + foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components) + { + ic.CanBeSelected = false; + } + + SetDoorAccess(null, tutorial_securityFinalDoorLight, false); + SetDoorAccess(tutorial_upperFinalDoor, null, false); + + // Room 1 + mechanic_firstDoor = Item.ItemList.Find(i => i.HasTag("mechanic_firstdoor")).GetComponent(); + mechanic_firstDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_firstdoorlight")).GetComponent(); + + SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, false); + + // Room 2 + mechanic_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentobjectivesensor")).GetComponent(); + mechanic_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentcabinet")).GetComponent(); + mechanic_secondDoor = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoor")).GetComponent(); + mechanic_secondDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoorlight")).GetComponent(); + + SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, false); + + // Room 3 + mechanic_weldingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_weldingobjectivesensor")).GetComponent(); + mechanic_workingPump = Item.ItemList.Find(i => i.HasTag("mechanic_workingpump")).GetComponent(); + mechanic_thirdDoor = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoor")).GetComponent(); + mechanic_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoorlight")).GetComponent(); + mechanic_brokenWall_1 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_1"); + //mechanic_ladderSensor = Item.ItemList.Find(i => i.HasTag("mechanic_laddersensor")).GetComponent(); + + SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, false); + mechanic_brokenWall_1.Indestructible = false; + mechanic_brokenWall_1.SpriteColor = Color.White; + for (int i = 0; i < mechanic_brokenWall_1.SectionCount; i++) + { + mechanic_brokenWall_1.AddDamage(i, 165); + } + mechanic_brokenhull_1 = mechanic_brokenWall_1.Sections[0].gap.FlowTargetHull; + + // Room 4 + mechanic_craftingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_craftingobjectivesensor")).GetComponent(); + mechanic_deconstructor = Item.ItemList.Find(i => i.HasTag("mechanic_deconstructor")).GetComponent(); + mechanic_fabricator = Item.ItemList.Find(i => i.HasTag("mechanic_fabricator")).GetComponent(); + mechanic_craftingCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_craftingcabinet")).GetComponent(); + mechanic_fourthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoor")).GetComponent(); + mechanic_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoorlight")).GetComponent(); + + SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, false); + + // Room 5 + mechanic_fifthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoor")).GetComponent(); + mechanic_fifthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoorlight")).GetComponent(); + mechanic_fireSensor = Item.ItemList.Find(i => i.HasTag("mechanic_firesensor")).GetComponent(); + + SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, false); + + // Room 6 + mechanic_divingSuitObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitobjectivesensor")).GetComponent(); + mechanic_divingSuitContainer = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitcontainer")).GetComponent(); + for (int i = 0; i < mechanic_divingSuitContainer.Inventory.Items.Length; i++) + { + foreach (ItemComponent ic in mechanic_divingSuitContainer.Inventory.Items[i].Components) + { + ic.CanBePicked = true; + } + } + mechanic_oxygenContainer = Item.ItemList.Find(i => i.HasTag("mechanic_oxygencontainer")).GetComponent(); + for (int i = 0; i < mechanic_oxygenContainer.Inventory.Items.Length; i++) + { + foreach (ItemComponent ic in mechanic_oxygenContainer.Inventory.Items[i].Components) + { + ic.CanBePicked = true; + } + } + tutorial_mechanicFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoor")).GetComponent(); + tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent(); + + SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, false); + + // Room 7 + mechanic_brokenPump = Item.ItemList.Find(i => i.HasTag("mechanic_brokenpump")).GetComponent(); + mechanic_brokenPump.Item.Indestructible = false; + mechanic_brokenPump.Item.Condition = 0; + mechanic_brokenWall_2 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_2"); + tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); + tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); + + mechanic_brokenWall_2.Indestructible = false; + mechanic_brokenWall_2.SpriteColor = Color.White; + for (int i = 0; i < mechanic_brokenWall_2.SectionCount; i++) + { + mechanic_brokenWall_2.AddDamage(i, 165); + } + mechanic_brokenhull_2 = mechanic_brokenWall_2.Sections[0].gap.FlowTargetHull; + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false); + + // Submarine + tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent(); + mechanic_submarineEngine = Item.ItemList.Find(i => i.HasTag("mechanic_submarineengine")).GetComponent(); + mechanic_submarineEngine.Item.Indestructible = false; + mechanic_submarineEngine.Item.Condition = 0f; + mechanic_ballastPump_1 = Item.ItemList.Find(i => i.HasTag("mechanic_ballastpump_1")).GetComponent(); + mechanic_ballastPump_1.Item.Indestructible = false; + mechanic_ballastPump_1.Item.Condition = 0f; + mechanic_ballastPump_2 = Item.ItemList.Find(i => i.HasTag("mechanic_ballastpump_2")).GetComponent(); + mechanic_ballastPump_2.Item.Indestructible = false; + mechanic_ballastPump_2.Item.Condition = 0f; + } + + public override void Update(float deltaTime) + { + mechanic_brokenhull_1.WaterVolume = MathHelper.Clamp(mechanic_brokenhull_1.WaterVolume, 0, mechanic_brokenhull_1.Volume * 0.85f); + base.Update(deltaTime); + } + + public override IEnumerable UpdateState() + { + while (GameMain.Instance.LoadingScreenOpen) yield return null; + + // Room 1 + SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition); + while (shakeTimer > 0.0f) // Wake up, shake + { + shakeTimer -= 0.1f; + GameMain.GameScreen.Cam.Shake = shakeAmount; + yield return new WaitForSeconds(0.1f); + } + yield return new WaitForSeconds(2.5f); + + mechanic_fabricator.RemoveFabricationRecipes(new List() { "extinguisher", "wrench", "weldingtool", "weldingfuel", "divingmask", "railgunshell", "nuclearshell", "uex", "harpoongun" }); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null); + + yield return new WaitForSeconds(2.5f); + TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Up), GameMain.Config.KeyBind(InputType.Left), GameMain.Config.KeyBind(InputType.Down), GameMain.Config.KeyBind(InputType.Right), GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Select)); // Open door objective + yield return new WaitForSeconds(0.0f); + SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, true); + SetHighlight(mechanic_firstDoor.Item, true); + do { yield return null; } while (!mechanic_firstDoor.IsOpen); + SetHighlight(mechanic_firstDoor.Item, false); + yield return new WaitForSeconds(1.5f); + RemoveCompletedObjective(segments[0]); + + // Room 2 + yield return new WaitForSeconds(0.0f); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null); + do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected); + TriggerTutorialSegment(1, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Deselect)); // Equipment & inventory objective + SetHighlight(mechanic_equipmentCabinet.Item, true); + bool firstSlotRemoved = false; + bool secondSlotRemoved = false; + bool thirdSlotRemoved = false; + do + { + if (IsSelectedItem(mechanic_equipmentCabinet.Item)) + { + if (!firstSlotRemoved) + { + HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f); + if (mechanic_equipmentCabinet.Inventory.Items[0] == null) firstSlotRemoved = true; + } + + if (!secondSlotRemoved) + { + HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f); + if (mechanic_equipmentCabinet.Inventory.Items[1] == null) secondSlotRemoved = true; + } + + if (!thirdSlotRemoved) + { + HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f); + if (mechanic_equipmentCabinet.Inventory.Items[2] == null) thirdSlotRemoved = true; + } + + for (int i = 0; i < mechanic.Inventory.slots.Length; i++) + { + if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + + yield return null; + } while (mechanic.Inventory.FindItemByIdentifier("divingmask") == null || mechanic.Inventory.FindItemByIdentifier("weldingtool") == null || mechanic.Inventory.FindItemByIdentifier("wrench") == null); // Wait until looted + SetHighlight(mechanic_equipmentCabinet.Item, false); + yield return new WaitForSeconds(1.5f); + RemoveCompletedObjective(segments[1]); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Breach"), ChatMessageType.Radio, null); + + // Room 3 + do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected); + TriggerTutorialSegment(2, GameMain.Config.KeyBind(InputType.Shoot), GameMain.Config.KeyBind(InputType.Aim)); // Welding objective + do + { + if (!mechanic.HasEquippedItem("divingmask")) + { + HighlightInventorySlot(mechanic.Inventory, "divingmask", highlightColor, .5f, .5f, 0f); + } + + if (!mechanic.HasEquippedItem("weldingtool")) + { + HighlightInventorySlot(mechanic.Inventory, "weldingtool", highlightColor, .5f, .5f, 0f); + } + yield return null; + } while (!mechanic.HasEquippedItem("divingmask") || !mechanic.HasEquippedItem("weldingtool")); // Wait until equipped + SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, true); + mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_1, mechanic_repairIcon, mechanic_repairIconColor); + do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_1)); // Highlight until repaired + mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1); + RemoveCompletedObjective(segments[2]); + yield return new WaitForSeconds(1f); + TriggerTutorialSegment(3, GameMain.Config.KeyBind(InputType.Select)); // Pump objective + SetHighlight(mechanic_workingPump.Item, true); + do + { + yield return null; + if (IsSelectedItem(mechanic_brokenPump.Item)) + { + if (mechanic_workingPump.IsActiveSlider.FlashTimer <= 0) + { + mechanic_workingPump.IsActiveSlider.Flash(uiHighlightColor, 1.5f, true); + } + } + } while (mechanic_workingPump.FlowPercentage >= 0 || !mechanic_workingPump.IsActive); // Highlight until draining + SetHighlight(mechanic_workingPump.Item, false); + do { yield return null; } while (mechanic_brokenhull_1.WaterPercentage > waterVolumeBeforeOpening); // Unlock door once drained + RemoveCompletedObjective(segments[3]); + SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, true); + yield return new WaitForSeconds(1.5f); + //TriggerTutorialSegment(11, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Up), GameMain.Config.KeyBind(InputType.Down), GameMain.Config.KeyBind(InputType.Select)); // Ladder objective + //do { yield return null; } while (!mechanic_ladderSensor.MotionDetected); + //RemoveCompletedObjective(segments[11]); + yield return new WaitForSeconds(2f); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.News"), ChatMessageType.Radio, null); + yield return new WaitForSeconds(1f); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Fire"), ChatMessageType.Radio, null); + yield return new WaitForSeconds(6f); + + // Room 4 + do { yield return null; } while (!mechanic_thirdDoor.IsOpen); + mechanic_fire = new DummyFireSource(new Vector2(20f, 2f), Item.ItemList.Find(i => i.HasTag("mechanic_fire")).WorldPosition); + //do { yield return null; } while (!mechanic_craftingObjectiveSensor.MotionDetected); + TriggerTutorialSegment(4); // Deconstruct + + SetHighlight(mechanic_craftingCabinet.Item, true); + do + { + for (int i = 0; i < mechanic_craftingCabinet.Inventory.Items.Length; i++) + { + if (mechanic_craftingCabinet.Inventory.Items[i] != null) + { + HighlightInventorySlot(mechanic_craftingCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + if (mechanic.SelectedConstruction == mechanic_craftingCabinet.Item) + { + for (int i = 0; i < mechanic.Inventory.slots.Length; i++) + { + if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + yield return null; + } while (mechanic.Inventory.FindItemByIdentifier("oxygentank") == null || mechanic.Inventory.FindItemByIdentifier("sodium") == null); // Wait until looted + yield return new WaitForSeconds(1.0f); + SetHighlight(mechanic_craftingCabinet.Item, false); + + SetHighlight(mechanic_deconstructor.Item, true); + + do + { + if (IsSelectedItem(mechanic_deconstructor.Item)) + { + if (mechanic.Inventory.FindItemByIdentifier("oxygentank") != null) + { + HighlightInventorySlot(mechanic.Inventory, "oxygentank", highlightColor, .5f, .5f, 0f); + + if (mechanic_deconstructor.InputContainer.Inventory.slots != null) + { + for (int i = 0; i < mechanic_deconstructor.InputContainer.Inventory.slots.Length; i++) + { + HighlightInventorySlot(mechanic_deconstructor.InputContainer.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + } + + if (mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank") != null && !mechanic_deconstructor.IsActive) + { + if (mechanic_deconstructor.ActivateButton.Frame.FlashTimer <= 0) + { + mechanic_deconstructor.ActivateButton.Frame.Flash(highlightColor, 1.5f, false); + } + } + + if (mechanic_deconstructor.OutputContainer.Inventory.FindItemByIdentifier("aluminium") != null) + { + HighlightInventorySlot(mechanic_deconstructor.OutputContainer.Inventory, "aluminium", highlightColor, .5f, .5f, 0f); + + for (int i = 0; i < mechanic.Inventory.slots.Length; i++) + { + if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + } + yield return null; + } while (mechanic.Inventory.FindItemByIdentifier("aluminium") == null); // Wait until deconstructed + + SetHighlight(mechanic_deconstructor.Item, false); + RemoveCompletedObjective(segments[4]); + yield return new WaitForSeconds(1f); + TriggerTutorialSegment(5); // Fabricate + SetHighlight(mechanic_fabricator.Item, true); + do + { + if (IsSelectedItem(mechanic_fabricator.Item)) + { + if (mechanic_fabricator.SelectedItem?.TargetItem.Identifier != "extinguisher") + { + mechanic_fabricator.HighlightRecipe("extinguisher", highlightColor); + } + else + { + if (mechanic_fabricator.OutputContainer.Inventory.FindItemByIdentifier("extinguisher") != null) + { + HighlightInventorySlot(mechanic_fabricator.OutputContainer.Inventory, "extinguisher", highlightColor, .5f, .5f, 0f); + + for (int i = 0; i < mechanic.Inventory.slots.Length; i++) + { + if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + else if (mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium") != null && mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("sodium") != null && !mechanic_fabricator.IsActive) + { + if (mechanic_fabricator.ActivateButton.Frame.FlashTimer <= 0) + { + mechanic_fabricator.ActivateButton.Frame.Flash(highlightColor, 1.5f, false); + } + } + else if (mechanic.Inventory.FindItemByIdentifier("aluminium") != null || mechanic.Inventory.FindItemByIdentifier("sodium") != null) + { + HighlightInventorySlot(mechanic.Inventory, "aluminium", highlightColor, .5f, .5f, 0f); + HighlightInventorySlot(mechanic.Inventory, "sodium", highlightColor, .5f, .5f, 0f); + + if (mechanic_fabricator.InputContainer.Inventory.Items[0] == null) + { + HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 0, highlightColor, .5f, .5f, 0f); + } + + if (mechanic_fabricator.InputContainer.Inventory.Items[1] == null) + { + HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 1, highlightColor, .5f, .5f, 0f); + } + } + } + } + yield return null; + } while (mechanic.Inventory.FindItemByIdentifier("extinguisher") == null); // Wait until extinguisher is created + RemoveCompletedObjective(segments[5]); + SetHighlight(mechanic_fabricator.Item, false); + SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, true); + + // Room 5 + do { yield return null; } while (!mechanic_fireSensor.MotionDetected); + TriggerTutorialSegment(6, GameMain.Config.KeyBind(InputType.Aim), GameMain.Config.KeyBind(InputType.Shoot)); // Using the extinguisher + do { yield return null; } while (!mechanic_fire.Removed); // Wait until extinguished + yield return new WaitForSeconds(3f); + RemoveCompletedObjective(segments[6]); + + if (mechanic.HasEquippedItem("extinguisher")) // do not trigger if dropped already + { + TriggerTutorialSegment(7); + do + { + HighlightInventorySlot(mechanic.Inventory, "extinguisher", highlightColor, 0.5f, 0.5f, 0f); + yield return null; + } while (mechanic.HasEquippedItem("extinguisher")); + RemoveCompletedObjective(segments[7]); + } + SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, true); + + // Room 6 + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Diving"), ChatMessageType.Radio, null); + do { yield return null; } while (!mechanic_divingSuitObjectiveSensor.MotionDetected); + TriggerTutorialSegment(8); // Dangers of pressure, equip diving suit objective + SetHighlight(mechanic_divingSuitContainer.Item, true); + do + { + if (IsSelectedItem(mechanic_divingSuitContainer.Item)) + { + if (mechanic_divingSuitContainer.Inventory.slots != null) + { + for (int i = 0; i < mechanic_divingSuitContainer.Inventory.slots.Length; i++) + { + HighlightInventorySlot(mechanic_divingSuitContainer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f); + } + } + } + yield return null; + } while (!mechanic.HasEquippedItem("divingsuit")); + SetHighlight(mechanic_divingSuitContainer.Item, false); + RemoveCompletedObjective(segments[8]); + SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, true); + + // Room 7 + mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_2, mechanic_repairIcon, mechanic_repairIconColor); + do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_2)); + mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_2); + TriggerTutorialSegment(9, GameMain.Config.KeyBind(InputType.Select)); // Repairing machinery (pump) + SetHighlight(mechanic_brokenPump.Item, true); + Repairable repairablePumpComponent = mechanic_brokenPump.Item.GetComponent(); + do + { + yield return null; + if (!mechanic_brokenPump.Item.IsFullCondition) + { + if (!mechanic.HasEquippedItem("wrench")) + { + HighlightInventorySlot(mechanic.Inventory, "wrench", highlightColor, 0.5f, 0.5f, 0f); + } + else if (IsSelectedItem(mechanic_brokenPump.Item) && repairablePumpComponent.CurrentFixer == null) + { + if (repairablePumpComponent.RepairButton.Frame.FlashTimer <= 0) + { + repairablePumpComponent.RepairButton.Frame.Flash(); + } + } + } + } while (!mechanic_brokenPump.Item.IsFullCondition || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive); + RemoveCompletedObjective(segments[9]); + SetHighlight(mechanic_brokenPump.Item, false); + do { yield return null; } while (mechanic_brokenhull_2.WaterPercentage > waterVolumeBeforeOpening); + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); + + // Submarine + do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Submarine"), ChatMessageType.Radio, null); + TriggerTutorialSegment(10); // Repairing ballast pumps, engine + while (ContentRunning) yield return null; + mechanic.AddActiveObjectiveEntity(mechanic_ballastPump_1.Item, mechanic_repairIcon, mechanic_repairIconColor); + mechanic.AddActiveObjectiveEntity(mechanic_ballastPump_2.Item, mechanic_repairIcon, mechanic_repairIconColor); + mechanic.AddActiveObjectiveEntity(mechanic_submarineEngine.Item, mechanic_repairIcon, mechanic_repairIconColor); + SetHighlight(mechanic_ballastPump_1.Item, true); + SetHighlight(mechanic_ballastPump_2.Item, true); + SetHighlight(mechanic_submarineEngine.Item, true); + // Remove highlights when each individual machine is repaired + do { CheckHighlights(); yield return null; } while (!mechanic_ballastPump_1.Item.IsFullCondition || !mechanic_ballastPump_2.Item.IsFullCondition || !mechanic_submarineEngine.Item.IsFullCondition); + CheckHighlights(); + RemoveCompletedObjective(segments[10]); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Complete"), ChatMessageType.Radio, null); + + // END TUTORIAL + CoroutineManager.StartCoroutine(TutorialCompleted()); + } + + private bool IsSelectedItem(Item item) + { + return mechanic?.SelectedConstruction == item; + } + + private bool WallHasDamagedSections(Structure wall) + { + for (int i = 0; i < wall.SectionCount; i++) + { + if (wall.Sections[i].damage > 0) return true; + } + + return false; + } + + private void CheckHighlights() + { + if (mechanic_ballastPump_1.Item.IsFullCondition && mechanic_ballastPump_1.Item.ExternalHighlight) + { + SetHighlight(mechanic_ballastPump_1.Item, false); + mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_1.Item); + } + if (mechanic_ballastPump_2.Item.IsFullCondition && mechanic_ballastPump_2.Item.ExternalHighlight) + { + SetHighlight(mechanic_ballastPump_2.Item, false); + mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_2.Item); + } + if (mechanic_submarineEngine.Item.IsFullCondition && mechanic_submarineEngine.Item.ExternalHighlight) + { + SetHighlight(mechanic_submarineEngine.Item, false); + mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item); + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/OfficerTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/OfficerTutorial.cs new file mode 100644 index 000000000..3feff2ddd --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/OfficerTutorial.cs @@ -0,0 +1,452 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml.Linq; +using System.Linq; +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; + +namespace Barotrauma.Tutorials +{ + class OfficerTutorial : ScenarioTutorial + { + // Other tutorial items + private LightComponent tutorial_mechanicFinalDoorLight; + private Steering tutorial_submarineSteering; + + // Room 1 + private float shakeTimer = 1f; + private float shakeAmount = 20f; + + // Room 2 + private MotionSensor officer_equipmentObjectiveSensor; + private ItemContainer officer_equipmentCabinet; + private Door officer_firstDoor; + private LightComponent officer_firstDoorLight; + + // Room 3 + private MotionSensor officer_crawlerSensor; + private Character officer_crawler; + private Vector2 officer_crawlerSpawnPos; + private Door officer_secondDoor; + private LightComponent officer_secondDoorLight; + + // Room 4 + private MotionSensor officer_somethingBigSensor; + private ItemContainer officer_coilgunLoader; + private ItemContainer officer_ammoShelf_1; + private ItemContainer officer_ammoShelf_2; + private PowerContainer officer_superCapacitor; + private Item officer_coilgunPeriscope; + private Character officer_hammerhead; + private Vector2 officer_hammerheadSpawnPos; + private Door officer_thirdDoor; + private LightComponent officer_thirdDoorLight; + + // Room 5 + private MotionSensor officer_rangedWeaponSensor; + private ItemContainer officer_rangedWeaponCabinet; + private ItemContainer officer_rangedWeaponHolder; + private Door officer_fourthDoor; + private LightComponent officer_fourthDoorLight; + + // Room 6 + private MotionSensor officer_mudraptorObjectiveSensor; + private Vector2 officer_mudraptorSpawnPos; + private Character officer_mudraptor; + private Door tutorial_securityFinalDoor; + private LightComponent tutorial_securityFinalDoorLight; + + // Submarine + private Door tutorial_submarineDoor; + private LightComponent tutorial_submarineDoorLight; + private MotionSensor tutorial_enteredSubmarineSensor; + private Item officer_subAmmoBox_1; + private Item officer_subAmmoBox_2; + private ItemContainer officer_subAmmoShelf; + private ItemContainer officer_subLoader_1; + private ItemContainer officer_subLoader_2; + private PowerContainer officer_subSuperCapacitor_1; + private PowerContainer officer_subSuperCapacitor_2; + + // Variables + private string radioSpeakerName; + private Character officer; + private string crawlerCharacterFile; + private string hammerheadCharacterFile; + private string mudraptorCharacterFile; + private float superCapacitorRechargeRate = 10; + private Sprite officer_gunIcon; + private Color officer_gunIconColor; + + public OfficerTutorial(XElement element) : base(element) + { + crawlerCharacterFile = Character.GetConfigFile("crawler"); + hammerheadCharacterFile = Character.GetConfigFile("hammerhead"); + mudraptorCharacterFile = Character.GetConfigFile("mudraptor"); + } + + public override void Start() + { + base.Start(); + + radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker"); + officer = Character.Controlled; + + var handcuffs = officer.Inventory.FindItemByIdentifier("handcuffs"); + handcuffs.Unequip(officer); + officer.Inventory.RemoveItem(handcuffs); + + var stunbaton = officer.Inventory.FindItemByIdentifier("stunbaton"); + stunbaton.Unequip(officer); + officer.Inventory.RemoveItem(stunbaton); + + var ballistichelmet = officer.Inventory.FindItemByIdentifier("ballistichelmet"); + ballistichelmet.Unequip(officer); + officer.Inventory.RemoveItem(ballistichelmet); + + var bodyarmor = officer.Inventory.FindItemByIdentifier("bodyarmor"); + bodyarmor.Unequip(officer); + officer.Inventory.RemoveItem(bodyarmor); + + var gunOrder = Order.PrefabList.Find(order => order.AITag == "operateweapons"); + officer_gunIcon = gunOrder.SymbolSprite; + officer_gunIconColor = gunOrder.Color; + + // Other tutorial items + tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent(); + tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent(); + + tutorial_submarineSteering.CanBeSelected = false; + foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components) + { + ic.CanBeSelected = false; + } + + SetDoorAccess(null, tutorial_mechanicFinalDoorLight, false); + + // Room 2 + officer_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("officer_equipmentobjectivesensor")).GetComponent(); + officer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("officer_equipmentcabinet")).GetComponent(); + officer_firstDoor = Item.ItemList.Find(i => i.HasTag("officer_firstdoor")).GetComponent(); + officer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("officer_firstdoorlight")).GetComponent(); + + SetDoorAccess(officer_firstDoor, officer_firstDoorLight, false); + + // Room 3 + officer_crawlerSensor = Item.ItemList.Find(i => i.HasTag("officer_crawlerobjectivesensor")).GetComponent(); + officer_crawlerSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_crawlerspawn")).WorldPosition; + officer_secondDoor = Item.ItemList.Find(i => i.HasTag("officer_seconddoor")).GetComponent(); + officer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("officer_seconddoorlight")).GetComponent(); + + SetDoorAccess(officer_secondDoor, officer_secondDoorLight, false); + + // Room 4 + officer_somethingBigSensor = Item.ItemList.Find(i => i.HasTag("officer_somethingbigobjectivesensor")).GetComponent(); + officer_coilgunLoader = Item.ItemList.Find(i => i.HasTag("officer_coilgunloader")).GetComponent(); + officer_superCapacitor = Item.ItemList.Find(i => i.HasTag("officer_supercapacitor")).GetComponent(); + officer_coilgunPeriscope = Item.ItemList.Find(i => i.HasTag("officer_coilgunperiscope")); + officer_hammerheadSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_hammerheadspawn")).WorldPosition; + officer_thirdDoor = Item.ItemList.Find(i => i.HasTag("officer_thirddoor")).GetComponent(); + officer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("officer_thirddoorlight")).GetComponent(); + officer_ammoShelf_1 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_1")).GetComponent(); + officer_ammoShelf_2 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_2")).GetComponent(); + + SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, false); + + // Room 5 + officer_rangedWeaponSensor = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponobjectivesensor")).GetComponent(); + officer_rangedWeaponCabinet = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponcabinet")).GetComponent(); + officer_rangedWeaponHolder = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponholder")).GetComponent(); + officer_fourthDoor = Item.ItemList.Find(i => i.HasTag("officer_fourthdoor")).GetComponent(); + officer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("officer_fourthdoorlight")).GetComponent(); + + SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, false); + + // Room 6 + officer_mudraptorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("officer_mudraptorobjectivesensor")).GetComponent(); + officer_mudraptorSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_mudraptorspawn")).WorldPosition; + tutorial_securityFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoor")).GetComponent(); + tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent(); + + SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, false); + + // Submarine + tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); + tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); + tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent(); + officer_subAmmoBox_1 = Item.ItemList.Find(i => i.HasTag("officer_subammobox_1")); + officer_subAmmoBox_2 = Item.ItemList.Find(i => i.HasTag("officer_subammobox_2")); + officer_subLoader_1 = Item.ItemList.Find(i => i.HasTag("officer_subloader_1")).GetComponent(); + officer_subLoader_2 = Item.ItemList.Find(i => i.HasTag("officer_subloader_2")).GetComponent(); + officer_subSuperCapacitor_1 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_1")).GetComponent(); + officer_subSuperCapacitor_2 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_2")).GetComponent(); + officer_subAmmoShelf = Item.ItemList.Find(i => i.HasTag("officer_subammoshelf")).GetComponent(); + SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); + } + + public override IEnumerable UpdateState() + { + while (GameMain.Instance.LoadingScreenOpen) yield return null; + + // Room 1 + SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition); + while (shakeTimer > 0.0f) // Wake up, shake + { + shakeTimer -= 0.1f; + GameMain.GameScreen.Cam.Shake = shakeAmount; + yield return new WaitForSeconds(0.1f); + } + + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.WakeUp"), ChatMessageType.Radio, null); + + // Room 2 + do { yield return null; } while (!officer_equipmentObjectiveSensor.MotionDetected); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Equipment"), ChatMessageType.Radio, null); + yield return new WaitForSeconds(3f); + //TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Deselect)); // Retrieve equipment + SetHighlight(officer_equipmentCabinet.Item, true); + bool firstSlotRemoved = false; + bool secondSlotRemoved = false; + bool thirdSlotRemoved = false; + do + { + if (IsSelectedItem(officer_equipmentCabinet.Item)) + { + if (!firstSlotRemoved) + { + HighlightInventorySlot(officer_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f); + if (officer_equipmentCabinet.Inventory.Items[0] == null) firstSlotRemoved = true; + } + + if (!secondSlotRemoved) + { + HighlightInventorySlot(officer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f); + if (officer_equipmentCabinet.Inventory.Items[1] == null) secondSlotRemoved = true; + } + + if (!thirdSlotRemoved) + { + HighlightInventorySlot(officer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f); + if (officer_equipmentCabinet.Inventory.Items[2] == null) thirdSlotRemoved = true; + } + + for (int i = 0; i < officer.Inventory.slots.Length; i++) + { + if (officer.Inventory.Items[i] == null) HighlightInventorySlot(officer.Inventory, i, highlightColor, .5f, .5f, 0f); + } + } + + yield return null; + } while (!officer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted + //RemoveCompletedObjective(segments[0]); + SetHighlight(officer_equipmentCabinet.Item, false); + do { yield return null; } while (IsSelectedItem(officer_equipmentCabinet.Item)); + TriggerTutorialSegment(1, GameMain.Config.KeyBind(InputType.Aim), GameMain.Config.KeyBind(InputType.Shoot)); // Equip melee weapon & armor + do + { + if (!officer.HasEquippedItem("stunbaton")) + { + HighlightInventorySlot(officer.Inventory, "stunbaton", highlightColor, .5f, .5f, 0f); + } + if (!officer.HasEquippedItem("bodyarmor")) + { + HighlightInventorySlot(officer.Inventory, "bodyarmor", highlightColor, .5f, .5f, 0f); + } + if (!officer.HasEquippedItem("ballistichelmet")) + { + HighlightInventorySlot(officer.Inventory, "ballistichelmet", highlightColor, .5f, .5f, 0f); + } + yield return new WaitForSeconds(1f); + } while (!officer.HasEquippedItem("stunbaton") || !officer.HasEquippedItem("bodyarmor") || !officer.HasEquippedItem("ballistichelmet")); + RemoveCompletedObjective(segments[1]); + SetDoorAccess(officer_firstDoor, officer_firstDoorLight, true); + + // Room 3 + do { yield return null; } while (!officer_crawlerSensor.MotionDetected); + TriggerTutorialSegment(2); + officer_crawler = SpawnMonster(crawlerCharacterFile, officer_crawlerSpawnPos); + do { yield return null; } while (!officer_crawler.IsDead); + RemoveCompletedObjective(segments[2]); + Heal(officer); + yield return new WaitForSeconds(1f); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.CrawlerDead"), ChatMessageType.Radio, null); + SetDoorAccess(officer_secondDoor, officer_secondDoorLight, true); + + // Room 4 + do { yield return null; } while (!officer_somethingBigSensor.MotionDetected); + TriggerTutorialSegment(3); // Arm railgun + do + { + SetHighlight(officer_coilgunLoader.Item, officer_coilgunLoader.Inventory.Items[0] == null || officer_coilgunLoader.Inventory.Items[0].Condition == 0); + HighlightInventorySlot(officer_coilgunLoader.Inventory, 0, highlightColor, .5f, .5f, 0f); + SetHighlight(officer_superCapacitor.Item, officer_superCapacitor.RechargeSpeed < superCapacitorRechargeRate); + SetHighlight(officer_ammoShelf_1.Item, officer_coilgunLoader.Item.ExternalHighlight ); + SetHighlight(officer_ammoShelf_2.Item, officer_coilgunLoader.Item.ExternalHighlight ); + if (IsSelectedItem(officer_coilgunLoader.Item)) + { + HighlightInventorySlot(officer.Inventory, "coilgunammobox", highlightColor, .5f, .5f, 0f); + } + yield return null; + } while (officer_coilgunLoader.Inventory.Items[0] == null || officer_superCapacitor.RechargeSpeed < superCapacitorRechargeRate || officer_coilgunLoader.Inventory.Items[0].Condition == 0); + SetHighlight(officer_coilgunLoader.Item, false); + SetHighlight(officer_superCapacitor.Item, false); + SetHighlight(officer_ammoShelf_1.Item, false); + SetHighlight(officer_ammoShelf_2.Item, false); + RemoveCompletedObjective(segments[3]); + yield return new WaitForSeconds(2f); + TriggerTutorialSegment(4, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Shoot), GameMain.Config.KeyBind(InputType.Deselect)); // Kill hammerhead + officer_hammerhead = SpawnMonster(hammerheadCharacterFile, officer_hammerheadSpawnPos); + officer_hammerhead.AIController.SelectTarget(officer.AiTarget); + SetHighlight(officer_coilgunPeriscope, true); + float originalDistance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerheadSpawnPos); + do + { + float distance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerhead.WorldPosition); + if (distance > originalDistance) + { + // Don't let the Hammerhead go too far from the periscope. + officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos); + } + yield return null; + } + while(!officer_hammerhead.IsDead); + Heal(officer); + SetHighlight(officer_coilgunPeriscope, false); + RemoveCompletedObjective(segments[4]); + yield return new WaitForSeconds(1f); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.HammerheadDead"), ChatMessageType.Radio, null); + SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, true); + + // Room 5 + //do { yield return null; } while (!officer_rangedWeaponSensor.MotionDetected); + do { yield return null; } while (!officer_thirdDoor.IsOpen); + yield return new WaitForSeconds(3f); + TriggerTutorialSegment(5, GameMain.Config.KeyBind(InputType.Aim), GameMain.Config.KeyBind(InputType.Shoot)); // Ranged weapons + SetHighlight(officer_rangedWeaponHolder.Item, true); + do { yield return null; } while (!officer_rangedWeaponHolder.Inventory.IsEmpty()); // Wait until looted + SetHighlight(officer_rangedWeaponHolder.Item, false); + do + { + HighlightInventorySlot(officer.Inventory, "harpoongun", highlightColor, 0.5f, 0.5f, 0f); + yield return null; + } while (!officer.HasEquippedItem("harpoongun")); // Wait until equipped + ItemContainer harpoonGunChamber = officer.Inventory.FindItemByIdentifier("harpoongun").GetComponent(); + SetHighlight(officer_rangedWeaponCabinet.Item, true); + do + { + if (IsSelectedItem(officer_rangedWeaponCabinet.Item)) + { + if (officer_rangedWeaponCabinet.Inventory.slots != null) + { + for (int i = 0; i < officer_rangedWeaponCabinet.Inventory.Items.Length; i++) + { + if (officer_rangedWeaponCabinet.Inventory.Items[i] == null) continue; + if (officer_rangedWeaponCabinet.Inventory.Items[i].Prefab.Identifier == "spear") + { + HighlightInventorySlot(officer_rangedWeaponCabinet.Inventory, i, highlightColor, 0.5f, 0.5f, 0f); + } + } + } + } + + for (int i = 0; i < officer.Inventory.Items.Length; i++) + { + if (officer.Inventory.Items[i] == null) continue; + if (officer.Inventory.Items[i].Prefab.Identifier == "spear") + { + HighlightInventorySlot(officer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f); + } + } + + if (officer.Inventory.FindItemByIdentifier("spear") != null || (IsSelectedItem(officer_rangedWeaponCabinet.Item) && officer_rangedWeaponCabinet.Inventory.FindItemByIdentifier("spear") != null)) + { + HighlightInventorySlot(officer.Inventory, "harpoongun", highlightColor, 0.5f, 0.5f, 0f); + } + yield return null; + } while (!harpoonGunChamber.Inventory.IsFull()); // Wait until all five harpons loaded + RemoveCompletedObjective(segments[5]); + SetHighlight(officer_rangedWeaponCabinet.Item, false); + SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, true); + + // Room 6 + do { yield return null; } while (!officer_mudraptorObjectiveSensor.MotionDetected); + TriggerTutorialSegment(6); + officer_mudraptor = SpawnMonster(mudraptorCharacterFile, officer_mudraptorSpawnPos); + do { yield return null; } while (!officer_mudraptor.IsDead); + Heal(officer); + RemoveCompletedObjective(segments[6]); + SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, true); + + // Submarine + do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected); + TriggerTutorialSegment(7); + while (ContentRunning) yield return null; + officer.AddActiveObjectiveEntity(officer_subAmmoBox_1, officer_gunIcon, officer_gunIconColor); + officer.AddActiveObjectiveEntity(officer_subAmmoBox_2, officer_gunIcon, officer_gunIconColor); + officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_1.Item, officer_gunIcon, officer_gunIconColor); + officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_1.Item, officer_gunIcon, officer_gunIconColor); + officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_2.Item, officer_gunIcon, officer_gunIconColor); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Submarine"), ChatMessageType.Radio, null); + do + { + SetHighlight(officer_subLoader_1.Item, officer_subLoader_1.Inventory.Items[0] == null || officer_subLoader_1.Inventory.Items[0].Condition == 0); + SetHighlight(officer_subLoader_2.Item, officer_subLoader_2.Inventory.Items[0] == null || officer_subLoader_2.Inventory.Items[0].Condition == 0); + HighlightInventorySlot(officer_subLoader_1.Inventory, 0, highlightColor, .5f, .5f, 0f); + HighlightInventorySlot(officer_subLoader_2.Inventory, 0, highlightColor, .5f, .5f, 0f); + + if (officer_subSuperCapacitor_1.Item.ExternalHighlight && officer_subSuperCapacitor_1.RechargeSpeed >= superCapacitorRechargeRate) + { + SetHighlight(officer_subSuperCapacitor_1.Item, false); + officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_1.Item); + } + + if (officer_subSuperCapacitor_2.Item.ExternalHighlight && officer_subSuperCapacitor_2.RechargeSpeed >= superCapacitorRechargeRate) + { + SetHighlight(officer_subSuperCapacitor_2.Item, false); + officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_2.Item); + } + + SetHighlight(officer_subAmmoBox_1, officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight); + SetHighlight(officer_subAmmoBox_2, officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight); + SetHighlight(officer_subAmmoShelf.Item, officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight); + if (officer_subAmmoBox_1.ParentInventory == officer_subLoader_1.Inventory || officer_subAmmoBox_1.ParentInventory == officer_subLoader_2.Inventory) officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_1); + if (officer_subAmmoBox_2.ParentInventory == officer_subLoader_1.Inventory || officer_subAmmoBox_2.ParentInventory == officer_subLoader_2.Inventory) officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_2); + yield return null; + } while (officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight || officer_subSuperCapacitor_1.Item.ExternalHighlight || officer_subSuperCapacitor_2.Item.ExternalHighlight); + SetHighlight(officer_subLoader_1.Item, false); + SetHighlight(officer_subLoader_2.Item, false); + SetHighlight(officer_subSuperCapacitor_1.Item, false); + SetHighlight(officer_subSuperCapacitor_2.Item, false); + SetHighlight(officer_subAmmoBox_1, false); + SetHighlight(officer_subAmmoBox_2, false); + SetHighlight(officer_subAmmoShelf.Item, false); + officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_1.Item); + officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_2.Item); + officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_1); + officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_2); + RemoveCompletedObjective(segments[7]); + GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Complete"), ChatMessageType.Radio, null); + + yield return new WaitForSeconds(4f); + CoroutineManager.StartCoroutine(TutorialCompleted()); + } + + private bool IsSelectedItem(Item item) + { + return officer?.SelectedConstruction == item; + } + + private Character SpawnMonster(string characterFile, Vector2 pos) + { + var character = Character.Create(characterFile, pos, ToolBox.RandomSeed(8)); + var ai = character.AIController as EnemyAIController; + ai.TargetOutposts = true; + character.CharacterHealth.SetVitality(character.Health / 2); + character.AnimController.Limbs.Where(l => l.attack != null).Select(l => l.attack).ForEach(a => a.AfterAttack = AIBehaviorAfterAttack.FallBack); + return character; + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ScenarioTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ScenarioTutorial.cs index 025ae979e..cbff14dca 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ScenarioTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ScenarioTutorial.cs @@ -1,44 +1,118 @@ -using System; +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Xml.Linq; namespace Barotrauma.Tutorials { class ScenarioTutorial : Tutorial { + private CoroutineHandle tutorialCoroutine; + private Character character; + private string spawnSub; private SpawnType spawnPointType; private string submarinePath; + private string startOutpostPath; + private string endOutpostPath; private string levelSeed; + private string levelParams; + + private Submarine startOutpost = null; + private Submarine endOutpost = null; + private bool currentTutorialCompleted = false; + private float fadeOutTime = 3f; + protected float waitBeforeFade = 4f; + + // Colors + protected Color highlightColor = Color.OrangeRed; + protected Color uiHighlightColor = new Color(150, 50, 0); + protected Color buttonHighlightColor = new Color(255, 100, 0); + protected Color inaccessibleColor = Color.Red; + protected Color accessibleColor = Color.Green; public ScenarioTutorial(XElement element) : base(element) { submarinePath = element.GetAttributeString("submarinepath", ""); + startOutpostPath = element.GetAttributeString("startoutpostpath", ""); + endOutpostPath = element.GetAttributeString("endoutpostpath", ""); + levelSeed = element.GetAttributeString("levelseed", "tuto"); - Enum.TryParse(element.GetAttributeString("spawnpointtype", "Human"), true, out spawnPointType); + levelParams = element.GetAttributeString("levelparams", ""); + + spawnSub = element.GetAttributeString("spawnsub", ""); + Enum.TryParse(element.GetAttributeString("spawnpointtype", "Human"), true, out spawnPointType); } public override void Initialize() { base.Initialize(); + currentTutorialCompleted = false; GameMain.Instance.ShowLoading(Loading()); } + private IEnumerable Loading() + { + Submarine.MainSub = Submarine.Load(submarinePath, "", true); + + LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Name == levelParams); + + yield return CoroutineStatus.Running; + + GameMain.GameSession = new GameSession(Submarine.MainSub, "", + GameModePreset.List.Find(g => g.Identifier == "tutorial")); + (GameMain.GameSession.GameMode as TutorialMode).Tutorial = this; + + if (generationParams != null) + { + Biome biome = LevelGenerationParams.GetBiomes().Find(b => generationParams.AllowedBiomes.Contains(b)); + + if (startOutpostPath != string.Empty) + { + startOutpost = Submarine.Load(startOutpostPath, "", false); + } + + if (endOutpostPath != string.Empty) + { + endOutpost = Submarine.Load(endOutpostPath, "", false); + } + + Level tutorialLevel = new Level(levelSeed, 0, 0, generationParams, biome, startOutpost, endOutpost); + GameMain.GameSession.StartRound(tutorialLevel); + } + else + { + GameMain.GameSession.StartRound(levelSeed); + } + + GameMain.GameSession.EventManager.Events.Clear(); + GameMain.GameSession.EventManager.Enabled = false; + GameMain.GameScreen.Select(); + + yield return CoroutineStatus.Success; + } + public override void Start() { base.Start(); - WayPoint wayPoint = WayPoint.GetRandom(spawnPointType, null); + Submarine.MainSub.GodMode = true; + + CharacterInfo charInfo = configElement.Element("Character") == null ? + new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "engineer")) : + new CharacterInfo(configElement.Element("Character")); + + WayPoint wayPoint = GetSpawnPoint(charInfo); + if (wayPoint == null) { DebugConsole.ThrowError("A waypoint with the spawntype \"" + spawnPointType + "\" is required for the tutorial event"); return; } - CharacterInfo charInfo = configElement.Element("Character") == null ? - new CharacterInfo(Character.HumanConfigFile, "", JobPrefab.List.Find(jp => jp.Identifier == "engineer")) : - new CharacterInfo(configElement.Element("Character")); - character = Character.Create(charInfo, wayPoint.WorldPosition, "", false, false); Character.Controlled = character; character.GiveJobItems(null); @@ -52,22 +126,82 @@ namespace Barotrauma.Tutorials idCard.AddTag("com"); idCard.AddTag("eng"); - CoroutineManager.StartCoroutine(UpdateState()); + tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState()); } - private IEnumerable Loading() + public override void AddToGUIUpdateList() { - Submarine.MainSub = Submarine.Load(submarinePath, "", true); - yield return CoroutineStatus.Running; + if (!currentTutorialCompleted) + { + base.AddToGUIUpdateList(); + } + } - GameMain.GameSession = new GameSession(Submarine.MainSub, "", - GameModePreset.List.Find(g => g.Identifier == "tutorial")); - (GameMain.GameSession.GameMode as TutorialMode).tutorial = this; - GameMain.GameSession.StartRound(levelSeed); - GameMain.GameSession.EventManager.Events.Clear(); - GameMain.GameScreen.Select(); + private WayPoint GetSpawnPoint(CharacterInfo charInfo) + { + Submarine spawnSub = null; - yield return CoroutineStatus.Success; + if (this.spawnSub != string.Empty) + { + switch (this.spawnSub) + { + case "startoutpost": + spawnSub = startOutpost; + break; + + case "endoutpost": + spawnSub = endOutpost; + break; + + default: + spawnSub = Submarine.MainSub; + break; + } + } + + return WayPoint.GetRandom(spawnPointType, charInfo.Job, spawnSub); + } + + protected bool HasOrder(Character character, string aiTag, string option = null) + { + if (character.CurrentOrder?.AITag == aiTag) + { + if (option == null) + { + return true; + } + else + { + HumanAIController humanAI = character.AIController as HumanAIController; + return humanAI.CurrentOrderOption == option; + } + } + + return false; + } + + protected void SetHighlight(Item item, bool state) + { + if (item.ExternalHighlight == state) return; + item.SpriteColor = (state) ? highlightColor : Color.White; + item.ExternalHighlight = state; + } + + protected void SetHighlight(Structure structure, bool state) + { + structure.SpriteColor = (state) ? highlightColor : Color.White; + structure.ExternalHighlight = state; + } + + protected void SetHighlight(Character character, bool state) + { + character.ExternalHighlight = state; + } + + protected void SetDoorAccess(Door door, LightComponent light, bool state) + { + if (state && door != null) door.requiredItems.Clear(); + if (light != null) light.LightColor = (state) ? accessibleColor : inaccessibleColor; } public override void Update(float deltaTime) @@ -75,27 +209,47 @@ namespace Barotrauma.Tutorials base.Update(deltaTime); if (character != null) { - if (Character.Controlled == null) + if (character.Oxygen < 1) { - CoroutineManager.StopCoroutines("TutorialMode.UpdateState"); + character.Oxygen = 1; + } + if (character.IsDead) + { + CoroutineManager.StartCoroutine(Dead()); + } + else if (Character.Controlled == null) + { + if (tutorialCoroutine != null) + { + CoroutineManager.StopCoroutines(tutorialCoroutine); + } infoBox = null; } else if (Character.Controlled.IsDead) { - Character.Controlled = null; - - CoroutineManager.StopCoroutines("TutorialMode.UpdateState"); - infoBox = null; CoroutineManager.StartCoroutine(Dead()); } } } + public override void Stop() + { + if (tutorialCoroutine != null) + { + CoroutineManager.StopCoroutines(tutorialCoroutine); + } + base.Stop(); + } + private IEnumerable Dead() { + GUI.PreventPauseMenuToggle = true; + Character.Controlled = character = null; + Stop(); + yield return new WaitForSeconds(3.0f); - var messageBox = new GUIMessageBox("You have died", "Do you want to try again?", new string[] { "Yes", "No" }); + var messageBox = new GUIMessageBox(TextManager.Get("Tutorial.TryAgainHeader"), TextManager.Get("Tutorial.TryAgain"), new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); messageBox.Buttons[0].OnClicked += Restart; messageBox.Buttons[0].OnClicked += messageBox.Close; @@ -106,5 +260,29 @@ namespace Barotrauma.Tutorials yield return CoroutineStatus.Success; } + + protected IEnumerable TutorialCompleted() + { + GUI.PreventPauseMenuToggle = true; + + Character.Controlled.ClearInputs(); + Character.Controlled = null; + + yield return new WaitForSeconds(waitBeforeFade); + + var endCinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, fadeOutTime); + currentTutorialCompleted = Completed = true; + while (endCinematic.Running) yield return null; + Stop(); + GameMain.MainMenuScreen.ReturnToMainMenu(null, null); + } + + protected void Heal(Character character) + { + character.SetAllDamage(0.0f, 0.0f, 0.0f); + character.Oxygen = 100.0f; + character.Bloodloss = 0.0f; + character.SetStun(0.0f, true); + } } } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs index 69a5e98c9..0eb1d1d0c 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs @@ -1,21 +1,74 @@ using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; using System.Reflection; +using System.Linq; using System.Xml.Linq; +using Barotrauma.Items.Components; +using Barotrauma.Extensions; namespace Barotrauma.Tutorials { abstract class Tutorial { + #region Tutorial variables + public static bool Initialized = false; + public static bool ContentRunning = false; public static List Tutorials; + protected bool started = false; protected GUIComponent infoBox; private Action infoBoxClosedCallback; protected XElement configElement; - private enum TutorialType { None, Scenario, Contextual }; - private TutorialType tutorialType = TutorialType.None; + protected VideoPlayer videoPlayer; + protected enum TutorialContentTypes { None = 0, Video = 1, ManualVideo = 2, TextOnly = 3 }; + protected string playableContentPath; + protected Point screenResolution; + protected float prevUIScale; + + private GUIFrame holderFrame, objectiveFrame; + private List activeObjectives = new List(); + private string objectiveTranslated; + + protected TutorialSegment activeContentSegment; + protected List segments; + + protected class TutorialSegment + { + public string Id; + public string Objective; + public TutorialContentTypes ContentType; + public XElement TextContent; + public XElement VideoContent; + public bool IsTriggered; + public GUIButton ReplayButton; + public GUITextBlock LinkedTitle, LinkedText; + public object[] Args; + + public TutorialSegment(XElement config) + { + Id = config.GetAttributeString("id", "Missing ID"); + Objective = TextManager.Get(config.GetAttributeString("objective", string.Empty), true); + Enum.TryParse(config.GetAttributeString("contenttype", "None"), true, out ContentType); + IsTriggered = config.GetAttributeBool("istriggered", false); + + switch (ContentType) + { + case TutorialContentTypes.None: + break; + case TutorialContentTypes.Video: + case TutorialContentTypes.ManualVideo: + VideoContent = config.Element("Video"); + TextContent = config.Element("Text"); + break; + case TutorialContentTypes.TextOnly: + TextContent = config.Element("Text"); + break; + } + } + } public string Name { @@ -34,7 +87,9 @@ namespace Barotrauma.Tutorials GameMain.Config.SaveNewPlayerConfig(); } } + #endregion + #region Tutorial Controls public static void Init() { Tutorials = new List(); @@ -106,27 +161,85 @@ namespace Barotrauma.Tutorials configElement = element; Name = element.GetAttributeString("name", "Unnamed"); completed = GameMain.Config.CompletedTutorialNames.Contains(Name); - Enum.TryParse(element.GetAttributeString("tutorialtype", "Scenario"), true, out tutorialType); + playableContentPath = element.GetAttributeString("playablecontentpath", ""); + + segments = new List(); + + foreach (var segment in element.Elements("Segment")) + { + segments.Add(new TutorialSegment(segment)); + } } public virtual void Initialize() { - + if (Initialized) return; + Initialized = true; + videoPlayer = new VideoPlayer(); } public virtual void Start() { - + activeObjectives.Clear(); + objectiveTranslated = TextManager.Get("Tutorial.Objective"); + CreateObjectiveFrame(); + + // Setup doors: Clear all requirements, unless the door is setup as locked. + foreach (var item in Item.ItemList) + { + var door = item.GetComponent(); + if (door != null) + { + if (door.requiredItems.Values.None(ris => ris.None(ri => ri.Identifiers.None(i => i == "locked")))) + { + door.requiredItems.Clear(); + } + } + } } public virtual void AddToGUIUpdateList() { + if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale) + { + CreateObjectiveFrame(); + } + + if (objectiveFrame != null && activeObjectives.Count > 0) + { + objectiveFrame.AddToGUIUpdateList(order: -1); + } + if (infoBox != null) infoBox.AddToGUIUpdateList(order: 100); + if (videoPlayer != null) videoPlayer.AddToGUIUpdateList(order: 100); } public virtual void Update(float deltaTime) { - + if (videoPlayer != null) + { + videoPlayer.Update(); + } + + if (activeObjectives != null) + { + for (int i = 0; i < activeObjectives.Count; i++) + { + CheckActiveObjectives(activeObjectives[i], deltaTime); + } + } + } + + public void CloseActiveContentGUI() + { + if (videoPlayer.IsPlaying) + { + videoPlayer.Stop(); + } + else if (infoBox != null) + { + CloseInfoFrame(null, null); + } } public virtual IEnumerable UpdateState() @@ -134,6 +247,231 @@ namespace Barotrauma.Tutorials yield return CoroutineStatus.Success; } + protected bool Restart(GUIButton button, object obj) + { + GUI.PreventPauseMenuToggle = false; + TutorialMode.StartTutorial(this); + return true; + } + + protected virtual void TriggerTutorialSegment(int index, params object[] args) + { + Inventory.draggingItem = null; + ContentRunning = true; + activeContentSegment = segments[index]; + segments[index].Args = args; + + string tutorialText = TextManager.GetFormatted(activeContentSegment.TextContent.GetAttributeString("tag", ""), true, args); + tutorialText = TextManager.ParseInputTypes(tutorialText); + string objectiveText = string.Empty; + + if (!string.IsNullOrEmpty(activeContentSegment.Objective)) + { + if (args.Length == 0) + { + objectiveText = activeContentSegment.Objective; + } + else + { + objectiveText = string.Format(activeContentSegment.Objective, args); + } + objectiveText = TextManager.ParseInputTypes(objectiveText); + activeContentSegment.Objective = objectiveText; + } + else + { + activeContentSegment.IsTriggered = true; // Complete at this stage only if no related objective + } + + + switch (activeContentSegment.ContentType) + { + case TutorialContentTypes.None: + break; + case TutorialContentTypes.Video: + infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText, + activeContentSegment.TextContent.GetAttributeInt("width", 300), + activeContentSegment.TextContent.GetAttributeInt("height", 80), + activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, () => LoadVideo(activeContentSegment)); + break; + case TutorialContentTypes.ManualVideo: + infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText, + activeContentSegment.TextContent.GetAttributeInt("width", 300), + activeContentSegment.TextContent.GetAttributeInt("height", 80), + activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, StopCurrentContentSegment, () => LoadVideo(activeContentSegment, false)); + break; + case TutorialContentTypes.TextOnly: + infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText, + activeContentSegment.TextContent.GetAttributeInt("width", 300), + activeContentSegment.TextContent.GetAttributeInt("height", 80), + activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, StopCurrentContentSegment); + break; + } + } + + public virtual void Stop() + { + started = ContentRunning = Initialized = false; + infoBox = null; + if (videoPlayer != null) + { + videoPlayer.Remove(); + videoPlayer = null; + } + } + #endregion + + #region Objectives + private void CreateObjectiveFrame() + { + holderFrame = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center)); + objectiveFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ObjectiveAnchor, holderFrame.RectTransform), style: null); + + for (int i = 0; i < activeObjectives.Count; i++) + { + CreateObjectiveGUI(activeObjectives[i], i, activeObjectives[i].ContentType); + } + + screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + prevUIScale = GUI.Scale; + } + + protected void StopCurrentContentSegment() + { + if (!string.IsNullOrEmpty(activeContentSegment.Objective)) + { + AddNewObjective(activeContentSegment, activeContentSegment.ContentType); + } + + activeContentSegment = null; + ContentRunning = false; + } + + protected virtual void CheckActiveObjectives(TutorialSegment objective, float deltaTime) + { + + } + + protected bool HasObjective(TutorialSegment segment) + { + return activeObjectives.Contains(segment); + } + + protected void AddNewObjective(TutorialSegment segment, TutorialContentTypes type) + { + activeObjectives.Add(segment); + CreateObjectiveGUI(segment, activeObjectives.Count - 1, type); + } + + private void CreateObjectiveGUI(TutorialSegment segment, int index, TutorialContentTypes type) + { + Point replayButtonSize = new Point((int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).X * GUI.Scale), (int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).Y * 1.45f * GUI.Scale)); + + segment.ReplayButton = new GUIButton(new RectTransform(replayButtonSize, objectiveFrame.RectTransform, Anchor.TopRight, Pivot.TopRight) { AbsoluteOffset = new Point(0, (replayButtonSize.Y + (int)(20f * GUI.Scale)) * index) }, style: null); + segment.ReplayButton.OnClicked += (GUIButton btn, object userdata) => + { + if (type == TutorialContentTypes.Video) + { + ReplaySegmentVideo(segment); + } + else + { + ShowSegmentText(segment); + } + return true; + }; + + string objectiveText = TextManager.ParseInputTypes(objectiveTranslated); + int yOffset = (int)((GUI.ObjectiveNameFont.MeasureString(objectiveText).Y / 2f + 5)); + segment.LinkedTitle = new GUITextBlock(new RectTransform(new Point((int)GUI.ObjectiveNameFont.MeasureString(objectiveText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterRight, Pivot.BottomRight) { AbsoluteOffset = new Point((int)(-10 * GUI.Scale), 0) }, + objectiveText, textColor: Color.White, font: GUI.ObjectiveTitleFont, textAlignment: Alignment.CenterRight); + segment.LinkedText = new GUITextBlock(new RectTransform(new Point(replayButtonSize.X, yOffset), segment.ReplayButton.RectTransform, Anchor.Center, Pivot.TopCenter) { AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }, + TextManager.ParseInputTypes(segment.Objective), textColor: new Color(4, 180, 108), font: GUI.ObjectiveNameFont, textAlignment: Alignment.CenterRight); + + segment.LinkedTitle.Color = segment.LinkedTitle.HoverColor = segment.LinkedTitle.PressedColor = segment.LinkedTitle.SelectedColor = Color.Transparent; + segment.LinkedText.Color = segment.LinkedText.HoverColor = segment.LinkedText.PressedColor = segment.LinkedText.SelectedColor = Color.Transparent; + segment.ReplayButton.Color = segment.ReplayButton.HoverColor = segment.ReplayButton.PressedColor = segment.ReplayButton.SelectedColor = Color.Transparent; + } + + private void ReplaySegmentVideo(TutorialSegment segment) + { + if (ContentRunning) return; + ContentRunning = true; + videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false); + } + + private void ShowSegmentText(TutorialSegment segment) + { + if (ContentRunning) return; + Inventory.draggingItem = null; + ContentRunning = true; + + string tutorialText = TextManager.GetFormatted(segment.TextContent.GetAttributeString("tag", ""), true, segment.Args); + + Action videoAction = null; + + if (segment.ContentType != TutorialContentTypes.TextOnly) + { + videoAction = () => LoadVideo(segment, false); + } + + infoBox = CreateInfoFrame(TextManager.Get(segment.Id), tutorialText, + segment.TextContent.GetAttributeInt("width", 300), + segment.TextContent.GetAttributeInt("height", 80), + segment.TextContent.GetAttributeString("anchor", "Center"), true, () => ContentRunning = false, videoAction); + } + + protected void RemoveCompletedObjective(TutorialSegment segment) + { + if (!HasObjective(segment)) return; + segment.IsTriggered = true; + segment.ReplayButton.OnClicked = null; + + int checkMarkHeight = (int)(segment.ReplayButton.Rect.Height * 1.2f); + int checkMarkWidth = (int)(checkMarkHeight * 0.93f); + + Color color = new Color(4, 180, 108); + + int objectiveTextWidth = segment.LinkedText.Rect.Width; + int objectiveTitleWidth = segment.LinkedTitle.Rect.Width; + + RectTransform rectTA; + if (objectiveTextWidth > objectiveTitleWidth) + { + rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft); + rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - 5, 0); + } + else + { + rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft); + rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - 5 - (objectiveTitleWidth), 0); + } + + GUIImage checkmark = new GUIImage(rectTA, "CheckMark"); + checkmark.Color = checkmark.SelectedColor = checkmark.HoverColor = checkmark.PressedColor = color; + + RectTransform rectTB = new RectTransform(new Vector2(1.1f, .8f), segment.LinkedText.RectTransform, Anchor.Center, Pivot.Center); + GUIImage stroke = new GUIImage(rectTB, "Stroke"); + stroke.Color = stroke.SelectedColor = stroke.HoverColor = stroke.PressedColor = color; + + CoroutineManager.StartCoroutine(WaitForObjectiveEnd(segment)); + } + + private IEnumerable WaitForObjectiveEnd(TutorialSegment objective) + { + yield return new WaitForSeconds(2.0f); + objectiveFrame.RemoveChild(objective.ReplayButton); + activeObjectives.Remove(objective); + + for (int i = 0; i < activeObjectives.Count; i++) + { + activeObjectives[i].ReplayButton.RectTransform.AbsoluteOffset = new Point(0, (activeObjectives[i].ReplayButton.Rect.Height + 20) * i); + } + } + + #endregion + + #region InfoFrame protected bool CloseInfoFrame(GUIButton button, object userData) { infoBox = null; @@ -141,87 +479,130 @@ namespace Barotrauma.Tutorials return true; } - protected GUIComponent CreateInfoFrame(string text, bool hasButton = false, Action callback = null) + protected GUIComponent CreateInfoFrame(string title, string text, int width = 300, int height = 80, string anchorStr = "", bool hasButton = false, Action callback = null, Action showVideo = null) { - int width = 300; - int height = hasButton ? 110 : 80; - - string wrappedText = ToolBox.WrapText(text, width, GUI.Font); - - height += wrappedText.Split('\n').Length * 25; - - var infoBlock = new GUIFrame(new RectTransform(new Point(width, height), GUI.Canvas, Anchor.TopRight) { AbsoluteOffset = new Point(20) }); - infoBlock.Flash(Color.Green); - - var textBlock = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.7f), infoBlock.RectTransform, Anchor.Center), - text, wrap: true); - - infoBoxClosedCallback = callback; - - if (hasButton) - { - var okButton = new GUIButton(new RectTransform(new Point(160, 50), infoBlock.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, -10) }, - TextManager.Get("OK")) - { - OnClicked = CloseInfoFrame - }; - } + if (hasButton) height += 60; - GUI.PlayUISound(GUISoundType.UIMessage); - - return infoBlock; - } - - protected GUIComponent CreateInfoFrame(string title, string text, int width, int height, string anchorStr, bool hasButton = false, Action callback = null) - { - if (hasButton) height += 30; - string wrappedText = ToolBox.WrapText(text, width, GUI.Font); - height += wrappedText.Split('\n').Length * 25; - + height += (int)(GUI.Font.MeasureString(wrappedText).Y + 50); if (title.Length > 0) { height += 35; } Anchor anchor = Anchor.TopRight; - Enum.TryParse(anchorStr, out anchor); - var infoBlock = new GUIFrame(new RectTransform(new Point((int)(width * GUI.Scale), (int)(height * GUI.Scale)), GUI.Canvas, anchor) { AbsoluteOffset = new Point(20) }); + if (anchorStr != string.Empty) + { + Enum.TryParse(anchorStr, out anchor); + } + + var background = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center), "InnerFrame", new Color(0, 0, 0, 1f)); + + var infoBlock = new GUIFrame(new RectTransform(new Point((int)(width * GUI.Scale), (int)(height * GUI.Scale)), background.RectTransform, anchor) { AbsoluteOffset = new Point(20) }); infoBlock.Flash(Color.Green); + var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), infoBlock.RectTransform, Anchor.Center)) + { + Stretch = true, + AbsoluteSpacing = 5 + }; + if (title.Length > 0) { - var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1f, .35f), infoBlock.RectTransform, Anchor.TopCenter, - Pivot.TopCenter), title, font: GUI.VideoTitleFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0)); - titleBlock.TextScale = GUI.Scale; + var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), + title, font: GUI.VideoTitleFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0)); + titleBlock.RectTransform.IsFixedSize = true; } - var textBlock = new GUITextBlock(new RectTransform(new Vector2(0.9f, 1f), infoBlock.RectTransform, Anchor.BottomCenter), - text, wrap: true); - textBlock.TextScale = GUI.Scale; + var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), text, wrap: true); + textBlock.RectTransform.IsFixedSize = true; infoBoxClosedCallback = callback; if (hasButton) { - var okButton = new GUIButton(new RectTransform(new Point(160, 50), infoBlock.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, -10) }, - TextManager.Get("OK")) + var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), infoContent.RectTransform) { MinSize = new Point(0, 30), MaxSize = new Point((int) infoContent.Rect.X, 60) }, isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.1f + }; + buttonContainer.RectTransform.IsFixedSize = true; + + if (showVideo != null) + { + var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform), + TextManager.Get("Video"), style: "GUIButtonLarge") + { + OnClicked = (GUIButton button, object obj) => + { + showVideo(); + return true; + } + }; + } + + var okButton = new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f), buttonContainer.RectTransform), + TextManager.Get("OK"), style: "GUIButtonLarge") { OnClicked = CloseInfoFrame }; } + infoBlock.RectTransform.NonScaledSize = new Point(infoBlock.Rect.Width, (int)(infoContent.Children.Sum(c => c.Rect.Height + infoContent.AbsoluteSpacing) / infoContent.RectTransform.RelativeSize.Y)); + GUI.PlayUISound(GUISoundType.UIMessage); - return infoBlock; + return background; + } + #endregion + + #region Video + protected void LoadVideo(TutorialSegment segment, bool showText = true) + { + if (videoPlayer == null) videoPlayer = new VideoPlayer(); + if (showText) + { + videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, segment.Objective, StopCurrentContentSegment); + } + else + { + videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), null, segment.Id, true, segment.Objective, null); + } + } + #endregion + + #region Highlights + protected void HighlightInventorySlot(Inventory inventory, string identifier, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount) + { + if (inventory.slots == null) { return; } + for (int i = 0; i < inventory.Items.Length; i++) + { + if (inventory.Items[i] != null && inventory.Items[i].Prefab.Identifier == identifier) + { + HighlightInventorySlot(inventory, i, color, fadeInDuration, fadeOutDuration, scaleUpAmount); + } + } } - protected bool Restart(GUIButton button, object obj) + protected void HighlightInventorySlotWithTag(Inventory inventory, string tag, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount) { - TutorialMode.StartTutorial(this); - return true; + if (inventory.slots == null) { return; } + for (int i = 0; i < inventory.Items.Length; i++) + { + if (inventory.Items[i] != null && inventory.Items[i].HasTag(tag)) + { + HighlightInventorySlot(inventory, i, color, fadeInDuration, fadeOutDuration, scaleUpAmount); + } + } } + + protected void HighlightInventorySlot(Inventory inventory, int index, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount) + { + if (inventory.slots == null || index < 0 || inventory.slots[index].HighlightTimer > 0) return; + inventory.slots[index].ShowBorderHighlight(color, fadeInDuration, fadeOutDuration, scaleUpAmount); + } + #endregion } } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/TutorialMode.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/TutorialMode.cs index f6b5dcd9c..407f742e9 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/TutorialMode.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/TutorialMode.cs @@ -1,11 +1,10 @@ using Barotrauma.Tutorials; -using Microsoft.Xna.Framework.Graphics; namespace Barotrauma { class TutorialMode : GameMode { - public Tutorial tutorial; + public Tutorial Tutorial; public static void StartTutorial(Tutorial tutorial) { @@ -20,18 +19,20 @@ namespace Barotrauma public override void Start() { base.Start(); - tutorial.Start(); + GameMain.GameSession.CrewManager = new CrewManager(true); + Tutorial.Start(); } public override void AddToGUIUpdateList() { - tutorial.AddToGUIUpdateList(); + base.AddToGUIUpdateList(); + Tutorial.AddToGUIUpdateList(); } public override void Update(float deltaTime) { base.Update(deltaTime); - tutorial.Update(deltaTime); + Tutorial.Update(deltaTime); } } } diff --git a/Barotrauma/BarotraumaClient/Source/GameSettings.cs b/Barotrauma/BarotraumaClient/Source/GameSettings.cs index 710d858e7..90399592b 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSettings.cs @@ -6,6 +6,7 @@ using Microsoft.Xna.Framework.Input; using OpenTK.Audio.OpenAL; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace Barotrauma @@ -80,8 +81,16 @@ namespace Barotrauma var leftPanel = new GUILayoutGroup(new RectTransform(new Vector2(0.25f, 1.0f), settingsFramePadding.RectTransform, Anchor.TopLeft)); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform), - TextManager.Get("Settings"), textAlignment: Alignment.TopLeft, font: GUI.LargeFont); + var settingsTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform), + TextManager.Get("Settings"), textAlignment: Alignment.TopLeft, font: GUI.LargeFont) + { ForceUpperCase = true }; + + //TODO: enable when new texts can be added + /*new GUIButton(new RectTransform(new Vector2(1.0f, 0.75f), settingsTitle.RectTransform, Anchor.CenterRight), style: "GUIBugButton") + { + ToolTip = "Bug Reporter", + OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; } + };*/ var generalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), leftPanel.RectTransform, Anchor.TopLeft)); @@ -155,7 +164,7 @@ namespace Barotrauma { UserData = tab }; - tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), tabButtonHolder.RectTransform), + tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), tabButtonHolder.RectTransform), TextManager.Get("SettingsTab." + tab.ToString()), style: "GUITabButton") { UserData = tab, @@ -193,7 +202,7 @@ namespace Barotrauma var resolutionDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), elementCount: supportedDisplayModes.Count) { OnSelected = SelectResolution, -#if OSX +#if !LINUX ButtonEnabled = GameMain.Config.WindowMode == WindowMode.Windowed #endif }; @@ -233,7 +242,7 @@ namespace Barotrauma { UnsavedSettings = true; GameMain.Config.WindowMode = (WindowMode)guiComponent.UserData; -#if OSX +#if !LINUX resolutionDD.ButtonEnabled = GameMain.Config.WindowMode == WindowMode.Windowed; #endif return true; @@ -429,7 +438,7 @@ namespace Barotrauma UnsavedSettings = true; return true; }; - + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), TextManager.Get("VoiceChat")); IList deviceNames = Alc.GetString((IntPtr)null, AlcGetStringList.CaptureDeviceSpecifier); @@ -438,6 +447,17 @@ namespace Barotrauma DebugConsole.NewMessage(name + " " + name.Length.ToString(), Color.Lime); } + GUITickBox directionalVoiceChat = new GUITickBox(new RectTransform(new Point(32, 32), audioSliders.RectTransform), TextManager.Get("DirectionalVoiceChat")); + directionalVoiceChat.Selected = UseDirectionalVoiceChat; + directionalVoiceChat.ToolTip = TextManager.Get("DirectionalVoiceChatToolTip"); + directionalVoiceChat.OnSelected = (tickBox) => + { + UseDirectionalVoiceChat = tickBox.Selected; + UnsavedSettings = true; + return true; + }; + + if (string.IsNullOrWhiteSpace(VoiceCaptureDevice)) VoiceCaptureDevice = deviceNames[0]; #if (!OSX) var deviceList = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), VoiceCaptureDevice, deviceNames.Count); @@ -627,7 +647,7 @@ namespace Barotrauma return true; } }; - + var inputFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f), controlsLayoutGroup.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.03f }; diff --git a/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs index 44ac24a05..71b3d07d0 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs @@ -1,4 +1,5 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; @@ -18,7 +19,7 @@ namespace Barotrauma Right, Center } - + private enum QuickUseAction { None, @@ -33,6 +34,8 @@ namespace Barotrauma } private static Dictionary limbSlotIcons; + + const InvSlotType PersonalSlots = InvSlotType.Card | InvSlotType.Headset | InvSlotType.InnerClothes | InvSlotType.OuterClothes | InvSlotType.Head; private Point screenResolution; @@ -50,11 +53,42 @@ namespace Barotrauma } } public bool Hidden { get; set; } + + private bool hidePersonalSlots; + private float hidePersonalSlotsState; + private GUIButton hideButton; + private Rectangle personalSlotArea; + public bool HidePersonalSlots + { + get { return hidePersonalSlots; } + } + + public Rectangle PersonalSlotArea + { + get { return personalSlotArea; } + } + partial void InitProjSpecific(XElement element) { Hidden = true; + hideButton = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), (int)(60 * GUI.Scale)), GUI.Canvas) + { AbsoluteOffset = HUDLayoutSettings.CrewArea.Location }, + "", style: "UIToggleButton"); + hideButton.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipHorizontally); + hideButton.OnClicked += (GUIButton btn, object userdata) => + { + hidePersonalSlots = !hidePersonalSlots; + foreach (GUIComponent child in btn.Children) + { + child.SpriteEffects = hidePersonalSlots ? SpriteEffects.None : SpriteEffects.FlipHorizontally; + } + return true; + }; + + hidePersonalSlots = false; + if (limbSlotIcons == null) { limbSlotIcons = new Dictionary(); @@ -66,13 +100,13 @@ namespace Barotrauma limbSlotIcons.Add(InvSlotType.Head, new Sprite("Content/UI/IconAtlas.png", new Rectangle(896 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); limbSlotIcons.Add(InvSlotType.LeftHand, new Sprite("Content/UI/IconAtlas.png", new Rectangle(640 + margin, 383 + margin, 128 - margin * 2, 128 - margin * 2))); limbSlotIcons.Add(InvSlotType.RightHand, new Sprite("Content/UI/IconAtlas.png", new Rectangle(768 + margin, 383 + margin, 128 - margin * 2, 128 - margin * 2))); + limbSlotIcons.Add(InvSlotType.OuterClothes, new Sprite("Content/UI/IconAtlas.png", new Rectangle(768 + margin, 896 + margin, 128 - margin * 2, 128 - margin * 2))); } - SlotPositions = new Vector2[SlotTypes.Length]; CurrentLayout = Layout.Default; SetSlotPositions(layout); } - + protected override void PutItem(Item item, int i, Character user, bool removeItem = true, bool createNetworkEvent = true) { base.PutItem(item, i, user, removeItem, createNetworkEvent); @@ -158,6 +192,11 @@ namespace Barotrauma { if (slots[i].Disabled || (hideEmptySlot[i] && Items[i] == null)) return true; + if (layout == Layout.Default) + { + if (PersonalSlots.HasFlag(SlotTypes[i]) && !personalSlotArea.Contains(slots[i].Rect.Center + slots[i].DrawOffset.ToPoint())) return true; + } + //no need to draw the right hand slot if the item is in both hands if (Items[i] != null && SlotTypes[i] == InvSlotType.RightHand && IsInLimbSlot(Items[i], InvSlotType.LeftHand)) { @@ -175,7 +214,6 @@ namespace Barotrauma return false; } - private void SetSlotPositions(Layout layout) { int spacing = (int)(10 * UIScale); @@ -184,27 +222,32 @@ namespace Barotrauma if (slots == null) CreateSlots(); - var upperSlots = InvSlotType.Card | InvSlotType.Headset | InvSlotType.InnerClothes | InvSlotType.Head; + hideButton.Visible = false; switch (layout) { case Layout.Default: { - int personalSlotCount = SlotTypes.Count(s => upperSlots.HasFlag(s)); - int normalSlotCount = SlotTypes.Count(s => !upperSlots.HasFlag(s)); + int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s)); + int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s)); int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (slotSize.X + spacing) / 2; - int upperX = HUDLayoutSettings.PortraitArea.X - slotSize.X; + int upperX = HUDLayoutSettings.PortraitArea.X - slotSize.X * 2; //make sure the rightmost normal slot doesn't overlap with the personal slots x -= Math.Max((x + normalSlotCount * (slotSize.X + spacing)) - (upperX - personalSlotCount * (slotSize.X + spacing)), 0); + int hideButtonSlotIndex = -1; for (int i = 0; i < SlotPositions.Length; i++) { - if (upperSlots.HasFlag(SlotTypes[i])) + if (PersonalSlots.HasFlag(SlotTypes[i])) { SlotPositions[i] = new Vector2(upperX, GameMain.GraphicsHeight - bottomOffset); upperX -= slotSize.X + spacing; + personalSlotArea = (hideButtonSlotIndex == -1) ? + new Rectangle(SlotPositions[i].ToPoint(), slotSize) : + Rectangle.Union(personalSlotArea, new Rectangle(SlotPositions[i].ToPoint(), slotSize)); + hideButtonSlotIndex = i; } else { @@ -212,19 +255,29 @@ namespace Barotrauma x += slotSize.X + spacing; } } + + if (hideButtonSlotIndex > -1) + { + hideButton.RectTransform.SetPosition(Anchor.TopLeft, Pivot.TopLeft); + hideButton.RectTransform.NonScaledSize = new Point(slotSize.X / 2, slotSize.Y + slots[hideButtonSlotIndex].EquipButtonRect.Height); + hideButton.RectTransform.AbsoluteOffset = new Point( + personalSlotArea.Right + spacing, + personalSlotArea.Y - slots[hideButtonSlotIndex].EquipButtonRect.Height); + hideButton.Visible = true; + } } break; case Layout.Right: { int extraOffset = 0; int x = HUDLayoutSettings.InventoryAreaLower.Right; - int upperX = HUDLayoutSettings.InventoryAreaLower.Right; + int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - slotSize.X - spacing; for (int i = 0; i < slots.Length; i++) { if (HideSlot(i)) continue; - if (upperSlots.HasFlag(SlotTypes[i])) + if (PersonalSlots.HasFlag(SlotTypes[i])) { - upperX -= slotSize.X + spacing; + //upperX -= slotSize.X + spacing; } else { @@ -236,10 +289,10 @@ namespace Barotrauma for (int i = 0; i < SlotPositions.Length; i++) { if (HideSlot(i)) continue; - if (upperSlots.HasFlag(SlotTypes[i])) + if (PersonalSlots.HasFlag(SlotTypes[i])) { - SlotPositions[i] = new Vector2(upperX, GameMain.GraphicsHeight - bottomOffset * 2 - extraOffset - spacing * 2); - upperX += slots[i].Rect.Width + spacing; + SlotPositions[i] = new Vector2(personalSlotX, GameMain.GraphicsHeight - bottomOffset * 2 - extraOffset - spacing * 2); + personalSlotX -= slots[i].Rect.Width + spacing; } else { @@ -260,14 +313,14 @@ namespace Barotrauma case Layout.Left: { int x = HUDLayoutSettings.InventoryAreaLower.X; - int upperX = x; + int personalSlotX = x; for (int i = 0; i < SlotPositions.Length; i++) { if (HideSlot(i)) continue; - if (upperSlots.HasFlag(SlotTypes[i])) + if (PersonalSlots.HasFlag(SlotTypes[i])) { - SlotPositions[i] = new Vector2(upperX, GameMain.GraphicsHeight - bottomOffset * 2 - spacing * 2); - upperX += slots[i].Rect.Width + spacing; + SlotPositions[i] = new Vector2(personalSlotX, GameMain.GraphicsHeight - bottomOffset * 2 - spacing * 2); + personalSlotX += slots[i].Rect.Width + spacing; } else { @@ -353,6 +406,27 @@ namespace Barotrauma ((selectedSlot != null && selectedSlot.IsSubSlot) || (draggingItem != null && (draggingSlot == null || !draggingSlot.MouseOn()))); if (CharacterHealth.OpenHealthWindow != null) hoverOnInventory = true; + if (layout == Layout.Default && hideButton.Visible) + { + hideButton.AddToGUIUpdateList(); + hideButton.UpdateManually(deltaTime, alsoChildren: true); + + hidePersonalSlotsState = hidePersonalSlots ? + Math.Min(hidePersonalSlotsState + deltaTime * 5.0f, 1.0f) : + Math.Max(hidePersonalSlotsState - deltaTime * 5.0f, 0.0f); + + for (int i = 0; i < slots.Length; i++) + { + if (!PersonalSlots.HasFlag(SlotTypes[i])) { continue; } + if (HidePersonalSlots) + { + if (selectedSlot?.Slot == slots[i]) { selectedSlot = null; } + highlightedSubInventorySlots.RemoveWhere(s => s.Slot == slots[i]); + } + slots[i].DrawOffset = Vector2.Lerp(Vector2.Zero, new Vector2(personalSlotArea.Width, 0.0f), hidePersonalSlotsState); + } + } + if (hoverOnInventory) HideTimer = 0.5f; if (HideTimer > 0.0f) HideTimer -= deltaTime; @@ -365,7 +439,23 @@ namespace Barotrauma QuickUseItem(Items[i], true, false, true); } } - + + //force personal slots open if an item is running out of battery/fuel/oxygen/etc + if (hidePersonalSlots) + { + for (int i = 0; i < slots.Length; i++) + { + if (Items[i]?.OwnInventory != null && Items[i].OwnInventory.Capacity == 1 && PersonalSlots.HasFlag(SlotTypes[i])) + { + if (Items[i].OwnInventory.Items[0].Condition > 0.0f && + Items[i].OwnInventory.Items[0].Condition / Items[i].OwnInventory.Items[0].MaxCondition < 0.15f) + { + hidePersonalSlots = false; + } + } + } + } + List hideSubInventories = new List(); foreach (var highlightedSubInventorySlot in highlightedSubInventorySlots) { @@ -702,7 +792,12 @@ namespace Barotrauma } base.Draw(spriteBatch); - + + if (hideButton != null && hideButton.Visible) + { + hideButton.DrawManually(spriteBatch, alsoChildren: true); + } + InventorySlot highlightedQuickUseSlot = null; for (int i = 0; i < capacity; i++) { @@ -716,7 +811,7 @@ namespace Barotrauma if (limbSlotIcons.ContainsKey(SlotTypes[i])) { var icon = limbSlotIcons[SlotTypes[i]]; - icon.Draw(spriteBatch, slots[i].Rect.Center.ToVector2(), Color.White * 0.3f, origin: icon.size / 2, scale: slots[i].Rect.Width / icon.size.X); + icon.Draw(spriteBatch, slots[i].Rect.Center.ToVector2() + slots[i].DrawOffset, Color.White * 0.3f, origin: icon.size / 2, scale: slots[i].Rect.Width / icon.size.X); } continue; } @@ -726,12 +821,12 @@ namespace Barotrauma if (IsInLimbSlot(Items[i], InvSlotType.LeftHand)) { var icon = limbSlotIcons[InvSlotType.LeftHand]; - icon.Draw(spriteBatch, new Vector2(slots[i].Rect.X, slots[i].Rect.Bottom), Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.35f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f); + icon.Draw(spriteBatch, new Vector2(slots[i].Rect.X, slots[i].Rect.Bottom) + slots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.35f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f); } if (IsInLimbSlot(Items[i], InvSlotType.RightHand)) { var icon = limbSlotIcons[InvSlotType.RightHand]; - icon.Draw(spriteBatch, new Vector2(slots[i].Rect.Right, slots[i].Rect.Bottom), Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.65f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f); + icon.Draw(spriteBatch, new Vector2(slots[i].Rect.Right, slots[i].Rect.Bottom) + slots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.65f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f); } Color color = slots[i].EquipButtonState == GUIComponent.ComponentState.Pressed ? Color.Gray : Color.White * 0.8f; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs index f0b6652d6..c5ecaf754 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs @@ -48,6 +48,7 @@ namespace Barotrauma.Items.Components partial class ItemComponent : ISerializableEntity { + private bool[] hasSoundsOfType; private Dictionary> sounds; private Dictionary soundSelectionModes; @@ -183,6 +184,8 @@ namespace Barotrauma.Items.Components private SoundChannel loopingSoundChannel; public void PlaySound(ActionType type, Vector2 position, Character user = null) { + if (!hasSoundsOfType[(int)type]) { return; } + if (loopingSound != null) { if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(position.X, position.Y, 0.0f)) > loopingSound.Range * loopingSound.Range) @@ -224,10 +227,9 @@ namespace Barotrauma.Items.Components } return; } - - if (!sounds.TryGetValue(type, out List matchingSounds)) return; - + ItemSound itemSound = null; + var matchingSounds = sounds[type]; if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying) { SoundSelectionMode soundSelectionMode = soundSelectionModes[type]; @@ -262,7 +264,7 @@ namespace Barotrauma.Items.Components private void PlaySound(ItemSound itemSound, Vector2 position, Character user = null) { - if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(position.X, position.Y, 0.0f)) > itemSound.Range * itemSound.Range) + if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > itemSound.Range * itemSound.Range) { return; } @@ -290,7 +292,7 @@ namespace Barotrauma.Items.Components { float volume = GetSoundVolume(itemSound); if (volume <= 0.0f) return; - SoundPlayer.PlaySound(itemSound.RoundSound.Sound, volume, itemSound.Range, position, item.CurrentHull); + SoundPlayer.PlaySound(itemSound.RoundSound.Sound, position, volume, itemSound.Range, item.CurrentHull); } } @@ -377,6 +379,10 @@ namespace Barotrauma.Items.Components public virtual void UpdateHUD(Character character, float deltaTime, Camera cam) { } + public virtual void CreateEditingHUD(SerializableEntityEditor editor) + { + } + private bool LoadElemProjSpecific(XElement subElement) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -447,6 +453,7 @@ namespace Barotrauma.Items.Components { soundList = new List(); sounds.Add(itemSound.Type, soundList); + hasSoundsOfType[(int)itemSound.Type] = true; } soundList.Add(itemSound); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemLabel.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemLabel.cs index 651858092..10eb66e95 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemLabel.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemLabel.cs @@ -28,7 +28,7 @@ namespace Barotrauma.Items.Components } private string text; - [Serialize("", true), Editable(100)] + [Serialize("", true, translationTextTag: "Label."), Editable(100)] public string Text { get { return text; } @@ -40,13 +40,20 @@ namespace Barotrauma.Items.Components { textBlock = null; } - + text = value; - TextBlock.Text = value; + DisplayText = TextManager.Get(text, returnNull: true) ?? value; + TextBlock.Text = DisplayText; SetScrollingText(); } } + public string DisplayText + { + get; + private set; + } + [Editable, Serialize("0.0,0.0,0.0,1.0", true)] public Color TextColor { @@ -115,7 +122,7 @@ namespace Barotrauma.Items.Components { if (!scrollable) return; - float totalWidth = textBlock.Font.MeasureString(text).X; + float totalWidth = textBlock.Font.MeasureString(DisplayText).X; float textAreaWidth = Math.Max(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z, 0); if (totalWidth >= textAreaWidth) { @@ -123,13 +130,13 @@ namespace Barotrauma.Items.Components //(so the text can scroll entirely out of view before we reset it back to start) needsScrolling = true; float spaceWidth = textBlock.Font.MeasureChar(' ').X; - scrollingText = new string(' ', (int)Math.Ceiling(textAreaWidth / spaceWidth)) + text; + scrollingText = new string(' ', (int)Math.Ceiling(textAreaWidth / spaceWidth)) + DisplayText; } else { //whole text can fit in the textblock, no need to scroll needsScrolling = false; - scrollingText = text; + scrollingText = DisplayText; scrollAmount = 0.0f; scrollIndex = 0; return; @@ -145,7 +152,7 @@ namespace Barotrauma.Items.Components charWidths[i] = charWidth; } - scrollIndex = MathHelper.Clamp(scrollIndex, 0, text.Length); + scrollIndex = MathHelper.Clamp(scrollIndex, 0, DisplayText.Length); } public override void Update(float deltaTime, Camera cam) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/LightComponent.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/LightComponent.cs index 141433f4e..7f676175e 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/LightComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/LightComponent.cs @@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components { if (light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f) { - light.LightSprite.Draw(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), lightColor * lightBrightness, 0.0f, 1.0f, Microsoft.Xna.Framework.Graphics.SpriteEffects.None, item.SpriteDepth - 0.0001f); + light.LightSprite.Draw(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), lightColor * lightBrightness, 0.0f, item.Scale, SpriteEffects.None, item.SpriteDepth - 0.0001f); } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs index f4cb9447f..ce430e5ee 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Controller.cs @@ -14,5 +14,59 @@ namespace Barotrauma.Items.Components } } } + + private bool crewAreaOriginalState; + private bool chatBoxOriginalState; + private bool isHUDsHidden; + + partial void HideHUDs(bool value) + { + if (isHUDsHidden == value) { return; } + if (value == true) + { + ToggleCrewArea(false, storeOriginalState: true); + ToggleChatBox(false, storeOriginalState: true); + } + else + { + ToggleCrewArea(crewAreaOriginalState, storeOriginalState: false); + ToggleChatBox(chatBoxOriginalState, storeOriginalState: false); + } + isHUDsHidden = value; + } + + private void ToggleCrewArea(bool value, bool storeOriginalState) + { + var crewManager = GameMain.GameSession.CrewManager; + if (storeOriginalState) + { + crewAreaOriginalState = crewManager.ToggleCrewAreaOpen; + } + crewManager.ToggleCrewAreaOpen = value; + } + + private void ToggleChatBox(bool value, bool storeOriginalState) + { + var crewManager = GameMain.GameSession.CrewManager; + if (crewManager.IsSinglePlayer) + { + if (crewManager.ChatBox != null) + { + if (storeOriginalState) + { + chatBoxOriginalState = crewManager.ChatBox.ToggleOpen; + } + crewManager.ChatBox.ToggleOpen = value; + } + } + else if (GameMain.Client != null) + { + if (storeOriginalState) + { + chatBoxOriginalState = GameMain.Client.ChatBox.ToggleOpen; + } + GameMain.Client.ChatBox.ToggleOpen = value; + } + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Deconstructor.cs index 85150941e..cb8889292 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Deconstructor.cs @@ -9,6 +9,10 @@ namespace Barotrauma.Items.Components { partial class Deconstructor : Powered, IServerSerializable, IClientSerializable { + public GUIButton ActivateButton + { + get { return activateButton; } + } private GUIButton activateButton; private GUIComponent inputInventoryHolder, outputInventoryHolder; private GUICustomComponent inputInventoryOverlay; @@ -44,7 +48,6 @@ namespace Barotrauma.Items.Components Visible = false, CanBeFocused = false }; - outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.3f), paddedFrame.RectTransform), style: null); } @@ -71,7 +74,7 @@ namespace Barotrauma.Items.Components public override void UpdateHUD(Character character, float deltaTime, Camera cam) { inSufficientPowerWarning.Visible = powerConsumption > 0 && voltage < minVoltage; - activateButton.Enabled = !inSufficientPowerWarning.Visible; + //activateButton.Enabled = !inSufficientPowerWarning.Visible; } private bool ToggleActive(GUIButton button, object obj) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs index 4dc0b89a1..f01906f3d 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs @@ -15,6 +15,10 @@ namespace Barotrauma.Items.Components private GUIFrame selectedItemFrame; + public GUIButton ActivateButton + { + get { return activateButton; } + } private GUIButton activateButton; private GUITextBox itemFilterBox; @@ -22,6 +26,10 @@ namespace Barotrauma.Items.Components private GUIComponent inputInventoryHolder, outputInventoryHolder; private GUICustomComponent inputInventoryOverlay, outputInventoryOverlay; + public FabricationRecipe SelectedItem + { + get { return selectedItem; } + } private FabricationRecipe selectedItem; private GUIComponent inSufficientPowerWarning; @@ -73,7 +81,31 @@ namespace Barotrauma.Items.Components { CanBeFocused = false }; - + + CreateRecipes(); + + activateButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.07f), paddedFrame.RectTransform), + TextManager.Get("FabricatorCreate"), style: "GUIButtonLarge") + { + OnClicked = StartButtonClicked, + UserData = selectedItem, + Enabled = false + }; + + inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform), TextManager.Get("FabricatorNoPower"), + textColor: Color.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow") + { + HoverColor = Color.Black, + IgnoreLayoutGroups = true, + Visible = false, + CanBeFocused = false + }; + } + + partial void CreateRecipes() + { + itemList.Content.RectTransform.ClearChildren(); + foreach (FabricationRecipe fi in fabricationRecipes) { GUIFrame frame = new GUIFrame(new RectTransform(new Point(itemList.Rect.Width, 30), itemList.Content.RectTransform), style: null) @@ -101,23 +133,6 @@ namespace Barotrauma.Items.Components }; } } - - activateButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.07f), paddedFrame.RectTransform), - TextManager.Get("FabricatorCreate"), style: "GUIButtonLarge") - { - OnClicked = StartButtonClicked, - UserData = selectedItem, - Enabled = false - }; - - inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform), TextManager.Get("FabricatorNoPower"), - textColor: Color.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow") - { - HoverColor = Color.Black, - IgnoreLayoutGroups = true, - Visible = false, - CanBeFocused = false - }; } partial void OnItemLoadedProjSpecific() @@ -241,6 +256,7 @@ namespace Barotrauma.Items.Components } } } + private void DrawOutputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) { overlayComponent.RectTransform.SetAsLastChild(); @@ -363,6 +379,29 @@ namespace Barotrauma.Items.Components return true; } + public void HighlightRecipe(string identifier, Color color) + { + foreach (GUIComponent child in itemList.Content.Children) + { + FabricationRecipe recipe = child.UserData as FabricationRecipe; + if (recipe?.DisplayName == null) { continue; } + if (recipe.TargetItem.Identifier == identifier) + { + if (child.FlashTimer > 0.0f) return; + child.Flash(color, 1.5f, false); + + for (int i = 0; i < child.CountChildren; i++) + { + var grandChild = child.GetChild(i); + if (grandChild is GUITextBlock) continue; + grandChild.Flash(color, 1.5f, false); + } + + return; + } + } + } + private bool StartButtonClicked(GUIButton button, object obj) { if (selectedItem == null) { return false; } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs index cd5c74d3e..5967624a1 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs @@ -221,7 +221,7 @@ namespace Barotrauma.Items.Components { hullInfoFrame.RectTransform.ScreenSpaceOffset = hullFrame.Rect.Center; hullInfoFrame.Visible = true; - hullNameText.Text = hull.RoomName; + hullNameText.Text = hull.DisplayName; foreach (Hull linkedHull in hullData.LinkedHulls) { diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs index b13727818..5ef0e010c 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs @@ -10,6 +10,10 @@ namespace Barotrauma.Items.Components { partial class Pump : Powered, IServerSerializable, IClientSerializable { + public GUIScrollBar IsActiveSlider + { + get { return isActiveSlider; } + } private GUIScrollBar isActiveSlider; private GUIScrollBar pumpSpeedSlider; private GUITickBox powerIndicator; @@ -49,7 +53,6 @@ namespace Barotrauma.Items.Components }; var sliderHandle = isActiveSlider.GetChild(); sliderHandle.RectTransform.NonScaledSize = new Point(84, sliderHandle.Rect.Height); - isActiveSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => { bool active = scrollBar.BarScroll < 0.5f; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs index d46b2029a..b4d53d0e0 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs @@ -11,7 +11,16 @@ namespace Barotrauma.Items.Components { partial class Reactor : Powered, IServerSerializable, IClientSerializable { + public GUIScrollBar AutoTempSlider + { + get { return autoTempSlider; } + } private GUIScrollBar autoTempSlider; + + public GUIScrollBar OnOffSwitch + { + get { return onOffSwitch; } + } private GUIScrollBar onOffSwitch; private const int GraphSize = 25; @@ -27,7 +36,16 @@ namespace Barotrauma.Items.Components private Sprite graphLine; + public GUIScrollBar FissionRateScrollBar + { + get { return fissionRateScrollBar; } + } private GUIScrollBar fissionRateScrollBar; + + public GUIScrollBar TurbineOutputScrollBar + { + get { return turbineOutputScrollBar; } + } private GUIScrollBar turbineOutputScrollBar; private float[] outputGraph = new float[GraphSize]; @@ -554,6 +572,8 @@ namespace Barotrauma.Items.Components fissionRateScrollBar.BarScroll = targetFissionRate / 100.0f; turbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f; onOffSwitch.BarScroll = shutDown ? Math.Max(onOffSwitch.BarScroll, 0.55f) : Math.Min(onOffSwitch.BarScroll, 0.45f); + + IsActive = true; } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs index 9cc203358..0c7c9c058 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs @@ -20,6 +20,10 @@ namespace Barotrauma.Items.Components private bool unsentChanges; private float networkUpdateTimer; + public GUITickBox ActiveTickBox + { + get { return activeTickBox; } + } private GUITickBox activeTickBox, passiveTickBox; private GUITextBlock signalWarningText; @@ -439,11 +443,14 @@ namespace Barotrauma.Items.Components { var mission = GameMain.GameSession.Mission; - if (!string.IsNullOrWhiteSpace(mission.SonarLabel) && mission.SonarPosition != Vector2.Zero) + if (!string.IsNullOrWhiteSpace(mission.SonarLabel)) { - DrawMarker(spriteBatch, - mission.SonarLabel, - mission.SonarPosition - transducerCenter, displayScale, center, (rect.Width * 0.47f)); + foreach (Vector2 sonarPosition in mission.SonarPositions) + { + DrawMarker(spriteBatch, + mission.SonarLabel, + sonarPosition - transducerCenter, displayScale, center, (rect.Width * 0.47f)); + } } } @@ -785,15 +792,32 @@ namespace Barotrauma.Items.Components foreach (Character c in Character.CharacterList) { - if (c.AnimController.CurrentHull != null || !c.Enabled) continue; - if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) continue; + if (c.AnimController.CurrentHull != null || !c.Enabled) { continue; } + if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) { continue; } + + if (c.AnimController.SimplePhysicsEnabled) + { + float pointDist = ((c.WorldPosition - pingSource) * displayScale).LengthSquared(); + if (pointDist > DisplayRadius * DisplayRadius) { continue; } + + if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr) + { + var blip = new SonarBlip( + c.WorldPosition, + MathHelper.Clamp(c.Mass, 0.1f, pingStrength), + MathHelper.Clamp(c.Mass * 0.03f, 0.1f, 2.0f)); + if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; } + sonarBlips.Add(blip); + } + continue; + } foreach (Limb limb in c.AnimController.Limbs) { if (!limb.body.Enabled) { continue; } float pointDist = ((limb.WorldPosition - pingSource) * displayScale).LengthSquared(); - if (limb.SimPosition == Vector2.Zero || pointDist > DisplayRadius * DisplayRadius) continue; + if (limb.SimPosition == Vector2.Zero || pointDist > DisplayRadius * DisplayRadius) { continue; } if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr) { @@ -801,13 +825,13 @@ namespace Barotrauma.Items.Components limb.WorldPosition + Rand.Vector(limb.Mass / 10.0f), MathHelper.Clamp(limb.Mass, 0.1f, pingStrength), MathHelper.Clamp(limb.Mass * 0.1f, 0.1f, 2.0f)); - if (!passive && !CheckBlipVisibility(blip, transducerPos)) continue; + if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; } sonarBlips.Add(blip); } } } } - + private void CreateBlipsForLine(Vector2 point1, Vector2 point2, Vector2 pingSource, Vector2 transducerPos, float pingRadius, float prevPingRadius, float lineStep, float zStep, float range, float pingStrength, bool passive) { diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs index ddd2ffb29..5cde6d3ec 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs @@ -10,6 +10,10 @@ namespace Barotrauma.Items.Components { partial class Repairable : ItemComponent, IDrawableComponent { + public GUIButton RepairButton + { + get { return repairButton; } + } private GUIButton repairButton; private GUIProgressBar progressBar; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs index 53c247abe..f5a3c8999 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs @@ -18,6 +18,14 @@ namespace Barotrauma.Items.Components private static Wire draggingConnected; + private Color flashColor; + private float flashDuration = 1.5f; + public float FlashTimer + { + get { return flashTimer; } + } + private float flashTimer; + public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character) { Rectangle panelRect = panel.GuiFrame.Rect; @@ -174,14 +182,38 @@ namespace Barotrauma.Items.Components } } } - + + if (flashTimer > 0.0f) + { + //the number of flashes depends on the duration, 1 flash per 1 full second + int flashCycleCount = (int)Math.Max(flashDuration, 1); + float flashCycleDuration = flashDuration / flashCycleCount; + + //MathHelper.Pi * 0.8f -> the curve goes from 144 deg to 0, + //i.e. quickly bumps up from almost full brightness to full and then fades out + connectionSpriteHighlight.Draw(spriteBatch, position, flashColor * (float)Math.Sin(flashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f)); + } + if (Wires.Any(w => w != null && w != draggingConnected)) { int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count; screwSprites[screwIndex].Draw(spriteBatch, position); } } - + + public void Flash(Color? color = null, float flashDuration = 1.5f) + { + flashTimer = flashDuration; + this.flashDuration = flashDuration; + flashColor = (color == null) ? Color.Red : (Color)color; + } + + public void UpdateFlashTimer(float deltaTime) + { + if (flashTimer <= 0) return; + flashTimer -= deltaTime; + } + private static void DrawWire(SpriteBatch spriteBatch, Wire wire, Item item, Vector2 end, Vector2 start, bool mouseIn, Wire equippedWire, ConnectionPanel panel, string label) { if (draggingConnected == wire) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs index a74b5d3b8..124992e2c 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs @@ -3,6 +3,7 @@ using Lidgren.Network; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Xml.Linq; @@ -16,16 +17,22 @@ namespace Barotrauma.Items.Components { uiElements.Clear(); + var visibleElements = customInterfaceElementList.Where(ciElement => !string.IsNullOrEmpty(ciElement.Label)); + GUILayoutGroup paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), GuiFrame.RectTransform, Anchor.Center), childAnchor: customInterfaceElementList.Count > 1 ? Anchor.TopCenter : Anchor.Center) - { RelativeSpacing = 0.05f }; + { + RelativeSpacing = 0.05f, + Stretch = visibleElements.Count() > 2 + }; - float elementSize = Math.Min(1.0f / customInterfaceElementList.Count, 0.5f); - foreach (CustomInterfaceElement ciElement in customInterfaceElementList) + float elementSize = Math.Min(1.0f / visibleElements.Count(), 0.5f); + foreach (CustomInterfaceElement ciElement in visibleElements) { if (ciElement.ContinuousSignal) { - var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementSize), paddedFrame.RectTransform), ciElement.Label) + var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementSize), paddedFrame.RectTransform), + TextManager.Get(ciElement.Label, returnNull: true) ?? ciElement.Label) { UserData = ciElement }; @@ -45,7 +52,8 @@ namespace Barotrauma.Items.Components } else { - var btn = new GUIButton(new RectTransform(new Vector2(1.0f, elementSize), paddedFrame.RectTransform), ciElement.Label, style: "GUIButtonLarge") + var btn = new GUIButton(new RectTransform(new Vector2(1.0f, elementSize), paddedFrame.RectTransform), + TextManager.Get(ciElement.Label, returnNull: true) ?? ciElement.Label, style: "GUIButtonLarge") { UserData = ciElement }; @@ -66,6 +74,43 @@ namespace Barotrauma.Items.Components } } + public override void CreateEditingHUD(SerializableEntityEditor editor) + { + base.CreateEditingHUD(editor); + + PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(customInterfaceElementList[0]); + PropertyDescriptor labelProperty = properties.Find("Label", false); + PropertyDescriptor signalProperty = properties.Find("Signal", false); + for (int i = 0; i< customInterfaceElementList.Count; i++) + { + editor.CreateStringField(customInterfaceElementList[i], + new SerializableProperty(labelProperty, customInterfaceElementList[i]), + customInterfaceElementList[i].Label, "Label #" + (i + 1), ""); + editor.CreateStringField(customInterfaceElementList[i], + new SerializableProperty(signalProperty, customInterfaceElementList[i]), + customInterfaceElementList[i].Signal, "Signal #" + (i + 1), ""); + } + } + + public void HighlightElement(int index, Color color, float duration, float pulsateAmount = 0.0f) + { + if (index < 0 || index >= uiElements.Count) { return; } + uiElements[index].Flash(color, duration); + + if (pulsateAmount > 0.0f) + { + if (uiElements[index] is GUIButton button) + { + button.Frame.Pulsate(Vector2.One, Vector2.One * (1.0f + pulsateAmount), duration); + button.Frame.RectTransform.SetPosition(Anchor.Center); + } + else + { + uiElements[index].Pulsate(Vector2.One, Vector2.One * (1.0f + pulsateAmount), duration); + } + } + } + partial void UpdateLabelsProjSpecific() { for (int i = 0; i < labels.Length && i < uiElements.Count; i++) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs index 0f3bfa0d7..61d5bcc62 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs @@ -154,11 +154,22 @@ namespace Barotrauma.Items.Components Vector2 nodeWorldPos = GameMain.SubEditorScreen.Cam.ScreenToWorld(PlayerInput.MousePosition) - sub.HiddenSubPosition - sub.Position;// Nodes[(int)selectedNodeIndex]; - nodeWorldPos.X = MathUtils.Round(nodeWorldPos.X, Submarine.GridSize.X / 2.0f); - nodeWorldPos.Y = MathUtils.Round(nodeWorldPos.Y, Submarine.GridSize.Y / 2.0f); + if (selectedNodeIndex.HasValue) + { + nodeWorldPos.X = MathUtils.Round(nodeWorldPos.X, Submarine.GridSize.X / 2.0f); + nodeWorldPos.Y = MathUtils.Round(nodeWorldPos.Y, Submarine.GridSize.Y / 2.0f); + + draggingWire.nodes[(int)selectedNodeIndex] = nodeWorldPos; + draggingWire.UpdateSections(); + } + else + { + if (Vector2.DistanceSquared(nodeWorldPos, draggingWire.nodes[(int)highlightedNodeIndex]) > Submarine.GridSize.X * Submarine.GridSize.X) + { + selectedNodeIndex = highlightedNodeIndex; + } + } - draggingWire.nodes[(int)selectedNodeIndex] = nodeWorldPos; - draggingWire.UpdateSections(); MapEntity.SelectEntity(draggingWire.item); } @@ -213,7 +224,8 @@ namespace Barotrauma.Items.Components Character.Controlled.ClearInputs(); } draggingWire = selectedWire; - selectedNodeIndex = closestIndex; + //selectedNodeIndex = closestIndex; + return; } //remove the node else if (PlayerInput.RightButtonClicked() && closestIndex > 0 && closestIndex < selectedWire.nodes.Count - 1) @@ -259,19 +271,11 @@ namespace Barotrauma.Items.Components } } } - } + } if (highlighted != null) { highlighted.item.IsHighlighted = true; - - if (Character.Controlled != null) - { - Character.Controlled.FocusedItem = null; - Character.Controlled.ResetInteract = true; - Character.Controlled.ClearInputs(); - } - if (PlayerInput.LeftButtonClicked()) { MapEntity.DisableSelect = true; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs index 2e5a6dd04..b79bcda92 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs @@ -150,14 +150,14 @@ namespace Barotrauma.Items.Components { if (moveSoundChannel == null && startMoveSound != null) { - moveSoundChannel = SoundPlayer.PlaySound(startMoveSound.Sound, startMoveSound.Volume, startMoveSound.Range, item.WorldPosition); + moveSoundChannel = SoundPlayer.PlaySound(startMoveSound.Sound, item.WorldPosition, startMoveSound.Volume, startMoveSound.Range); } else if (moveSoundChannel == null || !moveSoundChannel.IsPlaying) { if (moveSound != null) { moveSoundChannel.FadeOutAndDispose(); - moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, moveSound.Volume, moveSound.Range, item.WorldPosition); + moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, item.WorldPosition, moveSound.Volume, moveSound.Range); if (moveSoundChannel != null) moveSoundChannel.Looping = true; } } @@ -169,7 +169,7 @@ namespace Barotrauma.Items.Components if (endMoveSound != null && moveSoundChannel.Sound != endMoveSound.Sound) { moveSoundChannel.FadeOutAndDispose(); - moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, endMoveSound.Volume, endMoveSound.Range, item.WorldPosition); + moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, item.WorldPosition, endMoveSound.Volume, endMoveSound.Range); if (moveSoundChannel != null) moveSoundChannel.Looping = false; } else if (!moveSoundChannel.IsPlaying) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index 9f6de7fde..8fb30030a 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -26,6 +26,7 @@ namespace Barotrauma public Color Color; public Color HighlightColor; + public float HighlightScaleUpAmount; private CoroutineHandle highlightCoroutine; public float HighlightTimer; @@ -80,7 +81,7 @@ namespace Barotrauma return rect.Contains(PlayerInput.MousePosition); } - public void ShowBorderHighlight(Color color, float fadeInDuration, float fadeOutDuration) + public void ShowBorderHighlight(Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount = 0.5f) { if (highlightCoroutine != null) { @@ -88,6 +89,7 @@ namespace Barotrauma highlightCoroutine = null; } + HighlightScaleUpAmount = scaleUpAmount; highlightCoroutine = CoroutineManager.StartCoroutine(UpdateBorderHighlight(color, fadeInDuration, fadeOutDuration)); } @@ -154,8 +156,6 @@ namespace Barotrauma public SlotReference(Inventory parentInventory, InventorySlot slot, int slotIndex, bool isSubSlot, Inventory subInventory = null) { - - ParentInventory = parentInventory; Slot = slot; SlotIndex = slotIndex; @@ -714,12 +714,15 @@ namespace Barotrauma float scale = Math.Min(Math.Min(iconSize / sprite.size.X, iconSize / sprite.size.Y), 1.5f); Vector2 itemPos = PlayerInput.MousePosition; - if (GUI.MouseOn == null && selectedSlot == null) + bool mouseOnHealthInterface = CharacterHealth.OpenHealthWindow != null && CharacterHealth.OpenHealthWindow.MouseOnElement; + + if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null) { var shadowSprite = GUI.Style.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0]; - string toolTip = Character.Controlled.FocusedItem != null ? - TextManager.Get("PutItemIn").Replace("[itemname]", Character.Controlled.FocusedItem.Name) : - TextManager.Get("DropItem"); + string toolTip = mouseOnHealthInterface ? TextManager.Get("QuickUseAction.UseTreatment") : + Character.Controlled.FocusedItem != null ? + TextManager.Get("PutItemIn").Replace("[itemname]", Character.Controlled.FocusedItem.Name) : + TextManager.Get("DropItem"); int textWidth = (int)Math.Max(GUI.Font.MeasureString(draggingItem.Name).X, GUI.SmallFont.MeasureString(toolTip).X); int textSpacing = (int)(15 * GUI.Scale); Point shadowBorders = (new Point(40, 10)).Multiply(GUI.Scale); @@ -727,7 +730,7 @@ namespace Barotrauma new Rectangle(itemPos.ToPoint() - new Point(iconSize / 2) - shadowBorders, new Point(iconSize + textWidth + textSpacing, iconSize) + shadowBorders.Multiply(2)), Color.Black * 0.8f); GUI.DrawString(spriteBatch, new Vector2(itemPos.X + iconSize / 2 + textSpacing, itemPos.Y - iconSize / 2), draggingItem.Name, Color.White); GUI.DrawString(spriteBatch, new Vector2(itemPos.X + iconSize / 2 + textSpacing, itemPos.Y), toolTip, - color: Character.Controlled.FocusedItem == null ? Color.Red : Color.LightGreen, + color: Character.Controlled.FocusedItem == null && !mouseOnHealthInterface ? Color.Red : Color.LightGreen, font: GUI.SmallFont); } sprite.Draw(spriteBatch, itemPos + Vector2.One * 2, Color.Black, scale: scale); @@ -799,8 +802,7 @@ namespace Barotrauma if (slot.HighlightColor.A > 0) { - float scaleUpAmount = 0.5f; - float inflateAmount = (slot.HighlightColor.A / 255.0f) * scaleUpAmount * 0.5f; + float inflateAmount = (slot.HighlightColor.A / 255.0f) * slot.HighlightScaleUpAmount * 0.5f; rect.Inflate(rect.Width * inflateAmount, rect.Height * inflateAmount); } @@ -853,7 +855,7 @@ namespace Barotrauma if (itemContainer.ContainedStateIndicator?.Texture == null) { containedIndicatorArea.Inflate(0, -2); - GUI.DrawRectangle(spriteBatch, containedIndicatorArea, Color.DarkGray * 0.8f, true); + GUI.DrawRectangle(spriteBatch, containedIndicatorArea, Color.DarkGray * 0.9f, true); GUI.DrawRectangle(spriteBatch, new Rectangle(containedIndicatorArea.X, containedIndicatorArea.Y, (int)(containedIndicatorArea.Width * containedState), containedIndicatorArea.Height), Color.Lerp(Color.Red, Color.Green, containedState) * 0.8f, true); @@ -867,11 +869,11 @@ namespace Barotrauma if (containedState > 0.0f && containedState < 0.25f) { - indicatorScale += ((float)Math.Sin(Timing.TotalTime * 5.0f) + 1.0f) * 0.1f; + indicatorScale += ((float)Math.Sin(Timing.TotalTime * 5.0f) + 1.0f) * 0.25f; } indicatorSprite.Draw(spriteBatch, containedIndicatorArea.Center.ToVector2(), - Color.DarkGray * 0.6f, + Color.DarkGray * 0.9f, origin: indicatorSprite.size / 2, rotate: 0.0f, scale: indicatorScale); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index ee2a4d152..07b2f78eb 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -101,9 +101,22 @@ namespace Barotrauma return color; } - partial void SetActiveSprite() + partial void SetActiveSpriteProjSpecific() { activeSprite = prefab.sprite; + Holdable holdable = GetComponent(); + if (holdable != null && holdable.Attached) + { + foreach (ContainedItemSprite containedSprite in Prefab.ContainedSprites) + { + if (containedSprite.UseWhenAttached) + { + activeSprite = containedSprite.Sprite; + return; + } + } + } + if (Container != null) { foreach (ContainedItemSprite containedSprite in Prefab.ContainedSprites) @@ -171,10 +184,9 @@ namespace Barotrauma if (!Visible || (!editing && hiddenInGame)) return; if (editing && !ShowItems) return; - Color color = isHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? Color.Orange : GetSpriteColor(); + Color color = IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? Color.Orange : GetSpriteColor(); //if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f); - - Sprite activeSprite = prefab.sprite; + BrokenItemSprite fadeInBrokenSprite = null; float fadeInBrokenSpriteAlpha = 0.0f; if (condition < Prefab.Health) @@ -372,7 +384,7 @@ namespace Barotrauma Timing.TotalTime > LastImpactSoundTime + ImpactSoundInterval) { LastImpactSoundTime = (float)Timing.TotalTime; - SoundPlayer.PlaySound(Prefab.ImpactSoundTag, 1.0f, 500.0f, WorldPosition, CurrentHull); + SoundPlayer.PlaySound(Prefab.ImpactSoundTag, WorldPosition, hullGuess: CurrentHull); } } @@ -565,8 +577,13 @@ namespace Barotrauma } var componentEditor = new SerializableEntityEditor(listBox.Content.RectTransform, ic, inGame, showName: !inGame); - - if (inGame) continue; + + if (inGame) + { + ic.CreateEditingHUD(componentEditor); + componentEditor.Recalculate(); + continue; + } foreach (var kvp in ic.requiredItems) { @@ -599,6 +616,9 @@ namespace Barotrauma }; } } + + ic.CreateEditingHUD(componentEditor); + componentEditor.Recalculate(); } PositionEditingHUD(); @@ -783,7 +803,7 @@ namespace Barotrauma { if (!ic.CanBeSelected) { continue; } - bool useAlternativeLayout = ic.Item != this; + bool useAlternativeLayout = activeHUDs.Count > 1; bool wasUsingAlternativeLayout = ic.UseAlternativeLayout; ic.UseAlternativeLayout = useAlternativeLayout; needsLayoutUpdate |= ic.UseAlternativeLayout != wasUsingAlternativeLayout; @@ -817,14 +837,28 @@ namespace Barotrauma case NetEntityEvent.Type.ComponentState: { int componentIndex = msg.ReadRangedInteger(0, components.Count - 1); - (components[componentIndex] as IServerSerializable).ClientRead(type, msg, sendingTime); + if (components[componentIndex] is IServerSerializable serverSerializable) + { + serverSerializable.ClientRead(type, msg, sendingTime); + } + else + { + throw new Exception("Failed to read component state - " + components[componentIndex].GetType() + " is not IServerSerializable."); + } } break; - + case NetEntityEvent.Type.InventoryState: - { + { int containerIndex = msg.ReadRangedInteger(0, components.Count - 1); - (components[containerIndex] as ItemContainer).Inventory.ClientRead(type, msg, sendingTime); + if (components[containerIndex] is ItemContainer container) + { + container.Inventory.ClientRead(type, msg, sendingTime); + } + else + { + throw new Exception("Failed to read inventory state - " + components[containerIndex].GetType() + " is not an ItemContainer."); + } } break; case NetEntityEvent.Type.Status: diff --git a/Barotrauma/BarotraumaClient/Source/Items/ItemPrefab.cs b/Barotrauma/BarotraumaClient/Source/Items/ItemPrefab.cs index 53cdf7c63..d57d32a62 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/ItemPrefab.cs @@ -25,12 +25,14 @@ namespace Barotrauma class ContainedItemSprite { public readonly Sprite Sprite; + public readonly bool UseWhenAttached; public readonly string[] AllowedContainerIdentifiers; public readonly string[] AllowedContainerTags; public ContainedItemSprite(XElement element, string path = "", bool lazyLoad = false) { Sprite = new Sprite(element, path, lazyLoad: lazyLoad); + UseWhenAttached = element.GetAttributeBool("usewhenattached", false); AllowedContainerIdentifiers = element.GetAttributeStringArray("allowedcontaineridentifiers", new string[0], convertToLowerInvariant: true); AllowedContainerTags = element.GetAttributeStringArray("allowedcontainertags", new string[0], convertToLowerInvariant: true); } @@ -231,11 +233,11 @@ namespace Barotrauma } } - public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f) + public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, SpriteEffects spriteEffects = SpriteEffects.None) { if (!ResizeHorizontal && !ResizeVertical) { - sprite.Draw(spriteBatch, new Vector2(placeRect.Center.X, -(placeRect.Y - placeRect.Height / 2)), SpriteColor * 0.8f, scale: Scale * scale); + sprite.Draw(spriteBatch, new Vector2(placeRect.Center.X, -(placeRect.Y - placeRect.Height / 2)), SpriteColor * 0.8f, scale: scale); } else { diff --git a/Barotrauma/BarotraumaClient/Source/Map/Gap.cs b/Barotrauma/BarotraumaClient/Source/Map/Gap.cs index 10614b898..c158c9199 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Gap.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Gap.cs @@ -33,7 +33,7 @@ namespace Barotrauma if (!editing || !ShowGaps) return; Color clr = (open == 0.0f) ? Color.Red : Color.Cyan; - if (isHighlighted) clr = Color.Gold; + if (IsHighlighted) clr = Color.Gold; float depth = (ID % 255) * 0.000001f; diff --git a/Barotrauma/BarotraumaClient/Source/Map/Hull.cs b/Barotrauma/BarotraumaClient/Source/Map/Hull.cs index 8dc7700f0..252662461 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Hull.cs @@ -292,12 +292,12 @@ namespace Barotrauma }*/ } - if ((IsSelected || isHighlighted) && editing) + if ((IsSelected || IsHighlighted) && editing) { GUI.DrawRectangle(spriteBatch, new Vector2(drawRect.X + 5, -drawRect.Y + 5), new Vector2(rect.Width - 10, rect.Height - 10), - isHighlighted ? Color.LightBlue * 0.5f : Color.Red * 0.5f, true, 0, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f)); + IsHighlighted ? Color.LightBlue * 0.5f : Color.Red * 0.5f, true, 0, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f)); } foreach (MapEntity e in linkedTo) diff --git a/Barotrauma/BarotraumaClient/Source/Map/ItemAssemblyPrefab.cs b/Barotrauma/BarotraumaClient/Source/Map/ItemAssemblyPrefab.cs index 7c8bfc1e6..02184babd 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/ItemAssemblyPrefab.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/ItemAssemblyPrefab.cs @@ -22,7 +22,7 @@ namespace Barotrauma drawRect = new Rectangle( (int)(drawRect.X * scale) + drawArea.Center.X, -((int)((drawRect.Y - drawRect.Height) * scale) + drawArea.Center.Y), (int)(drawRect.Width * scale), (int)(drawRect.Height * scale)); - entity.First.DrawPlacing(spriteBatch, drawRect, scale); + entity.First.DrawPlacing(spriteBatch, drawRect, entity.First.Scale * scale); } } @@ -34,7 +34,7 @@ namespace Barotrauma { Rectangle drawRect = entity.Second; drawRect.Location += Submarine.MouseToWorldGrid(cam, Submarine.MainSub).ToPoint(); - entity.First.DrawPlacing(spriteBatch, drawRect); + entity.First.DrawPlacing(spriteBatch, drawRect, entity.First.Scale); } } diff --git a/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs index 0b4f41505..4f94c4362 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs @@ -105,7 +105,7 @@ namespace Barotrauma if (Vector2.DistanceSquared(bodyPos, levelWall.Body.Position) > 0.5f) { - levelWall.Body.SetTransform(bodyPos, levelWall.Body.Rotation); + levelWall.Body.SetTransformIgnoreContacts(ref bodyPos, levelWall.Body.Rotation); } } } diff --git a/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelRenderer.cs b/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelRenderer.cs index f17838929..28a1f7b89 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelRenderer.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelRenderer.cs @@ -217,7 +217,7 @@ namespace Barotrauma spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, - SamplerState.LinearClamp, DepthStencilState.Default, null, null, + SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null, cam.Transform); if (backgroundSpriteManager != null) backgroundSpriteManager.DrawObjects(spriteBatch, cam, drawFront: true); spriteBatch.End(); diff --git a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs index 49fa1d587..5ce895e7b 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs @@ -495,6 +495,8 @@ namespace Barotrauma.Lights spriteBatch.Draw(backgroundObstructor, new Rectangle(0, 0, (int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)), Color.White); } + spriteBatch.End(); + spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, effect: SolidColorEffect, transformMatrix: spriteBatchTransform); foreach (Character c in Character.CharacterList) { if (c.Enabled) { c.Draw(spriteBatch, cam); } diff --git a/Barotrauma/BarotraumaClient/Source/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaClient/Source/Map/LinkedSubmarine.cs index 0349f19dc..7b311c66b 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/LinkedSubmarine.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/LinkedSubmarine.cs @@ -13,7 +13,7 @@ namespace Barotrauma { if (!editing || wallVertices == null) return; - Color color = (isHighlighted) ? Color.Orange : Color.Green; + Color color = (IsHighlighted) ? Color.Orange : Color.Green; if (IsSelected) color = Color.Red; Vector2 pos = Position; diff --git a/Barotrauma/BarotraumaClient/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/Source/Map/MapEntity.cs index a49b60add..cfa1a5486 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/MapEntity.cs @@ -504,8 +504,19 @@ namespace Barotrauma { foreach (MapEntity e in selectedList) { + SpriteEffects spriteEffects = SpriteEffects.None; + if (e is Item item) + { + if (item.FlippedX && item.Prefab.CanSpriteFlipX) spriteEffects ^= SpriteEffects.FlipHorizontally; + if (item.flippedY && item.Prefab.CanSpriteFlipY) spriteEffects ^= SpriteEffects.FlipVertically; + } + else if (e is Structure structure) + { + if (structure.FlippedX && structure.Prefab.CanSpriteFlipX) spriteEffects ^= SpriteEffects.FlipHorizontally; + if (structure.flippedY && structure.Prefab.CanSpriteFlipY) spriteEffects ^= SpriteEffects.FlipVertically; + } e.prefab?.DrawPlacing(spriteBatch, - new Rectangle(e.WorldRect.Location + new Point((int)moveAmount.X, (int)-moveAmount.Y), e.WorldRect.Size)); + new Rectangle(e.WorldRect.Location + new Point((int)moveAmount.X, (int)-moveAmount.Y), e.WorldRect.Size), e.Scale, spriteEffects); GUI.DrawRectangle(spriteBatch, new Vector2(e.WorldRect.X, -e.WorldRect.Y) + moveAmount, new Vector2(e.rect.Width, e.rect.Height), diff --git a/Barotrauma/BarotraumaClient/Source/Map/MapEntityPrefab.cs b/Barotrauma/BarotraumaClient/Source/Map/MapEntityPrefab.cs index 2d2a03e2b..97786e794 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/MapEntityPrefab.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/MapEntityPrefab.cs @@ -37,7 +37,7 @@ namespace Barotrauma } } - public virtual void DrawPlacing(SpriteBatch spriteBatch, Rectangle drawRect, float scale = 1.0f) + public virtual void DrawPlacing(SpriteBatch spriteBatch, Rectangle drawRect, float scale = 1.0f, SpriteEffects spriteEffects = SpriteEffects.None) { if (Submarine.MainSub != null) { diff --git a/Barotrauma/BarotraumaClient/Source/Map/Structure.cs b/Barotrauma/BarotraumaClient/Source/Map/Structure.cs index 69c09b594..11e85a147 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Structure.cs @@ -1,4 +1,5 @@ -using Barotrauma.Lights; +using Barotrauma.Extensions; +using Barotrauma.Lights; using Barotrauma.Networking; using Lidgren.Network; using Microsoft.Xna.Framework; @@ -42,6 +43,14 @@ namespace Barotrauma } } + private string specialTag; + [Editable, Serialize("", true)] + public string SpecialTag + { + get { return specialTag; } + set { specialTag = value; } + } + // Only for testing in the debug build. Not saved. #if DEBUG [Editable, Serialize(true, false)] @@ -101,7 +110,7 @@ namespace Barotrauma editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this }; GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null); var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, elementHeight: 20); - + var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, 20)), isHorizontal: true) { Stretch = true, @@ -186,7 +195,7 @@ namespace Barotrauma if (HasBody && !ShowWalls) return; } - Color color = isHighlighted ? Color.Orange : spriteColor; + Color color = IsHighlighted ? Color.Orange : spriteColor; if (IsSelected && editing) { //color = Color.Lerp(color, Color.Gold, 0.5f); @@ -215,80 +224,54 @@ namespace Barotrauma { if (Prefab.BackgroundSprite != null) { - bool drawDropShadow = Submarine != null && HasBody; Vector2 dropShadowOffset = Vector2.Zero; - if (drawDropShadow) + if (UseDropShadow) { - dropShadowOffset = Submarine.HiddenSubPosition - Position; - if (dropShadowOffset != Vector2.Zero) + dropShadowOffset = DropShadowOffset; + if (dropShadowOffset == Vector2.Zero) { - if (IsHorizontal) + if (Submarine == null) { - dropShadowOffset = new Vector2(0.0f, Math.Sign(dropShadowOffset.Y) * 10.0f); + dropShadowOffset = Vector2.UnitY * 10.0f; } else { - dropShadowOffset = new Vector2(Math.Sign(dropShadowOffset.X) * 10.0f, 0.0f); + dropShadowOffset = IsHorizontal ? + new Vector2(0.0f, Math.Sign(Submarine.HiddenSubPosition.Y - Position.Y) * 10.0f) : + new Vector2(Math.Sign(Submarine.HiddenSubPosition.X - Position.X) * 10.0f, 0.0f); } - dropShadowOffset.Y = -dropShadowOffset.Y; } + dropShadowOffset.Y = -dropShadowOffset.Y; } + + SpriteEffects oldEffects = Prefab.BackgroundSprite.effects; + Prefab.BackgroundSprite.effects ^= SpriteEffects; - if (DrawTiled) + Point backGroundOffset = new Point( + MathUtils.PositiveModulo((int)-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width), + MathUtils.PositiveModulo((int)-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height)); + + Prefab.BackgroundSprite.DrawTiled( + spriteBatch, + new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)), + new Vector2(rect.Width, rect.Height), + color: color, + textureScale: TextureScale * Scale, + startOffset: backGroundOffset); + + if (UseDropShadow) { - SpriteEffects oldEffects = Prefab.BackgroundSprite.effects; - Prefab.BackgroundSprite.effects ^= SpriteEffects; - - Point backGroundOffset = new Point( - MathUtils.PositiveModulo((int)-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width), - MathUtils.PositiveModulo((int)-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height)); - Prefab.BackgroundSprite.DrawTiled( spriteBatch, - new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)), + new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)) + dropShadowOffset, new Vector2(rect.Width, rect.Height), - color: color, + color: Color.Black * 0.5f, textureScale: TextureScale * Scale, - startOffset: backGroundOffset); - - if (drawDropShadow) - { - Prefab.BackgroundSprite.DrawTiled( - spriteBatch, - new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)) + dropShadowOffset, - new Vector2(rect.Width, rect.Height), - color: Color.Black * 0.5f, - textureScale: TextureScale * Scale, - startOffset: backGroundOffset, - depth: (depth + Prefab.BackgroundSprite.Depth) / 2.0f); - } - - Prefab.BackgroundSprite.effects = oldEffects; + startOffset: backGroundOffset, + depth: (depth + Prefab.BackgroundSprite.Depth) / 2.0f); } - else - { - Prefab.BackgroundSprite.Draw( - spriteBatch, - new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)), - color, - Vector2.Zero, - scale: Scale, - rotate: 0, - spriteEffect: SpriteEffects); - if (drawDropShadow) - { - Prefab.BackgroundSprite.Draw( - spriteBatch, - new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)) + dropShadowOffset, - Color.Black * 0.5f, - Vector2.Zero, - scale: Scale, - rotate: 0, - spriteEffect: SpriteEffects, - depth: (depth + Prefab.BackgroundSprite.Depth) / 2.0f); - } - } + Prefab.BackgroundSprite.effects = oldEffects; } } @@ -315,39 +298,25 @@ namespace Barotrauma Submarine.DamageEffectColor = color; } } + + Point sectionOffset = new Point( + Math.Abs(rect.Location.X - Sections[i].rect.Location.X), + Math.Abs(rect.Location.Y - Sections[i].rect.Location.Y)); - if (DrawTiled) - { - Point sectionOffset = new Point( - Math.Abs(rect.Location.X - Sections[i].rect.Location.X), - Math.Abs(rect.Location.Y - Sections[i].rect.Location.Y)); + if (FlippedX && IsHorizontal) sectionOffset.X = Sections[i].rect.Right - rect.Right; + if (FlippedY && !IsHorizontal) sectionOffset.Y = (rect.Y - rect.Height) - (Sections[i].rect.Y - Sections[i].rect.Height); - if (FlippedX && IsHorizontal) sectionOffset.X = Sections[i].rect.Right - rect.Right; - if (FlippedY && !IsHorizontal) sectionOffset.Y = (rect.Y - rect.Height) - (Sections[i].rect.Y - Sections[i].rect.Height); + sectionOffset.X += MathUtils.PositiveModulo((int)-textureOffset.X, prefab.sprite.SourceRect.Width); + sectionOffset.Y += MathUtils.PositiveModulo((int)-textureOffset.Y, prefab.sprite.SourceRect.Height); - sectionOffset.X += MathUtils.PositiveModulo((int)-textureOffset.X, prefab.sprite.SourceRect.Width); - sectionOffset.Y += MathUtils.PositiveModulo((int)-textureOffset.Y, prefab.sprite.SourceRect.Height); - - prefab.sprite.DrawTiled( - spriteBatch, - new Vector2(Sections[i].rect.X + drawOffset.X, -(Sections[i].rect.Y + drawOffset.Y)), - new Vector2(Sections[i].rect.Width, Sections[i].rect.Height), - color: color, - startOffset: sectionOffset, - depth: depth, - textureScale: TextureScale * Scale); - } - else - { - prefab.sprite.Draw( - spriteBatch, - new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)), - color, - Vector2.Zero, - scale: Scale, - rotate: 0, - spriteEffect: SpriteEffects); - } + prefab.sprite.DrawTiled( + spriteBatch, + new Vector2(Sections[i].rect.X + drawOffset.X, -(Sections[i].rect.Y + drawOffset.Y)), + new Vector2(Sections[i].rect.Width, Sections[i].rect.Height), + color: color, + startOffset: sectionOffset, + depth: depth, + textureScale: TextureScale * Scale); } prefab.sprite.effects = oldEffects; } @@ -368,6 +337,20 @@ namespace Barotrauma -Bodies[i].Rotation, Color.White); } } + + if (SectionCount > 0 && HasBody) + { + for (int i = 0; i < SectionCount; i++) + { + if (GetSection(i).damage > 0) + { + var textPos = SectionPosition(i, true); + textPos.Y = -textPos.Y; + GUI.DrawString(spriteBatch, textPos, "Damage: " + (int)((GetSection(i).damage / Health) * 100f) + "%", Color.Yellow); + } + } + } + AiTarget?.Draw(spriteBatch); } } diff --git a/Barotrauma/BarotraumaClient/Source/Map/StructurePrefab.cs b/Barotrauma/BarotraumaClient/Source/Map/StructurePrefab.cs index 4863ea754..36ba5a87c 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/StructurePrefab.cs @@ -32,10 +32,19 @@ namespace Barotrauma GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - GameMain.GraphicsHeight, newRect.Width, newRect.Height + GameMain.GraphicsHeight * 2), Color.White); } - public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f) + public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, SpriteEffects spriteEffects = SpriteEffects.None) { - // TODO: the scale property is not used - sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), new Vector2(placeRect.Width, placeRect.Height), color: Color.White * 0.8f, textureScale: TextureScale * Scale); + SpriteEffects oldEffects = sprite.effects; + sprite.effects ^= spriteEffects; + + sprite.DrawTiled( + spriteBatch, + new Vector2(placeRect.X, -placeRect.Y), + new Vector2(placeRect.Width, placeRect.Height), + color: Color.White * 0.8f, + textureScale: TextureScale * scale); + + sprite.effects = oldEffects; } } } diff --git a/Barotrauma/BarotraumaClient/Source/Map/SubmarineBody.cs b/Barotrauma/BarotraumaClient/Source/Map/SubmarineBody.cs index 8c7476ed8..864b9dd92 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/SubmarineBody.cs @@ -42,10 +42,17 @@ namespace Barotrauma bool displace = moveAmount.LengthSquared() > 100.0f * 100.0f; foreach (Submarine sub in subsToMove) { - sub.PhysicsBody.SetTransform(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f); sub.PhysicsBody.LinearVelocity = newVelocity; - if (displace) sub.SubBody.DisplaceCharacters(moveAmount); + if (displace) + { + sub.PhysicsBody.SetTransform(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f); + sub.SubBody.DisplaceCharacters(moveAmount); + } + else + { + sub.PhysicsBody.SetTransformIgnoreContacts(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f); + } } if (closestSub != null && subsToMove.Contains(closestSub)) @@ -55,7 +62,6 @@ namespace Barotrauma if (Character.Controlled != null) Character.Controlled.CursorPosition += moveAmount; } - } } } diff --git a/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs b/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs index 25fb723e0..fc1cb229c 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/WayPoint.cs @@ -30,7 +30,7 @@ namespace Barotrauma Color clr = currentHull == null ? Color.Blue : Color.White; if (IsSelected) clr = Color.Red; - if (isHighlighted) clr = Color.DarkRed; + if (IsHighlighted) clr = Color.DarkRed; int iconX = iconIndices[(int)spawnType] * IconSize % iconTexture.Width; int iconY = (int)(Math.Floor(iconIndices[(int)spawnType] * IconSize / (float)iconTexture.Width)) * IconSize; diff --git a/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs index 8b80fc766..4a560e126 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/ChatMessage.cs @@ -61,7 +61,7 @@ namespace Barotrauma.Networking { orderOption = order.Options[optionIndex]; } - txt = order.GetChatMessage(targetCharacter?.Name, senderCharacter?.CurrentHull?.RoomName, givingOrderToSelf: targetCharacter == senderCharacter, orderOption: orderOption); + txt = order.GetChatMessage(targetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == senderCharacter, orderOption: orderOption); if (order.TargetAllCharacters) { diff --git a/Barotrauma/BarotraumaClient/Source/Networking/Client.cs b/Barotrauma/BarotraumaClient/Source/Networking/Client.cs index 9d69ff7ce..55bc409f0 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/Client.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/Client.cs @@ -39,25 +39,33 @@ namespace Barotrauma.Networking public void UpdateSoundPosition() { - if (VoipSound != null) + if (VoipSound == null) { return; } + + if (!VoipSound.IsPlaying) { - if (!VoipSound.IsPlaying) - { - DebugConsole.Log("Destroying voipsound"); - VoipSound.Dispose(); - VoipSound = null; - return; - } + DebugConsole.Log("Destroying voipsound"); + VoipSound.Dispose(); + VoipSound = null; + return; + } - if (character != null) + if (character != null) + { + if (GameMain.Config.UseDirectionalVoiceChat) { VoipSound.SetPosition(new Vector3(character.WorldPosition.X, character.WorldPosition.Y, 0.0f)); } else { VoipSound.SetPosition(null); + float dist = Vector3.Distance(new Vector3(character.WorldPosition, 0.0f), GameMain.SoundManager.ListenerPosition); + VoipSound.Gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist); } } + else + { + VoipSound.SetPosition(null); + } } partial void InitProjSpecific() diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 81e786f93..e0e35f159 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -26,6 +26,7 @@ namespace Barotrauma.Networking //TODO: move these to NetLobbyScreen public GUIButton EndRoundButton; public GUITickBox EndVoteTickBox; + private GUIComponent buttonContainer; private NetStats netStats; @@ -129,7 +130,7 @@ namespace Barotrauma.Networking chatBox.OnEnterMessage += EnterChatMessage; chatBox.InputBox.OnTextChanged += TypingChatMessage; - var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, inGameHUD.RectTransform), + buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, inGameHUD.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterRight) { AbsoluteSpacing = 5, @@ -630,13 +631,11 @@ namespace Barotrauma.Networking } else { - GameMain.GameSession?.CrewManager?.SetPlayerSpeaking(myClient); + GameMain.GameSession?.CrewManager?.SetClientSpeaking(myClient); } } } - if (gameStarted) SetRadioButtonColor(); - if (ShowNetStats && client?.ServerConnection != null) { netStats.AddValue(NetStats.NetStatType.ReceivedBytes, client.ServerConnection.Statistics.ReceivedBytes); @@ -1114,7 +1113,9 @@ namespace Barotrauma.Networking if (campaign == null) { - GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, missionIndex < 0 ? null : MissionPrefab.List[missionIndex]); + GameMain.GameSession = missionIndex < 0 ? + new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, MissionType.None) : + new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, MissionPrefab.List[missionIndex]); GameMain.GameSession.StartRound(levelSeed, levelDifficulty, loadSecondSub); } else @@ -1126,6 +1127,18 @@ namespace Barotrauma.Networking mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); } + for (int i = 0; i < Submarine.MainSubs.Length; i++) + { + if (!loadSecondSub && i > 0) { break; } + + var teamID = i == 0 ? Character.TeamType.Team1 : Character.TeamType.Team2; + Submarine.MainSubs[i].TeamID = teamID; + foreach (Submarine sub in Submarine.MainSubs[i].DockedTo) + { + sub.TeamID = teamID; + } + } + if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal) { string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ")."; @@ -2110,9 +2123,7 @@ namespace Barotrauma.Networking protected GUIFrame inGameHUD; protected ChatBox chatBox; public GUIButton ShowLogButton; //TODO: move to NetLobbyScreen - - private float myCharacterFrameOpenState; - + public GUIFrame InGameHUD { get { return inGameHUD; } @@ -2122,22 +2133,7 @@ namespace Barotrauma.Networking { get { return chatBox; } } - - protected void SetRadioButtonColor() - { - if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) - { - chatBox.RadioButton.GetChild().Color = new Color(60, 60, 60, 255); - } - else - { - var radioItem = Character.Controlled?.Inventory?.Items.FirstOrDefault(i => i?.GetComponent() != null); - chatBox.RadioButton.GetChild().Color = - (radioItem != null && Character.Controlled.HasEquippedItem(radioItem) && radioItem.GetComponent().CanTransmit()) ? - Color.White : new Color(60, 60, 60, 255); - } - } - + public bool TypingChatMessage(GUITextBox textBox, string text) { return chatBox.TypingChatMessage(textBox, text); @@ -2169,11 +2165,6 @@ namespace Barotrauma.Networking Screen.Selected == GameMain.GameScreen) { inGameHUD.AddToGUIUpdateList(); - - if (Character.Controlled == null) - { - GameMain.NetLobbyScreen.MyCharacterFrame.AddToGUIUpdateList(); - } } } @@ -2192,24 +2183,17 @@ namespace Barotrauma.Networking if (gameStarted && Screen.Selected == GameMain.GameScreen) { + bool disableButtons = + Character.Controlled != null && + Character.Controlled.SelectedConstruction?.GetComponent() != null; + buttonContainer.Visible = !disableButtons; + if (!GUI.DisableHUD && !GUI.DisableUpperHUD) { inGameHUD.UpdateManually(deltaTime); chatBox.Update(deltaTime); cameraFollowsSub.Visible = Character.Controlled == null; - - if (Character.Controlled == null) - { - myCharacterFrameOpenState = GameMain.NetLobbyScreen.MyCharacterFrameOpen ? myCharacterFrameOpenState + deltaTime * 5 : myCharacterFrameOpenState - deltaTime * 5; - myCharacterFrameOpenState = MathHelper.Clamp(myCharacterFrameOpenState, 0.0f, 1.0f); - - var myCharFrame = GameMain.NetLobbyScreen.MyCharacterFrame; - int padding = GameMain.GraphicsWidth - myCharFrame.Parent.Rect.Right; - - myCharFrame.RectTransform.AbsoluteOffset = - Vector2.SmoothStep(new Vector2(-myCharFrame.Rect.Width - padding, 0.0f), new Vector2(-padding, 0), myCharacterFrameOpenState).ToPoint(); - } } if (Character.Controlled == null || Character.Controlled.IsDead) { @@ -2246,7 +2230,47 @@ namespace Barotrauma.Networking public virtual void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) { - if (!gameStarted || Screen.Selected != GameMain.GameScreen || GUI.DisableHUD || GUI.DisableUpperHUD) return; + if (GUI.DisableHUD || GUI.DisableUpperHUD) return; + + if (fileReceiver != null && fileReceiver.ActiveTransfers.Count > 0) + { + Vector2 downloadBarSize = new Vector2(250, 35) * GUI.Scale; + Vector2 pos = new Vector2(GameMain.NetLobbyScreen.InfoFrame.Rect.X, GameMain.GraphicsHeight - downloadBarSize.Y - 5); + + GUI.DrawRectangle(spriteBatch, new Rectangle( + (int)pos.X, + (int)pos.Y, + (int)(fileReceiver.ActiveTransfers.Count * (downloadBarSize.X + 10)), + (int)downloadBarSize.Y), + Color.Black * 0.8f, true); + + for (int i = 0; i < fileReceiver.ActiveTransfers.Count; i++) + { + var transfer = fileReceiver.ActiveTransfers[i]; + + GUI.DrawString(spriteBatch, + pos, + ToolBox.LimitString(TextManager.Get("DownloadingFile").Replace("[filename]", transfer.FileName), GUI.SmallFont, (int)downloadBarSize.X), + Color.White, null, 0, GUI.SmallFont); + GUI.DrawProgressBar(spriteBatch, new Vector2(pos.X, -pos.Y - downloadBarSize.Y / 2), new Vector2(downloadBarSize.X * 0.7f, downloadBarSize.Y / 2), transfer.Progress, Color.Green); + GUI.DrawString(spriteBatch, pos + new Vector2(5, downloadBarSize.Y / 2), + MathUtils.GetBytesReadable((long)transfer.Received) + " / " + MathUtils.GetBytesReadable((long)transfer.FileSize), + Color.White, null, 0, GUI.SmallFont); + + if (GUI.DrawButton(spriteBatch, new Rectangle( + (int)(pos.X + downloadBarSize.X * 0.7f), (int)(pos.Y + downloadBarSize.Y / 2), + (int)(downloadBarSize.X * 0.3f), (int)(downloadBarSize.Y / 2)), + TextManager.Get("Cancel"), new Color(0.47f, 0.13f, 0.15f, 0.08f))) + { + CancelFileTransfer(transfer); + fileReceiver.StopTransfer(transfer); + } + + pos.X += (downloadBarSize.X + 10); + } + } + + if (!gameStarted || Screen.Selected != GameMain.GameScreen) return; inGameHUD.DrawManually(spriteBatch); @@ -2297,40 +2321,6 @@ namespace Barotrauma.Networking } } - if (fileReceiver != null && fileReceiver.ActiveTransfers.Count > 0) - { - Vector2 pos = new Vector2(GameMain.NetLobbyScreen.InfoFrame.Rect.X, GameMain.GraphicsHeight - 35); - - GUI.DrawRectangle(spriteBatch, new Rectangle( - (int)pos.X, - (int)pos.Y, - fileReceiver.ActiveTransfers.Count * 210 + 10, - 32), - Color.Black * 0.8f, true); - - for (int i = 0; i < fileReceiver.ActiveTransfers.Count; i++) - { - var transfer = fileReceiver.ActiveTransfers[i]; - - GUI.DrawString(spriteBatch, - pos, - ToolBox.LimitString(TextManager.Get("DownloadingFile").Replace("[filename]", transfer.FileName), GUI.SmallFont, 200), - Color.White, null, 0, GUI.SmallFont); - GUI.DrawProgressBar(spriteBatch, new Vector2(pos.X, -pos.Y - 15), new Vector2(135, 15), transfer.Progress, Color.Green); - GUI.DrawString(spriteBatch, pos + new Vector2(5, 15), - MathUtils.GetBytesReadable((long)transfer.Received) + " / " + MathUtils.GetBytesReadable((long)transfer.FileSize), - Color.White, null, 0, GUI.SmallFont); - - if (GUI.DrawButton(spriteBatch, new Rectangle((int)pos.X + 140, (int)pos.Y + 18, 60, 15), TextManager.Get("Cancel"), new Color(0.47f, 0.13f, 0.15f, 0.08f))) - { - CancelFileTransfer(transfer); - fileReceiver.StopTransfer(transfer); - } - - pos.X += 210; - } - } - if (!ShowNetStats) return; netStats.Draw(spriteBatch, new Rectangle(300, 10, 300, 150)); diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index d79c682de..e983956af 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -57,10 +57,20 @@ namespace Barotrauma.Networking return; } + var newEvent = new ClientEntityEvent(entity, (UInt16)(ID + 1)) + { + CharacterStateID = GameMain.Client.Character.LastNetworkUpdateID + }; + if (extraData != null) { newEvent.SetData(extraData); } + + for (int i = events.Count - 1; i >= 0; i--) + { + //we already have an identical event that's waiting to be sent + // -> no need to add a new one + if (!events[i].Sent && events[i].IsDuplicate(newEvent)) return; + } + ID++; - var newEvent = new ClientEntityEvent(entity, ID); - newEvent.CharacterStateID = GameMain.Client.Character.LastNetworkUpdateID; - if (extraData != null) newEvent.SetData(extraData); events.Add(newEvent); } @@ -79,7 +89,10 @@ namespace Barotrauma.Networking startIndex--; } - for (int i = startIndex; i < events.Count; i++) + //remove events the server has already received + events.RemoveRange(0, startIndex); + + for (int i = 0; i < events.Count; i++) { //find the first event that hasn't been sent in roundtriptime or at all eventLastSent.TryGetValue(events[i].ID, out float lastSent); @@ -232,6 +245,7 @@ namespace Barotrauma.Networking if (clientEvent == null) return; clientEvent.Write(buffer); + clientEvent.Sent = true; } protected void ReadEvent(NetIncomingMessage buffer, IServerSerializable entity, float sendingTime) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs b/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs index a33a49a0e..47d7e552f 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs @@ -25,6 +25,7 @@ namespace Barotrauma.Networking public SelectionMode? ModeSelectionMode; public SelectionMode? SubSelectionMode; public bool? AllowSpectating; + public bool? VoipEnabled; public bool? AllowRespawn; public YesNoMaybe? TraitorsEnabled; public string GameMode; @@ -59,17 +60,32 @@ namespace Barotrauma.Networking return contentPackageHashes.SetEquals(myContentPackageHashes); } - public void CreatePreviewWindow(GUIMessageBox messageBox) + public void CreatePreviewWindow(GUIListBox listBox) { - var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), messageBox.Content.RectTransform), ServerName, textAlignment: Alignment.Center, font: GUI.LargeFont, wrap: true); + listBox.ClearChildren(); - var serverMsg = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), messageBox.Content.RectTransform)); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverMsg.Content.RectTransform), ServerMessage, wrap: true); + if (listBox == null) return; - var columnContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), messageBox.Content.RectTransform), isHorizontal: true) + var previewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), listBox.Content.RectTransform, Anchor.Center)) + { + Stretch = true + }; + + var titleHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 0.07f), previewContainer.RectTransform)) + { + IsHorizontal = true, + Stretch = true + }; + + var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), titleHolder.RectTransform), ServerName, font: GUI.LargeFont, wrap: true); + + new GUITextBlock(new RectTransform(Vector2.One, title.RectTransform), + TextManager.Get("ServerListVersion") + ": " + (string.IsNullOrEmpty(GameVersion) ? TextManager.Get("Unknown") : GameVersion), textAlignment: Alignment.Right); + + var columnContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), previewContainer.RectTransform), isHorizontal: true) { Stretch = true, - RelativeSpacing = 0.05f + RelativeSpacing = 0.005f }; var columnLeft = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), columnContainer.RectTransform)) @@ -88,12 +104,75 @@ namespace Barotrauma.Networking // left column ----------------------------------------------------------------------------- //new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnLeft.RectTransform), IP + ":" + Port); - new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnLeft.RectTransform), - TextManager.Get("ServerListVersion") + ": " + (string.IsNullOrEmpty(GameVersion) ? TextManager.Get("Unknown") : GameVersion)); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), columnLeft.RectTransform), + + var serverMsg = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), columnLeft.RectTransform)) { ScrollBarVisible = true }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverMsg.Content.RectTransform), ServerMessage, wrap: true) { CanBeFocused = false }; + + // right column ----------------------------------------------------------------------------- + + /*var playerCount = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("ServerListPlayers")); + new GUITextBlock(new RectTransform(Vector2.One, playerCount.RectTransform), PlayerCount + "/" + MaxPlayers, textAlignment: Alignment.Right); + + + new GUITickBox(new RectTransform(new Vector2(1, elementHeight), columnRight.RectTransform), "Round running") + { + Selected = GameStarted, + CanBeFocused = false + };*/ + + var gameMode = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("GameMode")); + new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform), TextManager.Get(string.IsNullOrEmpty(GameMode) ? "Unknown" : GameMode), textAlignment: Alignment.Right); + + var traitors = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("Traitors")); + new GUITextBlock(new RectTransform(Vector2.One, traitors.RectTransform), TextManager.Get(!TraitorsEnabled.HasValue ? "Unknown" : TraitorsEnabled.Value.ToString()), textAlignment: Alignment.Right); + + + var subSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("ServerListSubSelection")); + new GUITextBlock(new RectTransform(Vector2.One, subSelection.RectTransform), TextManager.Get(!SubSelectionMode.HasValue ? "Unknown" : SubSelectionMode.Value.ToString()), textAlignment: Alignment.Right); + + var modeSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("ServerListModeSelection")); + new GUITextBlock(new RectTransform(Vector2.One, modeSelection.RectTransform), TextManager.Get(!ModeSelectionMode.HasValue ? "Unknown" : ModeSelectionMode.Value.ToString()), textAlignment: Alignment.Right); + + var allowSpectating = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), columnRight.RectTransform), TextManager.Get("ServerListAllowSpectating")) + { + CanBeFocused = false + }; + if (!AllowSpectating.HasValue) + new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), allowSpectating.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center); + else + allowSpectating.Selected = AllowSpectating.Value; + + var allowRespawn = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), columnRight.RectTransform), TextManager.Get("ServerSettingsAllowRespawning")) + { + CanBeFocused = false + }; + if (!AllowRespawn.HasValue) + new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), allowRespawn.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center); + else + allowRespawn.Selected = AllowRespawn.Value; + + var voipEnabledTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("serversettingsvoicechatenabled")) + { + CanBeFocused = false + }; + if (!VoipEnabled.HasValue) + new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), voipEnabledTickBox.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center); + else + voipEnabledTickBox.Selected = VoipEnabled.Value; + + var usingWhiteList = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), columnRight.RectTransform), TextManager.Get("ServerListUsingWhitelist")) + { + CanBeFocused = false + }; + if (!UsingWhiteList.HasValue) + new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), usingWhiteList.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center); + else + usingWhiteList.Selected = UsingWhiteList.Value; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), columnRight.RectTransform), TextManager.Get("ServerListContentPackages")); - var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), columnLeft.RectTransform)); + var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), columnRight.RectTransform)); if (ContentPackageNames.Count == 0) { new GUITextBlock(new RectTransform(Vector2.One, contentPackageList.Content.RectTransform), TextManager.Get("Unknown"), textAlignment: Alignment.Center) @@ -143,7 +222,7 @@ namespace Barotrauma.Networking } } } - if (availableWorkshopUrls.Count > 0 ) + if (availableWorkshopUrls.Count > 0) { var workshopBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.15f), columnLeft.RectTransform), TextManager.Get("ServerListSubscribeMissingPackages")) { @@ -159,64 +238,6 @@ namespace Barotrauma.Networking workshopBtn.TextBlock.AutoScale = true; } } - - // right column ----------------------------------------------------------------------------- - - var playerCount = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("ServerListPlayers")); - new GUITextBlock(new RectTransform(Vector2.One, playerCount.RectTransform), PlayerCount + "/" + MaxPlayers, textAlignment: Alignment.Right); - - - new GUITickBox(new RectTransform(new Vector2(1, elementHeight), columnRight.RectTransform), "Round running") - { - Selected = GameStarted, - CanBeFocused = false - }; - - var gameMode = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("GameMode")); - new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform), string.IsNullOrEmpty(GameMode) ? "Unknown" : GameMode, textAlignment: Alignment.Right); - - var traitors = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("Traitors")); - new GUITextBlock(new RectTransform(Vector2.One, traitors.RectTransform), !TraitorsEnabled.HasValue ? "Unknown" : TraitorsEnabled.Value.ToString(), textAlignment: Alignment.Right); - - - var subSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("ServerListSubSelection")); - new GUITextBlock(new RectTransform(Vector2.One, subSelection.RectTransform), !SubSelectionMode.HasValue ? "Unknown" : SubSelectionMode.Value.ToString(), textAlignment: Alignment.Right); - - var modeSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("ServerListModeSelection")); - new GUITextBlock(new RectTransform(Vector2.One, modeSelection.RectTransform), (!ModeSelectionMode.HasValue ? "Unknown" : ModeSelectionMode.Value.ToString()), textAlignment: Alignment.Right); - - var allowSpectating = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), columnRight.RectTransform), TextManager.Get("ServerListAllowSpectating")) - { - CanBeFocused = false - }; - if (!AllowSpectating.HasValue) - new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), allowSpectating.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center); - else - allowSpectating.Selected = AllowSpectating.Value; - - var allowRespawn = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), columnRight.RectTransform), "Allow respawn") - { - CanBeFocused = false - }; - if (!AllowRespawn.HasValue) - new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), allowRespawn.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center); - else - allowRespawn.Selected = AllowRespawn.Value; - - new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), columnRight.RectTransform), TextManager.Get("ServerListHasPassword")) - { - Selected = HasPassword, - CanBeFocused = false - }; - - var usingWhiteList = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), columnRight.RectTransform), TextManager.Get("ServerListUsingWhitelist")) - { - CanBeFocused = false - }; - if (!UsingWhiteList.HasValue) - new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), usingWhiteList.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center); - else - usingWhiteList.Selected = UsingWhiteList.Value; // ----------------------------------------------------------------------------- diff --git a/Barotrauma/BarotraumaClient/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaClient/Source/Networking/ServerSettings.cs index 335964b17..7607be585 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/ServerSettings.cs @@ -388,7 +388,7 @@ namespace Barotrauma.Networking ToolTip = TextManager.Get("ServerSettingsMinRespawnToolTip") }; - string minRespawnLabel = TextManager.Get("ServerSettingsMinRespawn"); + string minRespawnLabel = TextManager.Get("ServerSettingsMinRespawn") + " "; CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel); slider.ToolTip = minRespawnText.ToolTip; slider.UserData = minRespawnText; @@ -407,7 +407,7 @@ namespace Barotrauma.Networking ToolTip = TextManager.Get("ServerSettingsRespawnDurationToolTip") }; - string respawnDurationLabel = TextManager.Get("ServerSettingsRespawnDuration"); + string respawnDurationLabel = TextManager.Get("ServerSettingsRespawnDuration") + " "; CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel); slider.ToolTip = respawnDurationText.ToolTip; slider.UserData = respawnDurationText; @@ -471,7 +471,7 @@ namespace Barotrauma.Networking { string translatedLabel = TextManager.Get($"Character.{s}", true); var monsterEnabledBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), monsterFrame.Content.RectTransform) { MinSize = new Point(0, 25) }, - label: translatedLabel != null ? translatedLabel : s) + label: translatedLabel ?? s) { Selected = tempMonsterEnabled[s], OnSelected = (GUITickBox tb) => @@ -573,7 +573,7 @@ namespace Barotrauma.Networking //*********************************************** - string autoRestartDelayLabel = TextManager.Get("ServerSettingsAutoRestartDelay"); + string autoRestartDelayLabel = TextManager.Get("ServerSettingsAutoRestartDelay") + " "; var startIntervalText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), autoRestartDelayLabel); var startIntervalSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), barSize: 0.1f) { @@ -617,7 +617,7 @@ namespace Barotrauma.Networking GetPropertyData("AllowVoteKick").AssignGUIComponent(voteKickBox); CreateLabeledSlider(serverTab, "ServerSettingsKickVotesRequired", out slider, out sliderLabel); - string votesRequiredLabel = sliderLabel.Text; + string votesRequiredLabel = sliderLabel.Text + " "; slider.Step = 0.2f; slider.Range = new Vector2(0.5f, 1.0f); slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => @@ -629,7 +629,7 @@ namespace Barotrauma.Networking slider.OnMoved(slider, slider.BarScroll); CreateLabeledSlider(serverTab, "ServerSettingsAutobanTime", out slider, out sliderLabel); - string autobanLabel = sliderLabel.Text; + string autobanLabel = sliderLabel.Text + " "; slider.Step = 0.05f; slider.Range = new Vector2(0.0f, MaxAutoBanTime); slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => @@ -681,8 +681,8 @@ namespace Barotrauma.Networking traitorRatioSlider.Range = new Vector2(1.0f, maxPlayers); } - string traitorRatioLabel = TextManager.Get("ServerSettingsTraitorRatio"); - string traitorCountLabel = TextManager.Get("ServerSettingsTraitorCount"); + string traitorRatioLabel = TextManager.Get("ServerSettingsTraitorRatio") + " "; + string traitorCountLabel = TextManager.Get("ServerSettingsTraitorCount") + " "; traitorRatioSlider.Range = new Vector2(0.1f, 1.0f); traitorRatioSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => diff --git a/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs index 3d76093fb..9a729b108 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs @@ -107,6 +107,56 @@ namespace Barotrauma.Steam return true; } + public static bool GetFavouriteServers(Action onServerFound, Action onServerRulesReceived, Action onFinished) + { + if (instance == null || !instance.isInitialized) + { + return false; + } + + var filter = new ServerList.Filter + { + { "appid", AppID.ToString() }, + { "gamedir", "Barotrauma" }, + { "secure", "1" } + }; + + //include unresponsive servers in the server list + + //the response is queried using the server's query port, not the game port, + //so it may be possible to play on the server even if it doesn't respond to server list queries + var query = instance.client.ServerList.Favourites(filter); + query.OnUpdate += () => { UpdateServerQuery(query, onServerFound, onServerRulesReceived, includeUnresponsive: true); }; + query.OnFinished = onFinished; + + return true; + } + + public static bool GetServersFromHistory(Action onServerFound, Action onServerRulesReceived, Action onFinished) + { + if (instance == null || !instance.isInitialized) + { + return false; + } + + var filter = new ServerList.Filter + { + { "appid", AppID.ToString() }, + { "gamedir", "Barotrauma" }, + { "secure", "1" } + }; + + //include unresponsive servers in the server list + + //the response is queried using the server's query port, not the game port, + //so it may be possible to play on the server even if it doesn't respond to server list queries + var query = instance.client.ServerList.History(filter); + query.OnUpdate += () => { UpdateServerQuery(query, onServerFound, onServerRulesReceived, includeUnresponsive: true); }; + query.OnFinished = onFinished; + + return true; + } + private static void UpdateServerQuery(ServerList.Request query, Action onServerFound, Action onServerRulesReceived, bool includeUnresponsive) { IEnumerable servers = includeUnresponsive ? @@ -164,6 +214,7 @@ namespace Barotrauma.Steam } if (s.Rules.ContainsKey("allowspectating")) serverInfo.AllowSpectating = s.Rules["allowspectating"] == "True"; if (s.Rules.ContainsKey("allowrespawn")) serverInfo.AllowRespawn = s.Rules["allowrespawn"] == "True"; + if (s.Rules.ContainsKey("voicechatenabled")) serverInfo.VoipEnabled = s.Rules["voicechatenabled"] == "True"; if (s.Rules.ContainsKey("traitors")) { if (Enum.TryParse(s.Rules["traitors"], out YesNoMaybe traitorsEnabled)) serverInfo.TraitorsEnabled = traitorsEnabled; @@ -548,6 +599,8 @@ namespace Barotrauma.Steam if (!allowFileOverwrite) { + // TODO: If you create a new mod via the workshop interface and enable it, it will show the error msg, but still allows you to enable the content. + if (File.Exists(newContentPackagePath)) { errorMsg = TextManager.Get("WorkshopErrorOverwriteOnEnable") diff --git a/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipClient.cs index b1da9bde6..1ad61e4cd 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/Voip/VoipClient.cs @@ -115,7 +115,7 @@ namespace Barotrauma.Networking } } GameMain.NetLobbyScreen.SetPlayerSpeaking(client); - GameMain.GameSession?.CrewManager?.SetPlayerSpeaking(client); + GameMain.GameSession?.CrewManager?.SetClientSpeaking(client); } } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs index 3ad2a9ac7..f989d7926 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs @@ -16,7 +16,6 @@ namespace Barotrauma private GUIListBox saveList; private GUITextBox saveNameBox, seedBox; - private GUITickBox contextualTutorialBox; private GUILayoutGroup subPreviewContainer; @@ -24,14 +23,6 @@ namespace Barotrauma public Action StartNewGame; public Action LoadGame; - public bool TutorialSelected - { - get - { - if (contextualTutorialBox == null) return false; - return contextualTutorialBox.Selected; - } - } private readonly bool isMultiplayer; @@ -68,14 +59,23 @@ namespace Barotrauma new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed") + ":"); seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8)); - if (!isMultiplayer) - { - contextualTutorialBox = new GUITickBox(new RectTransform(new Point(32, 32), leftColumn.RectTransform), TextManager.Get("TutorialActive")); - UpdateTutorialSelection(); - } - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub") + ":"); + var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), isHorizontal: true) + { + Stretch = true + }; subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform)) { ScrollBarVisible = true }; + + var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("FilterMapEntities"), 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); + searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; }; + searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; }; + + searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; }; + var clearButton = new GUIButton(new RectTransform(new Vector2(0.075f, 1.0f), filterContainer.RectTransform), "x") + { + OnClicked = (btn, userdata) => { searchBox.Text = ""; FilterSubs(subList, ""); searchBox.Flash(Color.White); return true; } + }; if (!isMultiplayer) { subList.OnSelected = OnSubSelected; } @@ -186,6 +186,16 @@ namespace Barotrauma seedBox.Text = ToolBox.RandomSeed(8); } + private void FilterSubs(GUIListBox subList, string filter) + { + foreach (GUIComponent child in subList.Content.Children) + { + var sub = child.UserData as Submarine; + if (sub == null) { return; } + child.Visible = string.IsNullOrEmpty(filter) ? true : sub.Name.ToLower().Contains(filter.ToLower()); + } + } + private bool OnSubSelected(GUIComponent component, object obj) { if (subPreviewContainer == null) { return false; } @@ -375,14 +385,7 @@ namespace Barotrauma }, Enabled = false }; - } - - public void UpdateTutorialSelection() - { - if (isMultiplayer) return; - Tutorial contextualTutorial = Tutorial.Tutorials.Find(t => t is ContextualTutorial); - contextualTutorialBox.Selected = (contextualTutorial != null) ? !GameMain.Config.CompletedTutorialNames.Contains(contextualTutorial.Name) : true; - } + } private bool SelectSaveFile(GUIComponent component, object obj) { diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs index 3c322d12d..44859a667 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs @@ -9,23 +9,25 @@ namespace Barotrauma { class CampaignUI { - public enum Tab { Map, Crew, Store } + public enum Tab { Map, Crew, Store, Repair } private Tab selectedTab; private GUIFrame[] tabs; - - private GUIButton startButton; - private GUIFrame topPanel; private GUIListBox characterList; + private MapEntityCategory selectedItemCategory = MapEntityCategory.Equipment; + private GUIListBox myItemList; private GUIListBox storeItemList; + private GUITextBox searchBox; private GUIComponent missionPanel; private GUIComponent selectedLocationInfo; private GUIListBox selectedMissionInfo; + private GUIButton repairHullsButton, repairItemsButton; + private GUIFrame characterPreviewFrame; private List tabButtons = new List(); @@ -39,10 +41,7 @@ namespace Barotrauma public GUIComponent MapContainer { get; private set; } - public GUIButton StartButton - { - get { return startButton; } - } + public GUIButton StartButton { get; private set; } public CampaignMode Campaign { get; } @@ -70,7 +69,7 @@ namespace Barotrauma var outpostBtn = new GUIButton(new RectTransform(new Vector2(0.15f, 0.55f), topPanelContent.RectTransform), TextManager.Get("Outpost"), textAlignment: Alignment.Center, style: "GUISlopedHeader") { - OnClicked = (btn, userdata) => { SelectTab(Tab.Map); return true; } + OnClicked = (btn, userdata) => { SelectTab(Tab.Map); return true; } }; outpostBtn.TextBlock.Font = GUI.LargeFont; outpostBtn.TextBlock.AutoScale = true; @@ -112,7 +111,7 @@ namespace Barotrauma tabs[(int)Tab.Crew] = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.7f), container.RectTransform, Anchor.TopLeft) { RelativeOffset = new Vector2(0.0f, topPanel.RectTransform.RelativeSize.Y) - }, color: Color.Black * 0.7f); + }, color: Color.Black * 0.9f); new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), tabs[(int)Tab.Crew].RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) { CanBeFocused = false @@ -157,7 +156,7 @@ namespace Barotrauma tabs[(int)Tab.Store] = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.7f), container.RectTransform, Anchor.TopLeft) { RelativeOffset = new Vector2(0.1f, topPanel.RectTransform.RelativeSize.Y) - }, color: Color.Black * 0.7f); + }, color: Color.Black * 0.9f); new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), tabs[(int)Tab.Store].RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) { CanBeFocused = false @@ -174,15 +173,33 @@ namespace Barotrauma RelativeSpacing = 0.02f }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), storeContent.RectTransform), "", font: GUI.LargeFont) + var storeContentTop = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), storeContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + Stretch = true + }; + + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), storeContentTop.RectTransform), "", font: GUI.LargeFont) { TextGetter = GetMoney }; + var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.4f), storeContentTop.RectTransform), isHorizontal: true) + { + Stretch = true + }; + var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("FilterMapEntities"), textAlignment: Alignment.CenterLeft, font: GUI.Font); + searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform), font: GUI.Font); + searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; }; + searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; }; + + searchBox.OnTextChanged += (textBox, text) => { FilterStoreItems(null, text); return true; }; + var clearButton = new GUIButton(new RectTransform(new Vector2(0.1f, 1.0f), filterContainer.RectTransform), "x") + { + OnClicked = (btn, userdata) => { searchBox.Text = ""; FilterStoreItems(selectedItemCategory, ""); searchBox.Flash(Color.White); return true; } + }; var storeItemLists = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.8f), storeContent.RectTransform), isHorizontal: true) { - Stretch = true, - RelativeSpacing = 0.02f + Stretch = true }; myItemList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f), storeItemLists.RectTransform)); storeItemList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f), storeItemLists.RectTransform)) @@ -200,7 +217,7 @@ namespace Barotrauma "", style: "ItemCategory" + category.ToString()) { UserData = category, - OnClicked = (btn, userdata) => { SelectItemCategory((MapEntityCategory)userdata); return true; } + OnClicked = (btn, userdata) => { FilterStoreItems((MapEntityCategory)userdata, searchBox.Text); return true; } }; itemCategoryButtons.Add(categoryButton); @@ -216,7 +233,115 @@ namespace Barotrauma CanBeFocused = false }; } - SelectItemCategory(MapEntityCategory.Equipment); + FillStoreItemList(); + FilterStoreItems(MapEntityCategory.Equipment, ""); + + // repair tab ------------------------------------------------------------------------- + + tabs[(int)Tab.Repair] = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.5f), container.RectTransform, Anchor.TopLeft) + { + RelativeOffset = new Vector2(0.02f, topPanel.RectTransform.RelativeSize.Y) + }, color: Color.Black * 0.9f); + new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), tabs[(int)Tab.Repair].RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) + { + CanBeFocused = false + }; + + var repairContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), tabs[(int)Tab.Repair].RectTransform, Anchor.Center)) + { + RelativeSpacing = 0.05f, + Stretch = true + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), repairContent.RectTransform), "", font: GUI.LargeFont) + { + TextGetter = GetMoney + }; + + var repairHullsHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), repairContent.RectTransform), childAnchor: Anchor.TopRight) + { + RelativeSpacing = 0.05f, + Stretch = true + }; + new GUIImage(new RectTransform(new Vector2(0.3f, 1.0f), repairHullsHolder.RectTransform, Anchor.CenterLeft), "RepairHullButton") + { + IgnoreLayoutGroups = true, + CanBeFocused = false + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), repairHullsHolder.RectTransform), TextManager.Get("RepairAllWalls"), textAlignment: Alignment.Right, font: GUI.LargeFont) + { + ForceUpperCase = true + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), repairHullsHolder.RectTransform), "500", textAlignment: Alignment.Right, font: GUI.LargeFont); + repairHullsButton = new GUIButton(new RectTransform(new Vector2(0.4f, 0.3f), repairHullsHolder.RectTransform), TextManager.Get("Repair"), style: "GUIButtonLarge") + { + OnClicked = (btn, userdata) => + { + if (campaign.PurchasedHullRepairs) + { + campaign.Money += CampaignMode.HullRepairCost; + campaign.PurchasedHullRepairs = false; + } + else + { + if (campaign.Money >= CampaignMode.HullRepairCost) + { + campaign.Money -= CampaignMode.HullRepairCost; + campaign.PurchasedHullRepairs = true; + } + } + GameMain.Client?.SendCampaignState(); + btn.GetChild().Selected = campaign.PurchasedHullRepairs; + + return true; + } + }; + new GUITickBox(new RectTransform(new Vector2(0.65f), repairHullsButton.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(10, 0) }, "") + { + CanBeFocused = false + }; + + var repairItemsHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), repairContent.RectTransform), childAnchor: Anchor.TopRight) + { + RelativeSpacing = 0.05f, + Stretch = true + }; + new GUIImage(new RectTransform(new Vector2(0.3f, 1.0f), repairItemsHolder.RectTransform, Anchor.CenterLeft), "RepairItemsButton") + { + IgnoreLayoutGroups = true, + CanBeFocused = false + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), repairItemsHolder.RectTransform), TextManager.Get("RepairAllItems"), textAlignment: Alignment.Right, font: GUI.LargeFont) + { + ForceUpperCase = true + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), repairItemsHolder.RectTransform), "500", textAlignment: Alignment.Right, font: GUI.LargeFont); + repairItemsButton = new GUIButton(new RectTransform(new Vector2(0.4f, 0.3f), repairItemsHolder.RectTransform), TextManager.Get("Repair"), style: "GUIButtonLarge") + { + OnClicked = (btn, userdata) => + { + if (campaign.PurchasedItemRepairs) + { + campaign.Money += CampaignMode.ItemRepairCost; + campaign.PurchasedItemRepairs = false; + } + else + { + if (campaign.Money >= CampaignMode.ItemRepairCost) + { + campaign.Money -= CampaignMode.ItemRepairCost; + campaign.PurchasedItemRepairs = true; + } + } + GameMain.Client?.SendCampaignState(); + btn.GetChild().Selected = campaign.PurchasedItemRepairs; + + return true; + } + }; + new GUITickBox(new RectTransform(new Vector2(0.65f), repairItemsButton.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(10, 0) }, "") + { + CanBeFocused = false + }; // mission info ------------------------------------------------------------------------- @@ -330,8 +455,7 @@ namespace Barotrauma bool purchaseableItemsFound = false; foreach (MapEntityPrefab mapEntityPrefab in MapEntityPrefab.List) { - var itemPrefab = mapEntityPrefab as ItemPrefab; - if (itemPrefab == null) { continue; } + if (!(mapEntityPrefab is ItemPrefab itemPrefab)) { continue; } PriceInfo priceInfo = itemPrefab.GetPrice(Campaign.Map.CurrentLocation); if (priceInfo != null) { purchaseableItemsFound = true; break; } @@ -348,9 +472,9 @@ namespace Barotrauma else { //refresh store view - SelectItemCategory(MapEntityCategory.Equipment); - } - + FillStoreItemList(); + FilterStoreItems(MapEntityCategory.Equipment, searchBox.Text); + } } private void DrawMap(SpriteBatch spriteBatch, GUICustomComponent mapContainer) @@ -452,7 +576,7 @@ namespace Barotrauma RefreshMissionTab(selectedMission); - startButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.7f), missionContent.RectTransform, Anchor.CenterRight), + StartButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.7f), missionContent.RectTransform, Anchor.CenterRight), TextManager.Get("StartCampaignButton"), style: "GUIButtonLarge") { IgnoreLayoutGroups = true, @@ -461,7 +585,7 @@ namespace Barotrauma }; if (GameMain.Client != null) { - startButton.Visible = !GameMain.Client.GameStarted && + StartButton.Visible = !GameMain.Client.GameStarted && (GameMain.Client.HasPermission(Networking.ClientPermissions.ManageRound) || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)); } @@ -508,10 +632,10 @@ namespace Barotrauma CanBeFocused = false }; - if (startButton != null) + if (StartButton != null) { - startButton.Enabled = true; - startButton.Visible = GameMain.Client == null || + StartButton.Enabled = true; + StartButton.Visible = GameMain.Client == null || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageRound) || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign); } @@ -597,8 +721,7 @@ namespace Barotrauma private bool BuyItem(GUIComponent component, object obj) { - PurchasedItem pi = obj as PurchasedItem; - if (pi == null || pi.ItemPrefab == null) return false; + if (!(obj is PurchasedItem pi) || pi.ItemPrefab == null) return false; if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) { @@ -616,8 +739,7 @@ namespace Barotrauma private bool SellItem(GUIComponent component, object obj) { - PurchasedItem pi = obj as PurchasedItem; - if (pi == null || pi.ItemPrefab == null) return false; + if (!(obj is PurchasedItem pi) || pi.ItemPrefab == null) return false; if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) { @@ -659,35 +781,59 @@ namespace Barotrauma { button.Selected = (Tab)button.UserData == tab; } + + switch (selectedTab) + { + case Tab.Repair: + repairHullsButton.Enabled = + (Campaign.PurchasedHullRepairs || Campaign.Money >= CampaignMode.HullRepairCost) && + (GameMain.Client == null || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)); + repairHullsButton.GetChild().Selected = Campaign.PurchasedHullRepairs; + repairItemsButton.Enabled = + (Campaign.PurchasedItemRepairs || Campaign.Money >= CampaignMode.ItemRepairCost) && + (GameMain.Client == null || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)); + repairItemsButton.GetChild().Selected = Campaign.PurchasedItemRepairs; + break; + } } - private bool SelectItemCategory(MapEntityCategory category) + private void FillStoreItemList() { storeItemList.ClearChildren(); int width = storeItemList.Rect.Width; foreach (MapEntityPrefab mapEntityPrefab in MapEntityPrefab.List) { - var itemPrefab = mapEntityPrefab as ItemPrefab; - if (itemPrefab == null || !itemPrefab.Category.HasFlag(category)) continue; - + if (!(mapEntityPrefab is ItemPrefab itemPrefab)) { continue; } PriceInfo priceInfo = itemPrefab.GetPrice(Campaign.Map.CurrentLocation); if (priceInfo == null) continue; CreateItemFrame(new PurchasedItem(itemPrefab, 0), priceInfo, storeItemList, width); } - storeItemList.Content.RectTransform.SortChildren( (x, y) => (x.GUIComponent.UserData as PurchasedItem).ItemPrefab.Name.CompareTo((y.GUIComponent.UserData as PurchasedItem).ItemPrefab.Name)); + } + private void FilterStoreItems(MapEntityCategory? category, string filter) + { + if (category.HasValue) + { + selectedItemCategory = category.Value; + } + foreach (GUIComponent child in storeItemList.Content.Children) + { + var item = child.UserData as PurchasedItem; + if (item?.ItemPrefab?.Name == null) { continue; } + child.Visible = + (!category.HasValue || item.ItemPrefab.Category.HasFlag(category.Value)) && + (string.IsNullOrEmpty(filter) || item.ItemPrefab.Name.ToLower().Contains(searchBox.Text.ToLower())); + } foreach (GUIButton btn in itemCategoryButtons) { - btn.Selected = (MapEntityCategory)btn.UserData == category; + btn.Selected = (MapEntityCategory)btn.UserData == selectedItemCategory; } - + storeItemList.UpdateScrollBarSize(); storeItemList.BarScroll = 0.0f; - - return true; } public string GetMoney() @@ -707,9 +853,8 @@ namespace Barotrauma } if (prevInfoFrame != null) { tabs[(int)selectedTab].RemoveChild(prevInfoFrame); } - - CharacterInfo characterInfo = selection as CharacterInfo; - if (characterInfo == null) { return false; } + + if (!(selection is CharacterInfo characterInfo)) { return false; } if (Character.Controlled != null && characterInfo == Character.Controlled.Info) { return false; } if (characterPreviewFrame == null || characterPreviewFrame.UserData != characterInfo) @@ -761,11 +906,9 @@ namespace Barotrauma private bool HireCharacter(GUIButton button, object selection) { - CharacterInfo characterInfo = selection as CharacterInfo; - if (characterInfo == null) { return false; } + if (!(selection is CharacterInfo characterInfo)) { return false; } - SinglePlayerCampaign spCampaign = Campaign as SinglePlayerCampaign; - if (spCampaign == null) + if (!(Campaign is SinglePlayerCampaign spCampaign)) { DebugConsole.ThrowError("Characters can only be hired in the single player campaign.\n" + Environment.StackTrace); return false; @@ -784,11 +927,9 @@ namespace Barotrauma private bool FireCharacter(GUIButton button, object selection) { - CharacterInfo characterInfo = selection as CharacterInfo; - if (characterInfo == null) return false; + if (!(selection is CharacterInfo characterInfo)) return false; - SinglePlayerCampaign spCampaign = Campaign as SinglePlayerCampaign; - if (spCampaign == null) + if (!(Campaign is SinglePlayerCampaign spCampaign)) { DebugConsole.ThrowError("Characters can only be fired in the single player campaign.\n" + Environment.StackTrace); return false; diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs index 8dfbbdd0b..6e1905c0e 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs @@ -42,16 +42,16 @@ namespace Barotrauma private bool showParamsEditor; private bool showSpritesheet; private bool isFreezed; - private bool autoFreeze = true; - private bool limbPairEditing = true; - private bool uniformScaling = true; - private bool lockSpriteOrigin = true; + private bool autoFreeze; + private bool limbPairEditing; + private bool uniformScaling; + private bool lockSpriteOrigin; private bool lockSpritePosition; private bool lockSpriteSize; private bool recalculateCollider; private bool copyJointSettings; private bool displayColliders; - private bool displayWearables = true; + private bool displayWearables; private bool displayBackgroundColor; private bool ragdollResetRequiresForceLoading; private bool animationResetRequiresForceLoading; @@ -89,10 +89,16 @@ namespace Barotrauma public override void Select() { base.Select(); + + SoundPlayer.OverrideMusicType = "none"; + SoundPlayer.OverrideMusicDuration = null; + GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", 0.0f); + GUI.ForceMouseOn(null); CalculateSpritesheetPosition(); if (Submarine.MainSub == null) { + ResetVariables(); Submarine.MainSub = new Submarine("Content/AnimEditor.sub"); Submarine.MainSub.Load(unloadPrevious: false, showWarningMessages: false); originalWall = new WallGroup(new List(Structure.WallList)); @@ -101,6 +107,10 @@ namespace Barotrauma isEndlessRunner = true; GameMain.LightManager.LightingEnabled = false; } + else if (instance == null) + { + ResetVariables(); + } Submarine.MainSub.GodMode = true; if (Character.Controlled == null) { @@ -116,33 +126,71 @@ namespace Barotrauma OpenDoors(); GameMain.Instance.OnResolutionChanged += OnResolutionChanged; instance = this; + + if (!GameMain.Config.EditorDisclaimerShown) + { + GameMain.Instance.ShowEditorDisclaimer(); + } + } + + private void ResetVariables() + { + editAnimations = false; + editLimbs = false; + editJoints = false; + editIK = false; + showRagdoll = false; + showParamsEditor = false; + showSpritesheet = false; + isFreezed = false; + autoFreeze = true; + limbPairEditing = true; + uniformScaling = true; + lockSpriteOrigin = false; + lockSpritePosition = false; + lockSpriteSize = false; + recalculateCollider = false; + copyJointSettings = false; + displayColliders = false; + displayWearables = true; + displayBackgroundColor = false; + ragdollResetRequiresForceLoading = false; + animationResetRequiresForceLoading = false; + isExtrudingJoint = false; + isDrawingJoint = false; + Wizard.instance = null; } private void Reset() { - AnimParams.ForEach(a => a.Reset(true)); - RagdollParams.Reset(true); - RagdollParams.ClearHistory(); - CurrentAnimation.ClearHistory(); - if (!character.Removed) + ResetVariables(); + if (character != null) { - character.Remove(); + AnimParams.ForEach(a => a.Reset(true)); + RagdollParams.Reset(true); + RagdollParams.ClearHistory(); + CurrentAnimation.ClearHistory(); + if (!character.Removed) + { + character.Remove(); + } + character = null; } - character = null; } public override void Deselect() { base.Deselect(); + + SoundPlayer.OverrideMusicType = null; + GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", GameMain.Config.SoundVolume); + GUI.ForceMouseOn(null); if (isEndlessRunner) { Submarine.MainSub.Remove(); isEndlessRunner = false; - if (character != null) - { - Reset(); - } + Reset(); GameMain.World.ProcessChanges(); } else @@ -175,7 +223,7 @@ namespace Barotrauma { //base.AddToGUIUpdateList(); rightPanel.AddToGUIUpdateList(); - Wizard.Instance.AddToGUIUpdateList(); + Wizard.instance?.AddToGUIUpdateList(); if (displayBackgroundColor) { backgroundColorPanel.AddToGUIUpdateList(); @@ -207,7 +255,7 @@ namespace Barotrauma base.Update(deltaTime); spriteSheetRect = CalculateSpritesheetRectangle(); // Handle shortcut keys - if (GUI.KeyboardDispatcher.Subscriber == null) + if (GUI.KeyboardDispatcher.Subscriber == null && Wizard.instance == null) { if (PlayerInput.KeyDown(Keys.LeftControl)) { @@ -396,7 +444,7 @@ namespace Barotrauma } } } - if (!isFreezed) + if (!isFreezed && Wizard.instance == null) { if (character.AnimController.Invalid) { @@ -1131,7 +1179,22 @@ namespace Barotrauma character = Character.Create(configFile, spawnPosition, ToolBox.RandomSeed(8), hasAi: false, ragdoll: ragdoll); selectedJob = null; } - character.dontFollowCursor = dontFollowCursor; + if (character != null) + { + character.dontFollowCursor = dontFollowCursor; + } + if (character == null) + { + if (currentCharacterConfig == configFile) + { + return null; + } + else + { + // Respawn the current character; + SpawnCharacter(currentCharacterConfig); + } + } OnPostSpawn(); return character; } @@ -1247,27 +1310,33 @@ namespace Barotrauma string speciesName = name; // Config file string configFilePath = Path.Combine(mainFolder, $"{speciesName}.xml").Replace(@"\", @"/"); - if (ContentPackage.GetFilesOfType(GameMain.SelectedPackages, ContentType.Character).None(path => path.Contains(speciesName))) + if (ContentPackage.GetFilesOfType(GameMain.SelectedPackages, ContentType.Character).Any(path => path.Contains(speciesName))) { - // Create the config file - XElement mainElement = new XElement("Character", - new XAttribute("name", speciesName), - new XAttribute("humanoid", isHumanoid), - new XElement("ragdolls", new XAttribute("folder", Path.Combine(mainFolder, $"Ragdolls/").Replace(@"\", @"/"))), - new XElement("animations", new XAttribute("folder", Path.Combine(mainFolder, $"Animations/").Replace(@"\", @"/"))), - new XElement("health"), - new XElement("ai")); - XDocument doc = new XDocument(mainElement); - if (!Directory.Exists(mainFolder)) - { - Directory.CreateDirectory(mainFolder); - } - doc.Save(configFilePath); - // Add to the selected content package - contentPackage.AddFile(configFilePath, ContentType.Character); - contentPackage.Save(contentPackage.Path); - DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path)); + GUI.AddMessage(GetCharacterEditorTranslation("ExistingCharacterFound"), Color.Red, font: GUI.LargeFont); + // TODO: add a prompt: "Do you want to replace it?" + functionality + return false; } + + // Create the config file + XElement mainElement = new XElement("Character", + new XAttribute("name", speciesName), + new XAttribute("humanoid", isHumanoid), + new XElement("ragdolls", new XAttribute("folder", Path.Combine(mainFolder, $"Ragdolls/").Replace(@"\", @"/"))), + new XElement("animations", new XAttribute("folder", Path.Combine(mainFolder, $"Animations/").Replace(@"\", @"/"))), + new XElement("health"), + new XElement("ai")); + + XDocument doc = new XDocument(mainElement); + if (!Directory.Exists(mainFolder)) + { + Directory.CreateDirectory(mainFolder); + } + doc.Save(configFilePath); + // Add to the selected content package + contentPackage.AddFile(configFilePath, ContentType.Character); + contentPackage.Save(contentPackage.Path); + DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path)); + // Ragdoll string ragdollFolder = RagdollParams.GetFolder(speciesName); string ragdollPath = RagdollParams.GetDefaultFile(speciesName); @@ -1278,12 +1347,20 @@ namespace Barotrauma string animFolder = AnimationParams.GetFolder(speciesName); foreach (AnimationType animType in Enum.GetValues(typeof(AnimationType))) { - if (animType != AnimationType.NotDefined) + switch (animType) { - Type type = AnimationParams.GetParamTypeFromAnimType(animType, isHumanoid); - string fullPath = AnimationParams.GetDefaultFile(speciesName, animType); - AnimationParams.Create(fullPath, speciesName, animType, type); + case AnimationType.Walk: + case AnimationType.Run: + if (!ragdollParams.CanEnterSubmarine) { continue; } + break; + case AnimationType.SwimSlow: + case AnimationType.SwimFast: + break; + default: continue; } + Type type = AnimationParams.GetParamTypeFromAnimType(animType, isHumanoid); + string fullPath = AnimationParams.GetDefaultFile(speciesName, animType); + AnimationParams.Create(fullPath, speciesName, animType, type); } if (!AllFiles.Contains(configFilePath)) { @@ -1702,8 +1779,20 @@ namespace Barotrauma Vector2 buttonSize = new Vector2(1, 0.04f); Vector2 toggleSize = new Vector2(0.03f, 0.03f); Point margin = new Point(40, 60); - rightPanel = new GUIFrame(new RectTransform(new Vector2(0.15f, 0.95f), parent: Frame.RectTransform, anchor: Anchor.CenterRight) { RelativeOffset = new Vector2(0.01f, 0) }); - var layoutGroup = new GUILayoutGroup(new RectTransform(new Point(rightPanel.Rect.Width - margin.X, rightPanel.Rect.Height - margin.Y), rightPanel.RectTransform, Anchor.Center)); + rightPanel = new GUIFrame(new RectTransform(new Vector2(0.15f, 1.0f), parent: Frame.RectTransform, anchor: Anchor.CenterRight), style: "GUIFrameRight"); + var layoutGroup = new GUILayoutGroup(new RectTransform(new Point(rightPanel.Rect.Width - margin.X, rightPanel.Rect.Height - margin.Y), rightPanel.RectTransform, Anchor.Center)) + { + Stretch = true + }; + + var disclaimerBtnHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.04f), layoutGroup.RectTransform), style: null); + + var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), disclaimerBtnHolder.RectTransform, Anchor.TopRight), style: "GUINotificationButton") + { + OnClicked = (btn, userdata) => { GameMain.Instance.ShowEditorDisclaimer(); return true; } + }; + disclaimerBtn.RectTransform.MaxSize = new Point(disclaimerBtn.Rect.Height); + var characterDropDown = new GUIDropDown(new RectTransform(new Vector2(1, 0.04f), layoutGroup.RectTransform), elementCount: 10, style: null); characterDropDown.ListBox.Color = new Color(characterDropDown.ListBox.Color.R, characterDropDown.ListBox.Color.G, characterDropDown.ListBox.Color.B, byte.MaxValue); foreach (var file in AllFiles) @@ -1734,14 +1823,16 @@ namespace Barotrauma return true; }; } - var charButtons = new GUIFrame(new RectTransform(buttonSize, parent: layoutGroup.RectTransform), style: null); - var prevCharacterButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1), charButtons.RectTransform, Anchor.TopLeft), GetCharacterEditorTranslation("PreviousCharacter")); + var charButtons = new GUIFrame(new RectTransform(new Vector2(buttonSize.X, buttonSize.Y * 1.5f), parent: layoutGroup.RectTransform), style: null); + var prevCharacterButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), charButtons.RectTransform, Anchor.TopLeft), GetCharacterEditorTranslation("PreviousCharacter")); + prevCharacterButton.TextBlock.AutoScale = true; prevCharacterButton.OnClicked += (b, obj) => { SpawnCharacter(GetPreviousConfigFile()); return true; }; - var nextCharacterButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1), charButtons.RectTransform, Anchor.TopRight), GetCharacterEditorTranslation("NextCharacter")); + var nextCharacterButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), charButtons.RectTransform, Anchor.TopRight), GetCharacterEditorTranslation("NextCharacter")); + prevCharacterButton.TextBlock.AutoScale = true; nextCharacterButton.OnClicked += (b, obj) => { SpawnCharacter(GetNextConfigFile()); @@ -3764,7 +3855,7 @@ namespace Barotrauma void RecalculateCollider(Limb l) { // We want the collider to be slightly smaller than the source rect, because the source rect is usually a bit bigger than the graphic. - float multiplier = 0.75f; + float multiplier = 0.85f; l.body.SetSize(new Vector2(ConvertUnits.ToSimUnits(width), ConvertUnits.ToSimUnits(height)) * RagdollParams.LimbScale * RagdollParams.TextureScale * multiplier); TryUpdateLimbParam(l, "radius", ConvertUnits.ToDisplayUnits(l.body.radius / RagdollParams.LimbScale / RagdollParams.TextureScale)); TryUpdateLimbParam(l, "width", ConvertUnits.ToDisplayUnits(l.body.width / RagdollParams.LimbScale / RagdollParams.TextureScale)); @@ -4283,7 +4374,7 @@ namespace Barotrauma private List jointXElements = new List(); private List jointGUIElements = new List(); - private static Wizard instance; + public static Wizard instance; public static Wizard Instance { get @@ -4314,7 +4405,6 @@ namespace Barotrauma break; case Tab.None: default: - //activeView = null; instance = null; break; } @@ -4343,7 +4433,7 @@ namespace Barotrauma GUITextBox xmlPathElement = null; void UpdatePaths() { - string pathBase = $"Content/Characters/{Name}/{Name}"; + string pathBase = $"Mods/Characters/{Name}/{Name}"; XMLPath = $"{pathBase}.xml"; TexturePath = $"{pathBase}.png"; texturePathElement.Text = TexturePath; @@ -4422,7 +4512,7 @@ namespace Barotrauma // Cancel box.Buttons[0].OnClicked += (b, d) => { - Instance.SelectTab(Tab.None); + Wizard.Instance.SelectTab(Tab.None); return true; }; // Next @@ -4434,7 +4524,7 @@ namespace Barotrauma texturePathElement.Flash(Color.Red); return false; } - Instance.SelectTab(Tab.Ragdoll); + Wizard.Instance.SelectTab(Tab.Ragdoll); return true; }; return box; @@ -4575,7 +4665,7 @@ namespace Barotrauma // Previous box.Buttons[0].OnClicked += (b, d) => { - Instance.SelectTab(Tab.Character); + Wizard.Instance.SelectTab(Tab.Character); return true; }; // Parse and create @@ -4666,7 +4756,7 @@ namespace Barotrauma { GUI.AddMessage(GetCharacterEditorTranslation("CharacterCreated").Replace("[name]", Name), Color.Green, font: GUI.Font); } - Instance.SelectTab(Tab.None); + Wizard.Instance.SelectTab(Tab.None); return true; }; return box; @@ -4846,23 +4936,27 @@ namespace Barotrauma int width = rectInputs[2].IntValue; int height = rectInputs[3].IntValue; var colliderAttributes = new List(); - if (width == height) - { - colliderAttributes.Add(new XAttribute("radius", width / 2)); - } - else - { - if (height > width) - { - colliderAttributes.Add(new XAttribute("radius", width / 2)); - colliderAttributes.Add(new XAttribute("height", height - width)); - } - else - { - colliderAttributes.Add(new XAttribute("radius", height / 2)); - colliderAttributes.Add(new XAttribute("width", width - height)); - } - } + // Capsules/Circles + //if (width == height) + //{ + // colliderAttributes.Add(new XAttribute("radius", (int)(width / 2 * 0.85f))); + //} + //else + //{ + // if (height > width) + // { + // colliderAttributes.Add(new XAttribute("radius", (int)(width / 2 * 0.85f))); + // colliderAttributes.Add(new XAttribute("height",(int) (height - width * 0.85f))); + // } + // else + // { + // colliderAttributes.Add(new XAttribute("radius", (int)(height / 2 * 0.85f))); + // colliderAttributes.Add(new XAttribute("width", (int)(width - height * 0.85f))); + // } + //} + // Rectangles + colliderAttributes.Add(new XAttribute("height", (int)(height * 0.85f))); + colliderAttributes.Add(new XAttribute("width", (int)(width * 0.85f))); idToCodeName.TryGetValue(id, out string notes); LimbXElements.Add(id.ToString(), new XElement("limb", new XAttribute("id", id), diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index 3a99c392e..94bb1bd2c 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs @@ -59,10 +59,10 @@ namespace Barotrauma Stretch = true, RelativeSpacing = 0.02f }; - + // === CAMPAIGN var campaignHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.1f, 0.0f) }, isHorizontal: true); - + new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), campaignHolder.RectTransform), "MainMenuCampaignIcon") { CanBeFocused = false @@ -84,6 +84,17 @@ namespace Barotrauma RelativeSpacing = 0.035f }; + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), "Tutorial", textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + UserData = Tab.Tutorials, + OnClicked = (tb, userdata) => + { + SelectTab(tb, userdata); + return true; + } + }; + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("LoadGameButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") { ForceUpperCase = true, @@ -184,6 +195,10 @@ namespace Barotrauma UserData = Tab.SteamWorkshop, OnClicked = SelectTab }; + +#if OSX && !DEBUG + steamWorkshopButton.Text += " (Not yet available on MacOS)"; +#endif } new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SubEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") @@ -320,13 +335,11 @@ namespace Barotrauma return true; }; - UpdateTutorialList(); - this.game = game; } - #endregion +#endregion - #region Selection +#region Selection public override void Select() { base.Select(); @@ -338,8 +351,6 @@ namespace Barotrauma } Submarine.Unload(); - - UpdateTutorialList(); ResetButtonStates(null); @@ -386,9 +397,14 @@ namespace Barotrauma switch (selectedTab) { case Tab.NewGame: + if (!GameMain.Config.CampaignDisclaimerShown) + { + selectedTab = 0; + GameMain.Instance.ShowCampaignDisclaimer(() => { SelectTab(null, Tab.NewGame); }); + return true; + } campaignSetupUI.CreateDefaultSaveName(); campaignSetupUI.RandomizeSeed(); - campaignSetupUI.UpdateTutorialSelection(); campaignSetupUI.UpdateSubList(Submarine.SavedSubmarines); break; case Tab.LoadGame: @@ -405,6 +421,13 @@ namespace Barotrauma case Tab.HostServer: break; case Tab.Tutorials: + if (!GameMain.Config.CampaignDisclaimerShown) + { + selectedTab = 0; + GameMain.Instance.ShowCampaignDisclaimer(() => { SelectTab(null, Tab.Tutorials); }); + return true; + } + UpdateTutorialList(); break; case Tab.CharacterEditor: Submarine.MainSub = null; @@ -435,6 +458,8 @@ namespace Barotrauma public bool ReturnToMainMenu(GUIButton button, object obj) { + GUI.PreventPauseMenuToggle = false; + if (Selected != this) { Select(); @@ -459,7 +484,7 @@ namespace Barotrauma otherButton.Selected = false; } } - #endregion +#endregion private void QuickStart() { @@ -626,21 +651,22 @@ namespace Barotrauma " -ownerkey " + ownerKey.ToString(); string filename = "DedicatedServer.exe"; -#if LINUX || OSX +#if LINUX + filename = "./DedicatedServer"; +#elif OSX filename = "mono"; arguments = "./DedicatedServer.exe " + arguments; #endif - var processInfo = new ProcessStartInfo { FileName = filename, - Arguments = arguments, + Arguments = arguments #if !DEBUG + , WindowStyle = ProcessWindowStyle.Hidden #endif }; GameMain.ServerChildProcess = Process.Start(processInfo); - Thread.Sleep(1000); //wait until the server is ready before connecting GameMain.Client = new GameClient(name, "127.0.0.1:" + port.ToString(),ownerKey); @@ -676,6 +702,7 @@ namespace Barotrauma GameMain.TitleScreen.TitleSize.Y / 2.0f * GameMain.TitleScreen.Scale + 30.0f), 0.1f); #if !DEBUG +#if !OSX if (Steam.SteamManager.USE_STEAM) { if (GameMain.Config.UseSteamMatchmaking) @@ -685,6 +712,16 @@ namespace Barotrauma } steamWorkshopButton.Enabled = Steam.SteamManager.IsInitialized; } +#else + if (Steam.SteamManager.USE_STEAM) + { + if (GameMain.Config.UseSteamMatchmaking) + { + joinServerButton.Enabled = Steam.SteamManager.IsInitialized; + hostServerButton.Enabled = Steam.SteamManager.IsInitialized; + } + } +#endif #else joinServerButton.Enabled = true; hostServerButton.Enabled = true; @@ -769,13 +806,11 @@ namespace Barotrauma } selectedSub = new Submarine(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"), ""); - - ContextualTutorial.Selected = campaignSetupUI.TutorialSelected; + GameMain.GameSession = new GameSession(selectedSub, saveName, GameModePreset.List.Find(g => g.Identifier == "singleplayercampaign")); (GameMain.GameSession.GameMode as CampaignMode).GenerateMap(mapSeed); - GameMain.LobbyScreen.Select(); } @@ -797,7 +832,7 @@ namespace Barotrauma GameMain.LobbyScreen.Select(); } - #region UI Methods +#region UI Methods private void CreateHostServerFields() { Vector2 textLabelSize = new Vector2(1.0f, 0.1f); @@ -878,7 +913,7 @@ namespace Barotrauma OnClicked = HostServerClicked }; } - #endregion +#endregion } } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index a1c7438b6..f136ff3a2 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -158,14 +158,7 @@ namespace Barotrauma get; private set; } - - public GUIFrame MyCharacterFrame - { - get { return myCharacterFrame; } - } - - public bool MyCharacterFrameOpen; - + public GUIFrame InfoFrame { get { return infoFrame; } @@ -261,7 +254,7 @@ namespace Barotrauma public NetLobbyScreen() { - defaultModeContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), Frame.RectTransform, Anchor.Center), style: null); + defaultModeContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), Frame.RectTransform, Anchor.Center) { MaxSize = new Point(int.MaxValue, GameMain.GraphicsHeight - 100) }, style: null); campaignContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.75f), Frame.RectTransform, Anchor.TopCenter), style: null) { Visible = false @@ -300,18 +293,7 @@ namespace Barotrauma OnSelected = TogglePlayYourself, UserData = "playyourself" }; - - var toggleMyPlayerFrame = new GUIButton(new RectTransform(new Point(25, 70), myCharacterFrame.RectTransform, Anchor.TopLeft, Pivot.TopRight), "", style: "GUIButtonHorizontalArrow"); - toggleMyPlayerFrame.OnClicked += (GUIButton btn, object userdata) => - { - MyCharacterFrameOpen = !MyCharacterFrameOpen; - foreach (GUIComponent child in btn.Children) - { - child.SpriteEffects = MyCharacterFrameOpen ? SpriteEffects.FlipHorizontally : SpriteEffects.None; - } - return true; - }; - + //player list ------------------------------------------------------------------ playerListFrame = new GUIFrame(new RectTransform(new Vector2(0.3f - panelSpacing, 0.35f - panelSpacing), defaultModeContainer.RectTransform, Anchor.BottomRight)); @@ -698,7 +680,6 @@ namespace Barotrauma public override void Deselect() { textBox.Deselect(); - myCharacterFrame.GetChild().Visible = true; CampaignCharacterDiscarded = false; } @@ -713,10 +694,7 @@ namespace Barotrauma textBox.Select(); textBox.OnEnterPressed = GameMain.Client.EnterChatMessage; textBox.OnTextChanged += GameMain.Client.TypingChatMessage; - - myCharacterFrame.RectTransform.AbsoluteOffset = new Point(0, 0); - myCharacterFrame.GetChild().Visible = false; - + subList.Enabled = AllowSubSelection;// || GameMain.Server != null; shuttleList.Enabled = AllowSubSelection;// || GameMain.Server != null; @@ -737,11 +715,15 @@ namespace Barotrauma spectateButton.Visible = GameMain.Client.GameStarted; ReadyToStartBox.Visible = !GameMain.Client.GameStarted; ReadyToStartBox.Selected = false; - if (campaignUI?.StartButton != null) + if (campaignUI != null) { - campaignUI.StartButton.Visible = !GameMain.Client.GameStarted && - (GameMain.Client.HasPermission(ClientPermissions.ManageRound) || - GameMain.Client.HasPermission(ClientPermissions.ManageCampaign)); + //SelectTab(Tab.Map); + if (campaignUI.StartButton != null) + { + campaignUI.StartButton.Visible = !GameMain.Client.GameStarted && + (GameMain.Client.HasPermission(ClientPermissions.ManageRound) || + GameMain.Client.HasPermission(ClientPermissions.ManageCampaign)); + } } GameMain.Client.SetReadyToStart(ReadyToStartBox); } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs index 3fa7dcc26..f4212dc18 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs @@ -22,6 +22,7 @@ namespace Barotrauma private GUIFrame menu; private GUIListBox serverList; + private GUIListBox serverPreview; private GUIButton joinButton; @@ -45,37 +46,31 @@ namespace Barotrauma public ServerListScreen() { - int width = Math.Min(GameMain.GraphicsWidth - 160, 1000); - int height = Math.Min(GameMain.GraphicsHeight - 160, 700); + menu = new GUIFrame(new RectTransform(new Vector2(0.7f, 0.8f), GUI.Canvas, Anchor.Center) { MinSize = new Point(GameMain.GraphicsHeight, 0) }); - Rectangle panelRect = new Rectangle(0, 0, width, height); - - menu = new GUIFrame(new RectTransform(new Point(width, height), GUI.Canvas, Anchor.Center)); - - new GUITextBlock(new RectTransform(new Vector2(0.95f, 0.133f), menu.RectTransform, Anchor.TopCenter), - TextManager.Get("JoinServer"), textAlignment: Alignment.Left, font: GUI.LargeFont) { ForceUpperCase = true }; - - var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), menu.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.0f, 0.03f) }, style: null); + var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 0.95f), menu.RectTransform, Anchor.Center), isHorizontal: true) + { Stretch = true, RelativeSpacing = 0.02f }; //------------------------------------------------------------------------------------- //left column //------------------------------------------------------------------------------------- - var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.25f, 0.92f), paddedFrame.RectTransform, Anchor.TopLeft)); + var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.25f, 1.0f), paddedFrame.RectTransform, Anchor.CenterLeft)) { Stretch = true, RelativeSpacing = 0.5f }; - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.03f), leftColumn.RectTransform), style: null); + var infoHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), leftColumn.RectTransform)) { RelativeSpacing = 0.05f }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), TextManager.Get("YourName")); - clientNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.045f), leftColumn.RectTransform), "") + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), infoHolder.RectTransform, Anchor.Center), TextManager.Get("JoinServer"), font: GUI.LargeFont) + { ForceUpperCase = true }; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoHolder.RectTransform), TextManager.Get("YourName")); + clientNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.13f), infoHolder.RectTransform), "") { Text = GameMain.Config.DefaultPlayerName }; clientNameBox.OnTextChanged += RefreshJoinButtonState; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), TextManager.Get("ServerIP")); - // TODO: Show IP on server info window - ipBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.045f), leftColumn.RectTransform), ""); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoHolder.RectTransform), TextManager.Get("ServerIP")); + ipBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.13f), infoHolder.RectTransform), ""); ipBox.OnTextChanged += RefreshJoinButtonState; ipBox.OnSelected += (sender, key) => { @@ -85,43 +80,53 @@ namespace Barotrauma sender.UserData = null; } }; - - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.45f), leftColumn.RectTransform), style: null); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), TextManager.Get("FilterServers")); - searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), ""); + var filterHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), leftColumn.RectTransform)) { RelativeSpacing = 0.05f }; - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.03f), leftColumn.RectTransform), style: null); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), filterHolder.RectTransform), TextManager.Get("FilterServers")); + searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.13f), filterHolder.RectTransform), ""); + + var tickBoxHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), filterHolder.RectTransform)); searchBox.OnTextChanged += (txtBox, txt) => { FilterServers(); return true; }; - filterPassword = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), TextManager.Get("FilterPassword")); + filterPassword = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.27f), tickBoxHolder.RectTransform), TextManager.Get("FilterPassword")); filterPassword.OnSelected += (tickBox) => { FilterServers(); return true; }; - filterIncompatible = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), TextManager.Get("FilterIncompatibleServers")); + filterIncompatible = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.27f), tickBoxHolder.RectTransform), TextManager.Get("FilterIncompatibleServers")); filterIncompatible.OnSelected += (tickBox) => { FilterServers(); return true; }; - filterFull = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), TextManager.Get("FilterFullServers")); + filterFull = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.27f), tickBoxHolder.RectTransform), TextManager.Get("FilterFullServers")); filterFull.OnSelected += (tickBox) => { FilterServers(); return true; }; - filterEmpty = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), TextManager.Get("FilterEmptyServers")); + filterEmpty = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.27f), tickBoxHolder.RectTransform), TextManager.Get("FilterEmptyServers")); filterEmpty.OnSelected += (tickBox) => { FilterServers(); return true; }; //------------------------------------------------------------------------------------- //right column //------------------------------------------------------------------------------------- - var rightColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - leftColumn.RectTransform.RelativeSize.X - 0.017f, 0.97f), - paddedFrame.RectTransform, Anchor.TopRight)) + var rightColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - leftColumn.RectTransform.RelativeSize.X - 0.017f, 1.0f), + paddedFrame.RectTransform, Anchor.CenterRight)) { RelativeSpacing = 0.02f, Stretch = true }; - serverList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.85f), rightColumn.RectTransform, Anchor.Center)) + var serverListHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)) { Stretch = true, RelativeSpacing = 0.02f }; + + serverList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), serverListHolder.RectTransform, Anchor.Center)) { - OnSelected = SelectServer + OnSelected = (btn, obj) => { + ServerInfo serverInfo = (ServerInfo)obj; + + serverInfo.CreatePreviewWindow(serverPreview); + + return true; + } }; + serverList.OnSelected += SelectServer; + + serverPreview = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), serverListHolder.RectTransform, Anchor.Center)); + columnRelativeWidth = new float[] { 0.04f, 0.02f, 0.044f, 0.77f, 0.02f, 0.075f, 0.06f }; var buttonContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.075f), rightColumn.RectTransform), style: null); @@ -239,6 +244,7 @@ namespace Barotrauma { if (waitingForRefresh) return false; serverList.ClearChildren(); + serverPreview.ClearChildren(); ipBox.Text = null; joinButton.Enabled = false; @@ -352,7 +358,7 @@ namespace Barotrauma private void AddToServerList(ServerInfo serverInfo) { - var serverFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), serverList.Content.RectTransform) { MinSize = new Point(0, 20) }, + var serverFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), serverList.Content.RectTransform) { MinSize = new Point(0, 35) }, style: "InnerFrame", color: Color.White * 0.5f) { UserData = serverInfo @@ -390,19 +396,6 @@ namespace Barotrauma UserData = "password" }; - new GUIButton(new RectTransform(new Vector2(columnRelativeWidth[2], 0.8f), serverContent.RectTransform, Anchor.Center), style: "GUIButtonServerListInfo") { - ToolTip = TextManager.Get("ServerListInfo"), - OnClicked = (btn, obj) => { - SelectServer(null, serverInfo); - var msgBox = new GUIMessageBox("", "", new string[] { TextManager.Get("Cancel"), TextManager.Get("ServerListJoin") }, 550, 400); - msgBox.Buttons[0].OnClicked += msgBox.Close; - msgBox.Buttons[1].OnClicked += JoinServer; - msgBox.Buttons[1].OnClicked += msgBox.Close; - serverInfo.CreatePreviewWindow(msgBox); - return true; - } - }; - var serverName = new GUITextBlock(new RectTransform(new Vector2(columnRelativeWidth[3], 1.0f), serverContent.RectTransform), serverInfo.ServerName, style: "GUIServerListTextBox"); var gameStartedBox = new GUITickBox(new RectTransform(new Vector2(columnRelativeWidth[4], 0.4f), serverContent.RectTransform, Anchor.Center), diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs index 56adb609f..f924e04a8 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs @@ -329,6 +329,8 @@ namespace Barotrauma { element.Elements("sprite").ForEach(s => CreateSprite(s)); element.Elements("Sprite").ForEach(s => CreateSprite(s)); + element.Elements("backgroundsprite").ForEach(s => CreateSprite(s)); + element.Elements("BackgroundSprite").ForEach(s => CreateSprite(s)); element.Elements("brokensprite").ForEach(s => CreateSprite(s)); element.Elements("BrokenSprite").ForEach(s => CreateSprite(s)); element.Elements("containedsprite").ForEach(s => CreateSprite(s)); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs index 89cbf2ef2..a2a112ec5 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; +using System.Text; using System.Windows.Forms; namespace Barotrauma @@ -130,7 +131,7 @@ namespace Barotrauma OutlineColor = new Color(72, 124, 77, 255), OnClicked = (btn, userdata) => { - System.Diagnostics.Process.Start("steam://url/SteamWorkshopPage/" + SteamManager.AppID); + SteamManager.OverlayCustomURL("steam://url/SteamWorkshopPage/" + SteamManager.AppID); return true; } }; @@ -369,7 +370,7 @@ namespace Barotrauma catch (Exception e) { pendingPreviewImageDownloads.Remove(item.PreviewImageUrl); - DebugConsole.ThrowError("Downloading the preview image of the Workshop item \"" + item.Title + "\" failed.", e); + DebugConsole.ThrowError("Downloading the preview image of the Workshop item \"" + EnsureUTF8(item.Title) + "\" failed.", e); } } @@ -380,7 +381,7 @@ namespace Barotrauma CanBeFocused = false }; - var titleText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), rightColumn.RectTransform), item.Title, textAlignment: Alignment.CenterLeft, wrap: true) + var titleText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), rightColumn.RectTransform), EnsureUTF8(item.Title), textAlignment: Alignment.CenterLeft, wrap: true) { CanBeFocused = false }; @@ -397,14 +398,14 @@ namespace Barotrauma { if (SteamManager.UpdateWorkshopItem(item, out string errorMsg)) { - new GUIMessageBox("", TextManager.Get("WorkshopItemUpdated").Replace("[itemname]", item.Title)); + new GUIMessageBox("", TextManager.Get("WorkshopItemUpdated").Replace("[itemname]", EnsureUTF8(item.Title))); } else { DebugConsole.ThrowError(errorMsg); new GUIMessageBox( TextManager.Get("Error"), - TextManager.Get("WorkshopItemUpdateFailed").Replace("[itemname]", item.Title).Replace("[errormessage]", errorMsg)); + TextManager.Get("WorkshopItemUpdateFailed").Replace("[itemname]", EnsureUTF8(item.Title)).Replace("[errormessage]", errorMsg)); } btn.Enabled = false; btn.Visible = false; @@ -593,6 +594,7 @@ namespace Barotrauma { tickBox.Enabled = false; } + GameMain.Config.EnsureCoreContentPackageSelected(); } if (updateButton != null) { @@ -623,11 +625,11 @@ namespace Barotrauma //spacing new GUIFrame(new RectTransform(new Vector2(1.0f, 0.005f), content.RectTransform), style: null); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), item.Title, textAlignment: Alignment.TopLeft, font: GUI.LargeFont, wrap: true); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), EnsureUTF8(item.Title), textAlignment: Alignment.TopLeft, font: GUI.LargeFont, wrap: true); var creatorHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), content.RectTransform)) { IsHorizontal = true, Stretch = true }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), creatorHolder.RectTransform), TextManager.Get("WorkshopItemCreator") + ": " + item.OwnerName, textAlignment: Alignment.BottomLeft, wrap: true); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), creatorHolder.RectTransform), TextManager.Get("WorkshopItemCreator") + ": " + EnsureUTF8(item.OwnerName), textAlignment: Alignment.BottomLeft, wrap: true); new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), creatorHolder.RectTransform, Anchor.BottomRight), TextManager.Get("WorkshopShowItemInSteam"), style: null) { @@ -637,7 +639,7 @@ namespace Barotrauma OutlineColor = new Color(72, 124, 77, 255), OnClicked = (btn, userdata) => { - System.Diagnostics.Process.Start("steam://url/CommunityFilePage/" + item.Id); + SteamManager.OverlayCustomURL("steam://url/CommunityFilePage/" + item.Id); return true; } }; @@ -660,7 +662,7 @@ namespace Barotrauma //spacing new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), descriptionContainer.Content.RectTransform) { MinSize = new Point(0, 5) }, style: null); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), descriptionContainer.Content.RectTransform), item.Description, wrap: true) + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), descriptionContainer.Content.RectTransform), EnsureUTF8(item.Description), wrap: true) { CanBeFocused = false }; @@ -738,25 +740,28 @@ namespace Barotrauma itemEditor.Tags.Add("Submarine"); itemEditor.Description = sub.Description; - string previewImagePath = Path.GetFullPath(Path.Combine(SteamManager.WorkshopItemStagingFolder, SteamManager.PreviewImageName)); - try + if (sub.PreviewImage != null) { - using (Stream s = File.Create(previewImagePath)) + string previewImagePath = Path.GetFullPath(Path.Combine(SteamManager.WorkshopItemStagingFolder, SteamManager.PreviewImageName)); + try { - sub.PreviewImage.Texture.SaveAsPng(s, (int)sub.PreviewImage.size.X, (int)sub.PreviewImage.size.Y); - itemEditor.PreviewImage = previewImagePath; + using (Stream s = File.Create(previewImagePath)) + { + sub.PreviewImage.Texture.SaveAsPng(s, (int)sub.PreviewImage.size.X, (int)sub.PreviewImage.size.Y); + itemEditor.PreviewImage = previewImagePath; + } + if (new FileInfo(previewImagePath).Length > 1024 * 1024) + { + new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("WorkshopItemPreviewImageTooLarge")); + itemEditor.PreviewImage = SteamManager.DefaultPreviewImagePath; + } } - if (new FileInfo(previewImagePath).Length > 1024 * 1024) + catch (Exception e) { - new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("WorkshopItemPreviewImageTooLarge")); - itemEditor.PreviewImage = SteamManager.DefaultPreviewImagePath; + DebugConsole.ThrowError("Saving submarine preview image failed.", e); + itemEditor.PreviewImage = null; } } - catch (Exception e) - { - DebugConsole.ThrowError("Saving submarine preview image failed.", e); - itemEditor.PreviewImage = null; - } } private void CreateWorkshopItem(ContentPackage contentPackage) { @@ -793,7 +798,7 @@ namespace Barotrauma if (!item.Installed) { new GUIMessageBox(TextManager.Get("Error"), - TextManager.Get("WorkshopErrorInstallRequiredToEdit").Replace("[itemname]", item.Title)); + TextManager.Get("WorkshopErrorInstallRequiredToEdit").Replace("[itemname]", EnsureUTF8(item.Title))); return; } SteamManager.CreateWorkshopItemStaging(item, out itemEditor, out itemContentPackage); @@ -1228,7 +1233,7 @@ namespace Barotrauma string pleaseWaitText = TextManager.Get("WorkshopPublishPleaseWait"); var msgBox = new GUIMessageBox( pleaseWaitText, - TextManager.Get("WorkshopPublishInProgress").Replace("[itemname]", item.Title), + TextManager.Get("WorkshopPublishInProgress").Replace("[itemname]", EnsureUTF8(item.Title)), new string[] { TextManager.Get("Cancel") }); msgBox.Buttons[0].OnClicked = (btn, userdata) => @@ -1250,13 +1255,13 @@ namespace Barotrauma if (string.IsNullOrEmpty(item.Error)) { - new GUIMessageBox("", TextManager.Get("WorkshopItemPublished").Replace("[itemname]", item.Title)); + new GUIMessageBox("", TextManager.Get("WorkshopItemPublished").Replace("[itemname]", EnsureUTF8(item.Title))); } else { new GUIMessageBox( TextManager.Get("Error"), - TextManager.Get("WorkshopItemPublishFailed").Replace("[itemname]", item.Title) + item.Error); + TextManager.Get("WorkshopItemPublishFailed").Replace("[itemname]", EnsureUTF8(item.Title)) + item.Error); } createItemFrame.ClearChildren(); @@ -1285,6 +1290,12 @@ namespace Barotrauma { } + private string EnsureUTF8(string text) + { + byte[] bytes = Encoding.Default.GetBytes(text); + return Encoding.UTF8.GetString(bytes); + } + #endregion } } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index 52ac33d4a..8936318f5 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs @@ -183,6 +183,12 @@ namespace Barotrauma TextGetter = GetSubName }; + var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(0.1f, 1.0f), paddedTopPanel.RectTransform, Anchor.CenterRight), style: "GUINotificationButton") + { + OnClicked = (btn, userdata) => { GameMain.Instance.ShowEditorDisclaimer(); return true; } + }; + disclaimerBtn.RectTransform.MaxSize = new Point(disclaimerBtn.Rect.Height); + linkedSubBox = new GUIDropDown(new RectTransform(new Vector2(0.15f, 0.9f), paddedTopPanel.RectTransform) { RelativeOffset = new Vector2(0.385f, 0.0f) }, TextManager.Get("AddSubButton"), elementCount: 20) { @@ -310,6 +316,12 @@ namespace Barotrauma RelativeSpacing = 0.01f, Stretch = true }; + + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), tabButtonHolder.RectTransform), TextManager.Get("MapEntityCategory.All"), style: "GUITabButton") + { + OnClicked = (btn, userdata) => { ClearFilter(); return true; } + }; + foreach (MapEntityCategory category in Enum.GetValues(typeof(MapEntityCategory))) { entityCategoryButtons.Add(new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), tabButtonHolder.RectTransform), @@ -446,9 +458,10 @@ namespace Barotrauma //spacing new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), showEntitiesHolder.RectTransform) { MinSize = new Point(0, 3) }, style: null); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.025f), paddedLeftPanel.RectTransform, Anchor.BottomCenter) { AbsoluteOffset = new Point(10, 0) }, TextManager.Get("PreviouslyUsedLabel")); - previouslyUsedList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), paddedLeftPanel.RectTransform, Anchor.BottomCenter) { AbsoluteOffset = new Point(10, 0) }) + new GUITextBlock(new RectTransform(new Vector2(0.95f, 0.025f), paddedLeftPanel.RectTransform, Anchor.BottomCenter) { AbsoluteOffset = new Point(10, 0) }, TextManager.Get("PreviouslyUsedLabel")); + previouslyUsedList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), paddedLeftPanel.RectTransform, Anchor.BottomCenter)) { + ScrollBarVisible = true, OnSelected = SelectPrefab }; @@ -620,6 +633,10 @@ namespace Barotrauma cam.UpdateTransform(); GameAnalyticsManager.SetCustomDimension01("editor"); + if (!GameMain.Config.EditorDisclaimerShown) + { + GameMain.Instance.ShowEditorDisclaimer(); + } } public override void Deselect() @@ -832,8 +849,10 @@ namespace Barotrauma } string savePath = nameBox.Text + ".sub"; + string prevSavePath = null; if (Submarine.MainSub != null) { + prevSavePath = Submarine.MainSub.FilePath; savePath = Path.Combine(Path.GetDirectoryName(Submarine.MainSub.FilePath), savePath); } else @@ -872,6 +891,10 @@ namespace Barotrauma GUI.AddMessage(TextManager.Get("SubSavedNotification").Replace("[filepath]", Submarine.MainSub.FilePath), Color.Green); Submarine.RefreshSavedSub(savePath); + if (prevSavePath != null && prevSavePath != savePath) + { + Submarine.RefreshSavedSub(prevSavePath); + } linkedSubBox.ClearChildren(); foreach (Submarine sub in Submarine.SavedSubmarines) @@ -1011,7 +1034,7 @@ namespace Barotrauma var previewImageButtonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), rightColumn.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; - new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), previewImageButtonHolder.RectTransform), TextManager.Get("SubPreviewImageGenerate")) + new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), previewImageButtonHolder.RectTransform), TextManager.Get("SubPreviewImageCreate")) { OnClicked = (btn, userdata) => { @@ -1263,9 +1286,8 @@ namespace Barotrauma { if (CharacterMode) SetCharacterMode(false); if (WiringMode) SetWiringMode(false); - - Submarine.RefreshSavedSubs(); - + + loadFrame = new GUIButton(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker") { OnClicked = (btn, userdata) => { if (GUI.MouseOn == btn || GUI.MouseOn == btn.TextBlock) loadFrame = null; return true; }, @@ -1273,10 +1295,15 @@ namespace Barotrauma var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.36f), loadFrame.RectTransform, Anchor.Center) { MinSize = new Point(350, 500) }); - var paddedLoadFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), innerFrame.RectTransform, Anchor.Center)) { Stretch = true, RelativeSpacing = 0.05f }; + var paddedLoadFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), innerFrame.RectTransform, Anchor.Center)) { Stretch = true, RelativeSpacing = 0.02f }; var deleteButtonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedLoadFrame.RectTransform, Anchor.Center)); + var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedLoadFrame.RectTransform), isHorizontal: true) + { + Stretch = true + }; + var subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), paddedLoadFrame.RectTransform)) { ScrollBarVisible = true, @@ -1287,6 +1314,17 @@ namespace Barotrauma } }; + var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("FilterMapEntities"), textAlignment: Alignment.CenterLeft, font: GUI.Font); + var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform), font: GUI.Font); + searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; }; + searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; }; + + searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; }; + var clearButton = new GUIButton(new RectTransform(new Vector2(0.1f, 1.0f), filterContainer.RectTransform), "x") + { + OnClicked = (btn, userdata) => { searchBox.Text = ""; FilterSubs(subList, ""); searchBox.Flash(Color.White); return true; } + }; + foreach (Submarine sub in Submarine.SavedSubmarines) { GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), subList.Content.RectTransform) { MinSize = new Point(0, 30) }, @@ -1347,6 +1385,16 @@ namespace Barotrauma return true; } + private void FilterSubs(GUIListBox subList, string filter) + { + foreach (GUIComponent child in subList.Content.Children) + { + var sub = child.UserData as Submarine; + if (sub == null) { return; } + child.Visible = string.IsNullOrEmpty(filter) ? true : sub.Name.ToLower().Contains(filter.ToLower()); + } + } + private bool LoadSub(GUIButton button, object obj) { if (loadFrame == null) @@ -1415,6 +1463,7 @@ namespace Barotrauma { sub.Remove(); File.Delete(sub.FilePath); + Submarine.RefreshSavedSubs(); CreateLoadScreen(); } catch (Exception e) diff --git a/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs index 0d756b322..6d244e815 100644 --- a/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs @@ -277,13 +277,17 @@ namespace Barotrauma { component.RectTransform.Parent = layoutGroup.RectTransform; component.RectTransform.RepositionChildInHierarchy(childIndex); + Recalculate(); + } + public void Recalculate() + { int contentHeight = ContentHeight; RectTransform.NonScaledSize = new Point(RectTransform.NonScaledSize.X, contentHeight); layoutGroup.RectTransform.NonScaledSize = new Point(layoutGroup.RectTransform.NonScaledSize.X, contentHeight); } - private GUIComponent CreateNewField(SerializableProperty property, ISerializableEntity entity) + public GUIComponent CreateNewField(SerializableProperty property, ISerializableEntity entity) { object value = property.GetValue(entity); if (property.PropertyType == typeof(string) && value == null) @@ -351,7 +355,7 @@ namespace Barotrauma return propertyField; } - private GUIComponent CreateBoolField(ISerializableEntity entity, SerializableProperty property, bool value, string displayName, string toolTip) + public GUIComponent CreateBoolField(ISerializableEntity entity, SerializableProperty property, bool value, string displayName, string toolTip) { GUITickBox propertyTickBox = new GUITickBox(new RectTransform(new Point(Rect.Width, elementHeight), layoutGroup.RectTransform), displayName) { @@ -367,11 +371,11 @@ namespace Barotrauma return true; } }; - Fields.Add(property.Name, new GUIComponent[] { propertyTickBox }); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, new GUIComponent[] { propertyTickBox }); } return propertyTickBox; } - private GUIComponent CreateIntField(ISerializableEntity entity, SerializableProperty property, int value, string displayName, string toolTip) + public GUIComponent CreateIntField(ISerializableEntity entity, SerializableProperty property, int value, string displayName, string toolTip) { var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform), color: Color.Transparent); var label = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), frame.RectTransform), displayName, font: GUI.SmallFont) @@ -395,11 +399,11 @@ namespace Barotrauma TrySendNetworkUpdate(entity, property); } }; - Fields.Add(property.Name, new GUIComponent[] { numberInput }); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, new GUIComponent[] { numberInput }); } return frame; } - private GUIComponent CreateFloatField(ISerializableEntity entity, SerializableProperty property, float value, string displayName, string toolTip) + public GUIComponent CreateFloatField(ISerializableEntity entity, SerializableProperty property, float value, string displayName, string toolTip) { var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform), color: Color.Transparent); var label = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), frame.RectTransform), displayName, font: GUI.SmallFont) @@ -425,11 +429,11 @@ namespace Barotrauma TrySendNetworkUpdate(entity, property); } }; - Fields.Add(property.Name, new GUIComponent[] { numberInput }); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, new GUIComponent[] { numberInput }); } return frame; } - private GUIComponent CreateEnumField(ISerializableEntity entity, SerializableProperty property, object value, string displayName, string toolTip) + public GUIComponent CreateEnumField(ISerializableEntity entity, SerializableProperty property, object value, string displayName, string toolTip) { var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, elementHeight), layoutGroup.RectTransform), color: Color.Transparent); var label = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), frame.RectTransform), displayName, font: GUI.SmallFont) @@ -454,11 +458,11 @@ namespace Barotrauma return true; }; enumDropDown.SelectItem(value); - Fields.Add(property.Name, new GUIComponent[] { enumDropDown }); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, new GUIComponent[] { enumDropDown }); } return frame; } - private GUIComponent CreateEnumFlagField(ISerializableEntity entity, SerializableProperty property, object value, string displayName, string toolTip) + public GUIComponent CreateEnumFlagField(ISerializableEntity entity, SerializableProperty property, object value, string displayName, string toolTip) { var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, elementHeight), layoutGroup.RectTransform), color: Color.Transparent); var label = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), frame.RectTransform), displayName, font: GUI.SmallFont) @@ -487,22 +491,26 @@ namespace Barotrauma return true; }; - Fields.Add(property.Name, new GUIComponent[] { enumDropDown }); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, new GUIComponent[] { enumDropDown }); } return frame; } - private GUIComponent CreateStringField(ISerializableEntity entity, SerializableProperty property, string value, string displayName, string toolTip) + public GUIComponent CreateStringField(ISerializableEntity entity, SerializableProperty property, string value, string displayName, string toolTip) { - var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, elementHeight), layoutGroup.RectTransform), color: Color.Transparent); + var frame = new GUILayoutGroup(new RectTransform(new Point(Rect.Width, elementHeight), layoutGroup.RectTransform), isHorizontal: true) + { + Stretch = true + }; var label = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), frame.RectTransform), displayName, font: GUI.SmallFont, textAlignment: Alignment.Left) { ToolTip = toolTip }; - GUITextBox propertyBox = new GUITextBox(new RectTransform(new Vector2(0.6f, 1), frame.RectTransform, Anchor.TopRight)) + GUITextBox propertyBox = new GUITextBox(new RectTransform(new Vector2(0.6f, 1), frame.RectTransform)) { ToolTip = toolTip, Font = GUI.SmallFont, Text = value, + OverflowClip = true, OnEnterPressed = (textBox, text) => { if (property.TrySetValue(entity, text)) @@ -514,11 +522,36 @@ namespace Barotrauma return true; } }; - Fields.Add(property.Name, new GUIComponent[] { propertyBox }); + string translationTextTag = property.GetAttribute()?.translationTextTag; + if (translationTextTag != null) + { + new GUIButton(new RectTransform(new Vector2(0.1f, 1), frame.RectTransform, Anchor.TopRight), "...") + { + OnClicked = (bt, userData) => { CreateTextPicker(translationTextTag, entity, property, propertyBox); return true; } + }; + propertyBox.OnTextChanged += (tb, text) => + { + string translatedText = TextManager.Get(text, returnNull: true); + if (translatedText == null) + { + propertyBox.TextColor = Color.Gray; + propertyBox.ToolTip = TextManager.Get("StringPropertyCannotTranslate").Replace("[tag]", text ?? ""); + } + else + { + propertyBox.TextColor = Color.LightGreen; + propertyBox.ToolTip = TextManager.Get("StringPropertyTranslate").Replace("[translation]", translatedText); + } + return true; + }; + propertyBox.Text = value; + } + + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, new GUIComponent[] { propertyBox }); } return frame; } - private GUIComponent CreatePointField(ISerializableEntity entity, SerializableProperty property, Point value, string displayName, string toolTip) + public GUIComponent CreatePointField(ISerializableEntity entity, SerializableProperty property, Point value, string displayName, string toolTip) { var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform), color: Color.Transparent); var label = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), frame.RectTransform), displayName, font: GUI.SmallFont) @@ -566,11 +599,11 @@ namespace Barotrauma }; fields[i] = numberInput; } - Fields.Add(property.Name, fields); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; } - private GUIComponent CreateVector2Field(ISerializableEntity entity, SerializableProperty property, Vector2 value, string displayName, string toolTip) + public GUIComponent CreateVector2Field(ISerializableEntity entity, SerializableProperty property, Vector2 value, string displayName, string toolTip) { var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform), color: Color.Transparent); var label = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), frame.RectTransform), displayName, font: GUI.SmallFont) @@ -620,11 +653,11 @@ namespace Barotrauma }; fields[i] = numberInput; } - Fields.Add(property.Name, fields); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; } - private GUIComponent CreateVector3Field(ISerializableEntity entity, SerializableProperty property, Vector3 value, string displayName, string toolTip) + public GUIComponent CreateVector3Field(ISerializableEntity entity, SerializableProperty property, Vector3 value, string displayName, string toolTip) { var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform), color: Color.Transparent); var label = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), frame.RectTransform), displayName, font: GUI.SmallFont) @@ -678,11 +711,11 @@ namespace Barotrauma }; fields[i] = numberInput; } - Fields.Add(property.Name, fields); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; } - private GUIComponent CreateVector4Field(ISerializableEntity entity, SerializableProperty property, Vector4 value, string displayName, string toolTip) + public GUIComponent CreateVector4Field(ISerializableEntity entity, SerializableProperty property, Vector4 value, string displayName, string toolTip) { var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform), color: Color.Transparent); var label = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1), frame.RectTransform), displayName, font: GUI.SmallFont) @@ -740,14 +773,14 @@ namespace Barotrauma }; fields[i] = numberInput; } - Fields.Add(property.Name, fields); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; } - private GUIComponent CreateColorField(ISerializableEntity entity, SerializableProperty property, Color value, string displayName, string toolTip) + public GUIComponent CreateColorField(ISerializableEntity entity, SerializableProperty property, Color value, string displayName, string toolTip) { var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform), color: Color.Transparent); - var label = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1), frame.RectTransform) { MinSize = new Point(80, 26)}, displayName, font: GUI.SmallFont) + var label = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1), frame.RectTransform) { MinSize = new Point(80, 26) }, displayName, font: GUI.SmallFont) { ToolTip = toolTip }; @@ -807,11 +840,11 @@ namespace Barotrauma colorBox.Color = (Color)property.GetValue(entity); fields[i] = numberInput; } - Fields.Add(property.Name, fields); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; } - private GUIComponent CreateRectangleField(ISerializableEntity entity, SerializableProperty property, Rectangle value, string displayName, string toolTip) + public GUIComponent CreateRectangleField(ISerializableEntity entity, SerializableProperty property, Rectangle value, string displayName, string toolTip) { var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform), color: Color.Transparent); var label = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1), frame.RectTransform), displayName, font: GUI.SmallFont) @@ -867,9 +900,43 @@ namespace Barotrauma }; fields[i] = numberInput; } - Fields.Add(property.Name, fields); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } return frame; } + + public void CreateTextPicker(string textTag, ISerializableEntity entity, SerializableProperty property, GUITextBox textBox) + { + var msgBox = new GUIMessageBox("", "", new string[] { TextManager.Get("Cancel") }, width: 300, height: 400); + msgBox.Buttons[0].OnClicked = msgBox.Close; + + var textList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), msgBox.Content.RectTransform, Anchor.TopCenter)) + { + OnSelected = (component, userData) => + { + string text = userData as string ?? ""; + + if (property.TrySetValue(entity, text)) + { + TrySendNetworkUpdate(entity, property); + textBox.Text = (string)property.GetValue(entity); + textBox.Deselect(); + } + return true; + } + }; + + textTag = textTag.ToLowerInvariant(); + var tagTextPairs = TextManager.GetAllTagTextPairs(); + foreach (KeyValuePair tagTextPair in tagTextPairs) + { + if (!tagTextPair.Key.StartsWith(textTag)) { continue; } + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), textList.Content.RectTransform) { MinSize = new Point(0, 20) }, + ToolBox.LimitString(tagTextPair.Value, GUI.Font, textList.Content.Rect.Width)) + { + UserData = tagTextPair.Key + }; + } + } private void TrySendNetworkUpdate(ISerializableEntity entity, SerializableProperty property) { diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs index 5609902ab..3c1705000 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs @@ -35,6 +35,7 @@ namespace Barotrauma { public readonly string File; public readonly string Type; + public readonly bool DuckVolume; public readonly Vector2 IntensityRange; @@ -43,6 +44,7 @@ namespace Barotrauma this.File = Path.GetFullPath(element.GetAttributeString("file", "")); this.Type = element.GetAttributeString("type", "").ToLowerInvariant(); this.IntensityRange = element.GetAttributeVector2("intensityrange", new Vector2(0.0f, 100.0f)); + this.DuckVolume = element.GetAttributeBool("duckvolume", false); } } @@ -269,6 +271,13 @@ namespace Barotrauma } movementSoundVolume = Math.Max(movementSoundVolume, movementFactor); + if (!MathUtils.IsValid(movementSoundVolume)) + { + 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); + movementSoundVolume = 0.0f; + } } if (waterAmbiences.Count > 1) @@ -442,9 +451,9 @@ namespace Barotrauma { PlaySound( "ambient", + new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y) + Rand.Vector(100.0f), Rand.Range(0.5f, 1.0f), - 1000.0f, - new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y) + Rand.Vector(100.0f)); + 1000.0f); ambientSoundTimer = Rand.Range(ambientSoundInterval.X, ambientSoundInterval.Y); } @@ -458,23 +467,31 @@ namespace Barotrauma return matchingSounds[Rand.Int(matchingSounds.Count)]; } + /// + /// Play a sound defined in a sound xml file without any positional effects. + /// public static SoundChannel PlaySound(string soundTag, float volume = 1.0f) { var sound = GetSound(soundTag); return sound?.Play(volume); } - public static SoundChannel PlaySound(string soundTag, float volume, float range, Vector2 position, Hull hullGuess = null) + /// + /// Play a sound defined in a sound xml file. If the volume or range parameters are omitted, the volume and range defined in the sound xml are used. + /// + public static SoundChannel PlaySound(string soundTag, Vector2 position, float? volume = null, float? range = null, Hull hullGuess = null) { var sound = GetSound(soundTag); if (sound == null) return null; - return PlaySound(sound, sound.BaseGain * volume, range, position, hullGuess); + return PlaySound(sound, position, volume ?? sound.BaseGain, range ?? sound.BaseFar, hullGuess); } - public static SoundChannel PlaySound(Sound sound, float volume, float range, Vector2 position, Hull hullGuess = null) + public static SoundChannel PlaySound(Sound sound, Vector2 position, float? volume = null, float? range = null, Hull hullGuess = null) { - if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > range * range) return null; - return sound.Play(sound.BaseGain * volume, range, position, muffle: ShouldMuffleSound(Character.Controlled, position, range, hullGuess)); + float far = range ?? sound.BaseFar; + + if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > far * far) return null; + return sound.Play(volume ?? sound.BaseGain, far, position, muffle: ShouldMuffleSound(Character.Controlled, position, far, hullGuess)); } private static void UpdateMusic(float deltaTime) @@ -508,7 +525,7 @@ namespace Barotrauma //switch the music if nothing playing atm or the currently playing clip is not suitable anymore else if (targetMusic[0] == null || currentMusic[0] == null || !suitableMusic.Any(m => m.File == currentMusic[0].Filename)) { - targetMusic[0] = suitableMusic.GetRandom(); + targetMusic[0] = suitableMusic.GetRandom(); } //get the appropriate intensity layers for current situation @@ -543,6 +560,7 @@ namespace Barotrauma updateMusicTimer = UpdateMusicInterval; } + int activeTrackCount = targetMusic.Count(m => m != null); for (int i = 0; i < MaxMusicChannels; i++) { //nothing should be playing on this channel @@ -582,7 +600,12 @@ namespace Barotrauma musicChannel[i] = currentMusic[i].Play(0.0f, "music"); musicChannel[i].Looping = true; } - musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, 1.0f, MusicLerpSpeed * deltaTime); + float targetGain = 1.0f; + if (targetMusic[i].DuckVolume) + { + targetGain = (float)Math.Sqrt(1.0f / activeTrackCount); + } + musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, targetGain, MusicLerpSpeed * deltaTime); } } } @@ -650,8 +673,7 @@ namespace Barotrauma foreach (Character character in Character.CharacterList) { if (character.IsDead || !character.Enabled) continue; - EnemyAIController enemyAI = character.AIController as EnemyAIController; - if (enemyAI == null || (!enemyAI.AttackHumans && !enemyAI.AttackRooms)) continue; + if (!(character.AIController is EnemyAIController enemyAI) || (!enemyAI.AttackHumans && !enemyAI.AttackRooms)) continue; if (targetSubmarine != null) { @@ -669,9 +691,16 @@ namespace Barotrauma } } - if (GameMain.GameSession != null && Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0) + if (GameMain.GameSession != null) { - return "start"; + if (Submarine.Loaded != null && Level.Loaded != null && Submarine.MainSub.AtEndPosition) + { + return "levelend"; + } + if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0) + { + return "start"; + } } return "default"; diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/VoipSound.cs b/Barotrauma/BarotraumaClient/Source/Sounds/VoipSound.cs index 3e86fcc2c..18126a8ba 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/VoipSound.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/VoipSound.cs @@ -32,6 +32,9 @@ namespace Barotrauma.Sounds public bool UseRadioFilter; public bool UseMuffleFilter; + public float Near { get; private set; } + public float Far { get; private set; } + private static BiQuad[] muffleFilters = new BiQuad[] { new LowpassFilter(VoipConfig.FREQUENCY, 800) @@ -41,6 +44,16 @@ namespace Barotrauma.Sounds new BandpassFilter(VoipConfig.FREQUENCY, 2000) }; + public float Gain + { + get { return soundChannel == null ? 0.0f : soundChannel.Gain; } + set + { + if (soundChannel == null) { return; } + soundChannel.Gain = value; + } + } + public VoipSound(SoundManager owner, VoipQueue q) : base(owner, "voip", true, true) { VoipConfig.SetupEncoding(); @@ -64,8 +77,8 @@ namespace Barotrauma.Sounds public void SetRange(float near, float far) { - soundChannel.Near = near; - soundChannel.Far = far; + soundChannel.Near = Near = near; + soundChannel.Far = Far = far; } public void ApplyFilters(short[] buffer, int readSamples) diff --git a/Barotrauma/BarotraumaClient/Source/Sprite/DeformAnimations/SpriteDeformation.cs b/Barotrauma/BarotraumaClient/Source/Sprite/DeformAnimations/SpriteDeformation.cs index 52d33b786..19097bf21 100644 --- a/Barotrauma/BarotraumaClient/Source/Sprite/DeformAnimations/SpriteDeformation.cs +++ b/Barotrauma/BarotraumaClient/Source/Sprite/DeformAnimations/SpriteDeformation.cs @@ -104,14 +104,7 @@ namespace Barotrauma.SpriteDeformations public Point Resolution { - get - { - if (deformationParams.Resolution.X != Deformation.GetLength(0) || deformationParams.Resolution.Y != Deformation.GetLength(1)) - { - Deformation = new Vector2[deformationParams.Resolution.X, deformationParams.Resolution.Y]; - } - return deformationParams.Resolution; - } + get { return deformationParams.Resolution; } set { SetResolution(value); } } @@ -202,6 +195,15 @@ namespace Barotrauma.SpriteDeformations public static Vector2[,] GetDeformation(IEnumerable animations, Vector2 scale) { + foreach (SpriteDeformation animation in animations) + { + if (animation.deformationParams.Resolution.X != animation.Deformation.GetLength(0) || + animation.deformationParams.Resolution.Y != animation.Deformation.GetLength(1)) + { + animation.Deformation = new Vector2[animation.deformationParams.Resolution.X, animation.deformationParams.Resolution.Y]; + } + } + Point resolution = animations.First().Resolution; if (animations.Any(a => a.Resolution != resolution)) { @@ -211,7 +213,6 @@ namespace Barotrauma.SpriteDeformations } Vector2[,] deformation = new Vector2[resolution.X, resolution.Y]; - foreach (SpriteDeformation animation in animations) { animation.GetDeformation(out Vector2[,] animDeformation, out float multiplier); diff --git a/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs index 778e11f82..4198d21b7 100644 --- a/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs @@ -62,7 +62,7 @@ namespace Barotrauma { foreach (RoundSound sound in sounds) { - soundChannel = SoundPlayer.PlaySound(sound.Sound, sound.Volume, sound.Range, entity.WorldPosition, hull); + soundChannel = SoundPlayer.PlaySound(sound.Sound, entity.WorldPosition, sound.Volume, sound.Range, hull); if (soundChannel != null) soundChannel.Looping = loopSound; } } @@ -82,7 +82,7 @@ namespace Barotrauma selectedSoundIndex = Rand.Int(sounds.Count); } var selectedSound = sounds[selectedSoundIndex]; - soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, selectedSound.Volume, selectedSound.Range, entity.WorldPosition, hull); + soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, entity.WorldPosition, selectedSound.Volume, selectedSound.Range, hull); if (soundChannel != null) soundChannel.Looping = loopSound; } } diff --git a/Barotrauma/BarotraumaClient/Source/Utils/LocalizationCSVtoXML.cs b/Barotrauma/BarotraumaClient/Source/Utils/LocalizationCSVtoXML.cs index 67e912b03..a9ad8e446 100644 --- a/Barotrauma/BarotraumaClient/Source/Utils/LocalizationCSVtoXML.cs +++ b/Barotrauma/BarotraumaClient/Source/Utils/LocalizationCSVtoXML.cs @@ -151,6 +151,7 @@ namespace Barotrauma for (int i = traitStart + NPCPersonalityTrait.List.Count; i < csvContent.Length; i++) // Conversations { + string[] presplit = csvContent[i].Split(','); // Handling speaker index fetching, somehow doesn't work with the regex string[] split = SplitCSV(csvContent[i]); int emptyFields = 0; @@ -172,20 +173,20 @@ namespace Barotrauma continue; } - string speaker = split[1]; - int depthIndex = int.Parse(split[2]); + string speaker = presplit[1]; + int depthIndex = int.Parse(presplit[2]); // 3 = original line string line = split[4].Replace("\"", ""); string flags = split[5].Replace("\"", ""); string allowedJobs = split[6].Replace("\"", ""); string speakerTags = split[7].Replace("\"", ""); - string minIntensity = split[8].Replace("\"", ""); - string maxIntensity = split[9].Replace("\"", ""); + string minIntensity = split[8].Replace("\"", "").Replace(",", "."); + string maxIntensity = split[9].Replace("\"", "").Replace(",", "."); string element = $"{GetIndenting(depthIndex)}" + $" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Barotrauma/BarotraumaClient/monomachineconfig b/Barotrauma/BarotraumaClient/monomachineconfig new file mode 100644 index 000000000..455709536 --- /dev/null +++ b/Barotrauma/BarotraumaClient/monomachineconfig @@ -0,0 +1,310 @@ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+ + + +
+ +
+
+
+
+ + + + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+ + +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Barotrauma/BarotraumaClient/mscorlib.dll b/Barotrauma/BarotraumaClient/mscorlib.dll new file mode 100755 index 000000000..3f61223f1 Binary files /dev/null and b/Barotrauma/BarotraumaClient/mscorlib.dll differ diff --git a/Barotrauma/BarotraumaServer/DedicatedServer b/Barotrauma/BarotraumaServer/DedicatedServer new file mode 100755 index 000000000..330b12e4c --- /dev/null +++ b/Barotrauma/BarotraumaServer/DedicatedServer @@ -0,0 +1,35 @@ +#!/bin/bash +# MonoKickstart Shell Script +# Written by Ethan "flibitijibibo" Lee + +# Move to script's directory +cd "`dirname "$0"`" + +# Get the system architecture +UNAME=`uname` +ARCH=`uname -m` + +# MonoKickstart picks the right libfolder, so just execute the right binary. +if [ "$UNAME" == "Darwin" ]; then + # ... Except on OSX. + export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:./osx/ + + # El Capitan is a total idiot and wipes this variable out, making the + # Steam overlay disappear. This sidesteps "System Integrity Protection" + # and resets the variable with Valve's own variable (they provided this + # fix by the way, thanks Valve!). Note that you will need to update your + # launch configuration to the script location, NOT just the app location + # (i.e. Kick.app/Contents/MacOS/Kick, not just Kick.app). + # -flibit + if [ "$STEAM_DYLD_INSERT_LIBRARIES" != "" ] && [ "$DYLD_INSERT_LIBRARIES" == "" ]; then + export DYLD_INSERT_LIBRARIES="$STEAM_DYLD_INSERT_LIBRARIES" + fi + + ./DedicatedServer.bin.osx $@ +else + if [ "$ARCH" == "x86_64" ]; then + ./DedicatedServer.bin.x86_64 $@ + else + ./DedicatedServer.bin.x86 $@ + fi +fi diff --git a/Barotrauma/BarotraumaServer/DedicatedServer.bin.osx b/Barotrauma/BarotraumaServer/DedicatedServer.bin.osx new file mode 100755 index 000000000..98bf55fc7 Binary files /dev/null and b/Barotrauma/BarotraumaServer/DedicatedServer.bin.osx differ diff --git a/Barotrauma/BarotraumaServer/DedicatedServer.bin.x86 b/Barotrauma/BarotraumaServer/DedicatedServer.bin.x86 new file mode 100755 index 000000000..c7c060f04 Binary files /dev/null and b/Barotrauma/BarotraumaServer/DedicatedServer.bin.x86 differ diff --git a/Barotrauma/BarotraumaServer/DedicatedServer.bin.x86_64 b/Barotrauma/BarotraumaServer/DedicatedServer.bin.x86_64 new file mode 100755 index 000000000..47535d05b Binary files /dev/null and b/Barotrauma/BarotraumaServer/DedicatedServer.bin.x86_64 differ diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index bbde03313..b5497c509 100644 --- a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.8.9.10")] -[assembly: AssemblyFileVersion("0.8.9.10")] +[assembly: AssemblyVersion("0.8.10.0")] +[assembly: AssemblyFileVersion("0.8.0.0")] diff --git a/Barotrauma/BarotraumaServer/Server.csproj b/Barotrauma/BarotraumaServer/Server.csproj index 1d344c8fe..3647a5b6d 100644 --- a/Barotrauma/BarotraumaServer/Server.csproj +++ b/Barotrauma/BarotraumaServer/Server.csproj @@ -215,14 +215,26 @@ - - + + PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + - \ No newline at end of file + diff --git a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs index 3b8d62294..d00a8d7eb 100644 --- a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs @@ -21,9 +21,9 @@ namespace Barotrauma { if (!Enabled) { return 1000.0f; } - if (recipient.Character == null) + if (recipient.Character == null || recipient.Character.IsDead) { - return 0.1f; + return 0.2f; } else { @@ -360,7 +360,7 @@ namespace Barotrauma Vector2 relativeCursorPos = cursorPosition - AimRefPosition; tempBuffer.Write((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI))); } - tempBuffer.Write(IsRagdolled); + tempBuffer.Write(IsRagdolled || IsUnconscious || Stun > 0.0f || IsDead); tempBuffer.Write(AnimController.Dir > 0.0f); } diff --git a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs index 1dd05bae8..a7d06ffa1 100644 --- a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs @@ -193,7 +193,7 @@ namespace Barotrauma Console.CursorLeft = 0; Console.CursorTop -= cursorLine; Console.Write(input); - Console.CursorLeft = input.Length % Console.WindowWidth; + Console.CursorLeft = input.Length % consoleWidth; } private static void AssignOnClientRequestExecute(string names, Action onClientRequestExecute) diff --git a/Barotrauma/BarotraumaServer/Source/GameMain.cs b/Barotrauma/BarotraumaServer/Source/GameMain.cs index 6d44fd936..279693165 100644 --- a/Barotrauma/BarotraumaServer/Source/GameMain.cs +++ b/Barotrauma/BarotraumaServer/Source/GameMain.cs @@ -53,6 +53,21 @@ namespace Barotrauma get { return Config.SelectedContentPackages; } } + + private static ContentPackage vanillaContent; + public static ContentPackage VanillaContent + { + get + { + if (vanillaContent == null) + { + // TODO: Dynamic method for defining and finding the vanilla content package. + vanillaContent = ContentPackage.List.SingleOrDefault(cp => Path.GetFileName(cp.Path).ToLowerInvariant() == "vanilla 0.9.xml"); + } + return vanillaContent; + } + } + public readonly string[] CommandLineArgs; public GameMain(string[] args) @@ -83,6 +98,7 @@ namespace Barotrauma LevelGenerationParams.LoadPresets(); ScriptedEventSet.LoadPrefabs(); + AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); StructurePrefab.LoadAll(GetFilesOfType(ContentType.Structure)); ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item)); JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs)); @@ -90,7 +106,6 @@ namespace Barotrauma NPCConversation.LoadAll(GetFilesOfType(ContentType.NPCConversations)); ItemAssemblyPrefab.LoadAll(); LevelObjectPrefab.LoadAll(); - AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); GameModePreset.Init(); LocationType.Init(); diff --git a/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs index 0e1667db4..271e444c2 100644 --- a/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs @@ -168,6 +168,8 @@ namespace Barotrauma msg.Write(isRunning && endWatchman != null ? endWatchman.ID : (UInt16)0); msg.Write(Money); + msg.Write(PurchasedHullRepairs); + msg.Write(PurchasedItemRepairs); msg.Write((UInt16)CargoManager.PurchasedItems.Count); foreach (PurchasedItem pi in CargoManager.PurchasedItems) @@ -192,6 +194,8 @@ namespace Barotrauma { UInt16 selectedLocIndex = msg.ReadUInt16(); byte selectedMissionIndex = msg.ReadByte(); + bool purchasedHullRepairs = msg.ReadBoolean(); + bool purchasedItemRepairs = msg.ReadBoolean(); UInt16 purchasedItemCount = msg.ReadUInt16(); List purchasedItems = new List(); @@ -208,6 +212,33 @@ namespace Barotrauma return; } + if (purchasedHullRepairs != this.PurchasedHullRepairs) + { + if (purchasedHullRepairs && Money >= HullRepairCost) + { + this.PurchasedHullRepairs = true; + Money -= HullRepairCost; + } + else if (!purchasedHullRepairs) + { + this.PurchasedHullRepairs = false; + Money += HullRepairCost; + } + } + if (purchasedItemRepairs != this.PurchasedItemRepairs) + { + if (purchasedItemRepairs && Money >= ItemRepairCost) + { + this.PurchasedItemRepairs = true; + Money -= ItemRepairCost; + } + else if (!purchasedItemRepairs) + { + this.PurchasedItemRepairs = false; + Money += ItemRepairCost; + } + } + Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); if (Map.SelectedConnection != null) { diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Reactor.cs index af7fefd81..5d6439226 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Reactor.cs @@ -20,6 +20,8 @@ namespace Barotrauma.Items.Components if (!item.CanClientAccess(c)) return; + IsActive = true; + if (!autoTemp && AutoTemp) blameOnBroken = c; if (turbineOutput < targetTurbineOutput) blameOnBroken = c; if (fissionRate > targetFissionRate) blameOnBroken = c; diff --git a/Barotrauma/BarotraumaServer/Source/Map/Hull.cs b/Barotrauma/BarotraumaServer/Source/Map/Hull.cs index 3b10a7a24..3fc9ec6d3 100644 --- a/Barotrauma/BarotraumaServer/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaServer/Source/Map/Hull.cs @@ -33,7 +33,8 @@ namespace Barotrauma //update client hulls if the amount of water has changed by >10% //or if oxygen percentage has changed by 5% if (Math.Abs(lastSentVolume - waterVolume) > Volume * 0.1f || - Math.Abs(lastSentOxygen - OxygenPercentage) > 5f) + Math.Abs(lastSentOxygen - OxygenPercentage) > 5f || + FireSources.Count > 0) { sendUpdateTimer -= deltaTime; if (sendUpdateTimer < 0.0f) diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs index d4c20614b..d9137afc7 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs @@ -1754,11 +1754,10 @@ namespace Barotrauma.Networking Log("Game mode: " + selectedMode.Name, ServerLog.MessageType.ServerMessage); Log("Submarine: " + selectedSub.Name, ServerLog.MessageType.ServerMessage); Log("Level seed: " + GameMain.NetLobbyScreen.LevelSeed, ServerLog.MessageType.ServerMessage); - } + } - bool missionAllowRespawn = campaign == null && - (!(GameMain.GameSession.GameMode is MissionMode) || - ((MissionMode)GameMain.GameSession.GameMode).Mission.AllowRespawn); + MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode; + bool missionAllowRespawn = campaign == null && (missionMode?.Mission == null || missionMode.Mission.AllowRespawn); if (serverSettings.AllowRespawn && missionAllowRespawn) respawnManager = new RespawnManager(this, usingShuttle ? selectedShuttle : null); @@ -1767,6 +1766,12 @@ namespace Barotrauma.Networking { var teamID = n == 0 ? Character.TeamType.Team1 : Character.TeamType.Team2; + Submarine.MainSubs[n].TeamID = teamID; + foreach (Submarine sub in Submarine.MainSubs[n].DockedTo) + { + sub.TeamID = teamID; + } + //find the clients in this team List teamClients = teamCount == 1 ? new List(connectedClients) : @@ -1935,10 +1940,8 @@ namespace Barotrauma.Networking MultiPlayerCampaign campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; - bool missionAllowRespawn = campaign == null && - (!(GameMain.GameSession.GameMode is MissionMode) || - ((MissionMode)GameMain.GameSession.GameMode).Mission.AllowRespawn); - + MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode; + bool missionAllowRespawn = campaign == null && (missionMode?.Mission == null || missionMode.Mission.AllowRespawn); msg.Write(serverSettings.AllowRespawn && missionAllowRespawn); msg.Write(Submarine.MainSubs[1] != null); //loadSecondSub diff --git a/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs index 326df08c3..d1d613a94 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -10,9 +10,7 @@ namespace Barotrauma.Networking class ServerEntityEvent : NetEntityEvent { private IServerSerializable serializable; - - public bool Sent; - + #if DEBUG public string StackTrace; #endif diff --git a/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs index 8b489f1a0..593bec08f 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs @@ -182,6 +182,15 @@ namespace Barotrauma.Networking } else { + //tell the respawning client they're no longer a traitor + if (GameMain.Server.TraitorManager != null && clients[i].Character != null) + { + if (GameMain.Server.TraitorManager.TraitorList.Any(t => t.Character == clients[i].Character)) + { + GameMain.Server.SendDirectChatMessage(TextManager.Get("traitorrespawnmessage"), clients[i], ChatMessageType.MessageBox); + } + } + clients[i].Character = character; character.OwnerClientIP = clients[i].Connection.RemoteEndPoint.Address.ToString(); character.OwnerClientName = clients[i].Name; diff --git a/Barotrauma/BarotraumaServer/Source/Networking/SteamManager.cs b/Barotrauma/BarotraumaServer/Source/Networking/SteamManager.cs index 025e42e8b..99b9ac68b 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/SteamManager.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/SteamManager.cs @@ -56,6 +56,7 @@ namespace Barotrauma.Steam Instance.server.SetKey("usingwhitelist", (server.ServerSettings.Whitelist != null && server.ServerSettings.Whitelist.Enabled).ToString()); Instance.server.SetKey("modeselectionmode", server.ServerSettings.ModeSelectionMode.ToString()); Instance.server.SetKey("subselectionmode", server.ServerSettings.SubSelectionMode.ToString()); + Instance.server.SetKey("voicechatenabled", server.ServerSettings.VoiceChatEnabled.ToString()); Instance.server.SetKey("allowspectating", server.ServerSettings.AllowSpectating.ToString()); Instance.server.SetKey("allowrespawn", server.ServerSettings.AllowRespawn.ToString()); Instance.server.SetKey("traitors", server.ServerSettings.TraitorsEnabled.ToString()); diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 529a93864..6cb28c845 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -5,11 +5,15 @@ - - - + + + + + + + + - @@ -70,14 +74,11 @@ - - - diff --git a/Barotrauma/BarotraumaShared/MonoKickstart_LICENSE b/Barotrauma/BarotraumaShared/MonoKickstart_LICENSE new file mode 100644 index 000000000..13efc538d --- /dev/null +++ b/Barotrauma/BarotraumaShared/MonoKickstart_LICENSE @@ -0,0 +1,26 @@ +/* MonoKickstart - Kick Binaries for Mono Applications + * + * Copyright (c) 2012 Edward Rudd. + * Modified in 2013 by Ethan Lee. + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Ethan "flibitijibibo" Lee + * + */ \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedCode.projitems b/Barotrauma/BarotraumaShared/SharedCode.projitems index f1aa81dab..05589e12a 100644 --- a/Barotrauma/BarotraumaShared/SharedCode.projitems +++ b/Barotrauma/BarotraumaShared/SharedCode.projitems @@ -45,6 +45,7 @@ + @@ -180,6 +181,7 @@ + diff --git a/Barotrauma/BarotraumaShared/SharedContent.projitems b/Barotrauma/BarotraumaShared/SharedContent.projitems index 7fae3d1c5..0cfdcd61a 100644 --- a/Barotrauma/BarotraumaShared/SharedContent.projitems +++ b/Barotrauma/BarotraumaShared/SharedContent.projitems @@ -24,6 +24,10 @@ + + + + @@ -357,31 +361,181 @@ PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + PreserveNewest @@ -591,94 +745,94 @@ PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest @@ -1053,6 +1207,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -1089,9 +1246,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -1146,9 +1300,6 @@ PreserveNewest - - PreserveNewest - @@ -1301,7 +1452,7 @@ PreserveNewest - + PreserveNewest @@ -1343,36 +1494,15 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest PreserveNewest - - PreserveNewest - PreserveNewest - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -1568,22 +1698,10 @@ PreserveNewest - + PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - + PreserveNewest @@ -1592,10 +1710,10 @@ PreserveNewest - + PreserveNewest - + PreserveNewest @@ -1910,6 +2028,24 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -1928,6 +2064,72 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -1964,9 +2166,19 @@ PreserveNewest - + PreserveNewest + + PreserveNewest + + + Never + + + Never + + @@ -2464,18 +2676,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -2933,9 +3133,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -3132,10 +3329,10 @@ PreserveNewest - PreserveNewest + Never - PreserveNewest + Never PreserveNewest diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs index 6a3befb3c..7188dc107 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs @@ -12,6 +12,13 @@ namespace Barotrauma { partial class EnemyAIController : AIController { + public static bool DisableEnemyAI; + + /// + /// Enable the character to attack the outposts and the characters inside them. Disabled by default. + /// + public bool TargetOutposts; + class WallTarget { public Vector2 Position; @@ -115,6 +122,7 @@ namespace Barotrauma private readonly float memoryFadeTime = 0.5f; public LatchOntoAI LatchOntoAI { get; private set; } + public SwarmBehavior SwarmBehavior { get; private set; } public bool AttackHumans { @@ -208,6 +216,10 @@ namespace Barotrauma case "latchonto": LatchOntoAI = new LatchOntoAI(subElement, this); break; + case "swarm": + case "swarmbehavior": + SwarmBehavior = new SwarmBehavior(subElement, this); + break; case "targetpriority": targetingPriorities.Add(subElement.GetAttributeString("tag", "").ToLowerInvariant(), new TargetingPriority(subElement)); break; @@ -249,7 +261,7 @@ namespace Barotrauma } } - public TargetingPriority GetTargetingPriority(string targetTag) + private TargetingPriority GetTargetingPriority(string targetTag) { if (targetingPriorities.TryGetValue(targetTag, out TargetingPriority priority)) { @@ -267,6 +279,7 @@ namespace Barotrauma public override void Update(float deltaTime) { + if (DisableEnemyAI) { return; } bool ignorePlatforms = (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X)); if (steeringManager is IndoorsSteeringManager) @@ -356,12 +369,8 @@ namespace Barotrauma default: throw new NotImplementedException(); } - - // Just some debug code that makes the characters to follow the mouse cursor - //run = true; - //Vector2 mousePos = ConvertUnits.ToSimUnits(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition)); - //steeringManager.SteeringSeek(mousePos, Character.AnimController.GetCurrentSpeed(run)); - + + SwarmBehavior?.Update(deltaTime); steeringManager.Update(Character.AnimController.GetCurrentSpeed(run)); } @@ -484,7 +493,7 @@ namespace Barotrauma } else { - if (!IsProperlyLatched) + if (!IsProperlyLatchedOnSub) { UpdateWallTarget(); } @@ -1000,11 +1009,12 @@ namespace Barotrauma private void UpdateEating(float deltaTime) { - if (SelectedAiTarget == null) + if (SelectedAiTarget == null) //SelectedAiTarget.Entity is Character c && !c.IsDead { State = AIState.Idle; return; } + Character targetChar = SelectedAiTarget.Entity as Character; Limb mouthLimb = Array.Find(Character.AnimController.Limbs, l => l != null && l.MouthPos.HasValue); if (mouthLimb == null) mouthLimb = Character.AnimController.GetLimb(LimbType.Head); @@ -1036,14 +1046,14 @@ namespace Barotrauma #region Targeting - private bool IsProperlyLatched => LatchOntoAI != null && LatchOntoAI.IsAttached && SelectedAiTarget?.Entity == wallTarget?.Structure; + private bool IsProperlyLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub && SelectedAiTarget?.Entity == wallTarget?.Structure; //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 TargetingPriority priority) { - if (IsProperlyLatched) + if (IsProperlyLatchedOnSub) { // If attached to a valid target, just keep the target. // Priority not used in this case. @@ -1063,11 +1073,11 @@ namespace Barotrauma continue; } if (target.Type == AITarget.TargetType.HumanOnly) { continue; } - // Don't attack outposts. - if (target.Entity.Submarine != null && target.Entity.Submarine.IsOutpost) { continue; } - + if (!TargetOutposts) + { + if (target.Entity.Submarine != null && target.Entity.Submarine.IsOutpost) { continue; } + } Character targetCharacter = target.Entity as Character; - //ignore the aitarget if it is the Character itself if (targetCharacter == character) continue; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs index f41af8814..c2133c51b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs @@ -267,7 +267,7 @@ namespace Barotrauma if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime)) { Character.Speak( - newOrder.GetChatMessage("", Character.CurrentHull?.RoomName, givingOrderToSelf: false), ChatMessageType.Order); + newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order); } } } @@ -286,18 +286,18 @@ namespace Barotrauma if (Character.PressureTimer > 50.0f && Character.CurrentHull != null) { - Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.RoomName), null, 0, "pressure", 30.0f); + Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.DisplayName), null, 0, "pressure", 30.0f); } } public override void OnAttacked(Character attacker, AttackResult attackResult) { - // Damage from falling etc. - if (Character.LastDamageSource == null) { return; } float damage = attackResult.Damage; if (damage <= 0) { return; } if (attacker == null || attacker.IsDead || attacker.Removed) { + // Ignore damage from falling etc that we shouldn't react to. + if (Character.LastDamageSource == null) { return; } AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced)); } else if (IsFriendly(attacker)) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs index da327ea2d..e264c0f05 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs @@ -218,13 +218,13 @@ namespace Barotrauma } else if (character.Submarine != currentPath.CurrentNode.Submarine) { - pos -= FarseerPhysics.ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position-character.Submarine.Position); + pos -= FarseerPhysics.ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position - character.Submarine.Position); } } } //only humanoids can climb ladders - if (character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent) + if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent) { if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item && currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition)) @@ -234,7 +234,7 @@ namespace Barotrauma } var collider = character.AnimController.Collider; - if (character.IsClimbing) + if (character.IsClimbing && !character.AnimController.InWater) { Vector2 diff = currentPath.CurrentNode.SimPosition - pos; bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent; @@ -278,6 +278,12 @@ namespace Barotrauma } else if (character.AnimController.InWater) { + // If the character is underwater, we don't need the ladders anymore + if (character.IsClimbing) + { + character.AnimController.Anim = AnimController.Animation.None; + character.SelectedConstruction = null; + } if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2)) { currentPath.SkipToNextNode(); @@ -388,13 +394,25 @@ namespace Barotrauma buttonPressCooldown = ButtonPressInterval; break; } + else + { + if (!door.HasRequiredItems(character, false) && shouldBeOpen) + { + currentPath.Unreachable = true; + return; + } + + door.Item.TryInteract(character, false, true, true); + buttonPressCooldown = ButtonPressInterval; + break; + } } } } private float? GetNodePenalty(PathNode node, PathNode nextNode) { - if (character == null) return 0.0f; + if (character == null) { return 0.0f; } float penalty = 0.0f; if (nextNode.Waypoint.ConnectedGap != null && nextNode.Waypoint.ConnectedGap.Open < 0.9f) @@ -403,21 +421,23 @@ namespace Barotrauma { penalty = 100.0f; } - - if (!canBreakDoors) + else if (!canBreakDoors) { //door closed and the character can't open doors -> node can't be traversed - if (!canOpenDoors || character.LockHands) return null; + if (!canOpenDoors || character.LockHands) { return null; } var doorButtons = nextNode.Waypoint.ConnectedDoor.Item.GetConnectedComponents(); - if (!doorButtons.Any()) return null; + if (!doorButtons.Any()) + { + if (!nextNode.Waypoint.ConnectedDoor.HasRequiredItems(character, false)) { return null; } + } foreach (Controller button in doorButtons) { if (Math.Sign(button.Item.Position.X - nextNode.Waypoint.Position.X) != - Math.Sign(node.Position.X - nextNode.Position.X)) continue; + Math.Sign(node.Position.X - nextNode.Position.X)) { continue; } - if (!button.HasRequiredItems(character, false)) return null; + if (!button.HasRequiredItems(character, false)) { return null; } } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs index fe5304a4c..c85b2caa7 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs @@ -54,6 +54,8 @@ namespace Barotrauma get { return attachJoints.Count > 0; } } + public bool IsAttachedToSub => IsAttached && attachTargetBody?.UserData is Entity entity && (entity is Submarine sub || entity?.Submarine != null); + public LatchOntoAI(XElement element, EnemyAIController enemyAI) { attachToWalls = element.GetAttributeBool("attachtowalls", false); @@ -207,10 +209,10 @@ namespace Barotrauma break; } - if (attachTargetBody != null && deattachTimer < 0.0f) + if (IsAttached && attachTargetBody != null && deattachTimer < 0.0f) { Entity entity = attachTargetBody.UserData as Entity; - Submarine attachedSub = entity is Submarine ? (Submarine)entity : entity?.Submarine; + Submarine attachedSub = entity is Submarine sub ? sub : entity?.Submarine; if (attachedSub != null) { float velocity = attachedSub.Velocity == Vector2.Zero ? 0.0f : attachedSub.Velocity.Length(); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs index 6b7dda849..9acf4bc99 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -44,6 +44,7 @@ namespace Barotrauma private AIObjectiveContainItem reloadWeaponObjective; private Hull retreatTarget; private AIObjectiveGoTo retreatObjective; + private AIObjectiveFindSafety findSafety; private float coolDownTimer; @@ -60,7 +61,9 @@ namespace Barotrauma { Enemy = enemy; coolDownTimer = CoolDown; - HumanAIController.ObjectiveManager.GetObjective().Priority = 0; + findSafety = HumanAIController.ObjectiveManager.GetObjective(); + findSafety.Priority = 0; + findSafety.unreachable.Clear(); Mode = mode; if (Enemy == null) { @@ -175,7 +178,7 @@ namespace Barotrauma { if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted)) { - retreatTarget = HumanAIController.ObjectiveManager.GetObjective().FindBestHull(new List() { character.CurrentHull }); + retreatTarget = findSafety.FindBestHull(new List() { character.CurrentHull }); } if (retreatTarget != null) { @@ -235,6 +238,7 @@ namespace Barotrauma { if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range) { + character.SetInput(InputType.Shoot, false, true); Weapon.Use(deltaTime, character); } } @@ -264,6 +268,7 @@ namespace Barotrauma } if (target != null && target == Enemy) { + character.SetInput(InputType.Shoot, false, true); Weapon.Use(deltaTime, character); } } @@ -275,7 +280,6 @@ namespace Barotrauma { abandon = true; SteeringManager.Reset(); - //HumanAIController.ObjectiveManager.GetObjective().Priority = 100; } public override bool IsCompleted() diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 0b5032321..77e9c5901 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { @@ -17,7 +18,7 @@ namespace Barotrauma const float SearchHullInterval = 3.0f; const float clearUnreachableInterval = 30; - private List unreachable = new List(); + public readonly List unreachable = new List(); private float currenthullSafety; private float unreachableClearTimer; @@ -59,11 +60,16 @@ namespace Barotrauma else { divingGearObjective = null; - // Reset the timer so that we get a safe hull target. - searchHullTimer = 0; + // Reduce the timer so that we get a safe hull target faster. + searchHullTimer = Math.Min(1, searchHullTimer); } } + if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD) + { + searchHullTimer = Math.Min(1, searchHullTimer); + } + if (unreachableClearTimer > 0) { unreachableClearTimer -= deltaTime; @@ -78,7 +84,7 @@ namespace Barotrauma { searchHullTimer -= deltaTime; } - else if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD) + else { var bestHull = FindBestHull(); if (bestHull != null && bestHull != currentHull) @@ -187,10 +193,17 @@ namespace Barotrauma float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f; float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist)); hullSafety *= distanceFactor; + //skip the hull if the safety is already less than the best hull + //(no need to do the expensive pathfinding if we already know we're not going to choose this hull) + if (hullSafety < bestValue) { continue; } // Each unsafe node reduces the hull safety value. // Ignore current hull, because otherwise the would block all paths from the current hull to the target hull. var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition); - if (path.Unreachable) { continue; } + if (path.Unreachable) + { + unreachable.Add(hull); + continue; + } int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull)); hullSafety /= 1 + unsafeNodes; // If the target is not inside a friendly submarine, considerably reduce the hull safety. @@ -218,7 +231,6 @@ namespace Barotrauma } } } - // Huge preference for closer targets float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance)); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs index 865f18203..c03fbef43 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -26,6 +26,9 @@ namespace Barotrauma private float standStillTimer; private float walkDuration; + private readonly List targetHulls = new List(20); + private readonly List hullWeights = new List(20); + public AIObjectiveIdle(Character character) : base(character, "") { standStillTimer = Rand.Range(-10.0f, 10.0f); @@ -106,7 +109,7 @@ namespace Barotrauma if (isCurrentHullOK) { // Check that there is no unsafe or forbidden hulls on the way to the target - // Only do this when the current hull is ok, because otherwise the would block all paths from the current hull to the target hull. + // Only do this when the current hull is ok, because otherwise would block all paths from the current hull to the target hull. var path = PathSteering.PathFinder.FindPath(character.SimPosition, randomHull.SimPosition); if (path.Unreachable || path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) @@ -128,8 +131,8 @@ namespace Barotrauma character.AIController.SelectTarget(currentTarget.AiTarget); string errorMsg = null; #if DEBUG - bool isRoomNameFound = currentTarget.RoomName != null; - errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.RoomName : currentTarget.ToString()) + ")"; + bool isRoomNameFound = currentTarget.DisplayName != null; + errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")"; #endif var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsg); PathSteering.SetPath(path); @@ -230,13 +233,9 @@ namespace Barotrauma } } - private readonly List targetHulls = new List(20); - private readonly List hullWeights = new List(20); - private void FindTargetHulls() { bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull); - targetHulls.Clear(); hullWeights.Clear(); foreach (var hull in Hull.hullList) @@ -266,7 +265,6 @@ namespace Barotrauma hullWeights.Add(weight); } } - } private bool IsForbidden(Hull hull) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs index fd2d1c962..d4a9125a8 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs @@ -83,12 +83,12 @@ namespace Barotrauma return; } } + if (repairTool == null) + { + FindRepairTool(); + } if (character.CanInteractWith(Item)) { - if (repairTool == null) - { - FindRepairTool(); - } if (repairTool != null) { OperateRepairTool(deltaTime); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs index 943d7110d..72004ee14 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -83,7 +83,7 @@ namespace Barotrauma if (character.SelectedCharacter == null) { character?.Speak(TextManager.Get("DialogFoundUnconsciousTarget") - .Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.RoomName), + .Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName), null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/SwarmBehavior.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/SwarmBehavior.cs new file mode 100644 index 000000000..3eb217589 --- /dev/null +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/SwarmBehavior.cs @@ -0,0 +1,95 @@ +using FarseerPhysics; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace Barotrauma +{ + class SwarmBehavior + { + private float minDistFromClosest; + private float maxDistFromCenter; + private float cohesion; + + private List members = new List(); + + private AIController ai; + + public SwarmBehavior(XElement element, AIController ai) + { + this.ai = ai; + minDistFromClosest = ConvertUnits.ToSimUnits(element.GetAttributeFloat("mindistfromclosest", 10.0f)); + maxDistFromCenter = ConvertUnits.ToSimUnits(element.GetAttributeFloat("maxdistfromcenter", 1000.0f)); + cohesion = element.GetAttributeFloat("cohesion", 0.1f); + } + + public static void CreateSwarm(IEnumerable swarm) + { + foreach (AICharacter character in swarm) + { + if (character.AIController is EnemyAIController enemyAI && enemyAI.SwarmBehavior != null) + { + enemyAI.SwarmBehavior.members = swarm.ToList(); + } + } + } + + public void Update(float deltaTime) + { + members.RemoveAll(m => m.IsDead || m.Removed); + if (members.Count < 2) { return; } + + //calculate the "center of mass" of the swarm and the distance to the closest character in the swarm + float closestDistSqr = float.MaxValue; + Vector2 center = Vector2.Zero; + AICharacter closest = null; + foreach (AICharacter member in members) + { + center += member.SimPosition; + if (member == ai.Character) { continue; } + float distSqr = Vector2.DistanceSquared(member.SimPosition, ai.Character.SimPosition); + if (distSqr < closestDistSqr) + { + closestDistSqr = distSqr; + closest = member; + } + } + center /= members.Count; + + if (closest == null) { return; } + + //steer away from the closest if too close + float closestDist = (float)Math.Sqrt(closestDistSqr); + if (closestDist < minDistFromClosest) + { + Vector2 diff = closest.SimPosition - ai.SimPosition; + if (diff.LengthSquared() < 0.0001f) + { + diff = Vector2.UnitX; + } + ai.SteeringManager.SteeringManual(deltaTime, -diff); + } + //steer closer to the center of mass if too far + else if (Vector2.DistanceSquared(center, ai.SimPosition) > maxDistFromCenter * maxDistFromCenter) + { + float distFromCenter = Vector2.Distance(center, ai.SimPosition); + ai.SteeringManager.SteeringSeek(center, (distFromCenter - maxDistFromCenter) / 10.0f); + } + + //keep the characters moving in roughly the same direction + if (cohesion > 0.0f) + { + Vector2 avgVel = Vector2.Zero; + foreach (AICharacter member in members) + { + avgVel += member.AnimController.TargetMovement; + } + avgVel /= members.Count; + ai.SteeringManager.SteeringManual(deltaTime, avgVel * cohesion); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs b/Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs index 159604d36..5779f8cec 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs @@ -7,7 +7,7 @@ namespace Barotrauma { //characters that are further than this from the camera (and all clients) //have all their limb physics bodies disabled - const float EnableSimplePhysicsDist = 10000.0f; + const float EnableSimplePhysicsDist = 6000.0f; const float DisableSimplePhysicsDist = EnableSimplePhysicsDist * 0.9f; const float EnableSimplePhysicsDistSqr = EnableSimplePhysicsDist * EnableSimplePhysicsDist; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs index 6e1406f46..6f049f1a6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs @@ -588,6 +588,7 @@ namespace Barotrauma } } + float prevWalkPos = WalkPos; WalkPos -= MainLimb.LinearVelocity.X * (CurrentAnimationParams.CycleSpeed / RagdollParams.JointScale / 100.0f); Vector2 transformedStepSize = Vector2.Zero; @@ -620,8 +621,14 @@ namespace Barotrauma footPos.X += limb.StepOffset.X * Dir; footPos.Y += limb.StepOffset.Y; + bool playFootstepSound = false; if (limb.type == LimbType.LeftFoot) { + if (Math.Sign(Math.Sin(prevWalkPos)) > 0 && Math.Sign(transformedStepSize.Y) < 0) + { + playFootstepSound = true; + } + limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f; limb.DebugTargetPos = footPos + new Vector2( transformedStepSize.X + movement.X * 0.1f, @@ -630,13 +637,20 @@ namespace Barotrauma } else if (limb.type == LimbType.RightFoot) { + if (Math.Sign(Math.Sin(prevWalkPos)) < 0 && Math.Sign(transformedStepSize.Y) > 0) + { + playFootstepSound = true; + } + limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f; limb.DebugTargetPos = footPos + new Vector2( -transformedStepSize.X + movement.X * 0.1f, (-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f); limb.MoveToPos(limb.DebugTargetPos, FootMoveForce); } - +#if CLIENT + if (playFootstepSound) { PlayImpactSound(limb); } +#endif if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID)) { SmoothRotateWithoutWrapping(limb, diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index f1e3c96d7..f941f1d68 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -527,7 +527,12 @@ namespace Barotrauma Limb leftLeg = GetLimb(LimbType.LeftLeg); Limb rightLeg = GetLimb(LimbType.RightLeg); - + + float limpAmount = + character.CharacterHealth.GetAfflictionStrength("damage", leftFoot, true) + + character.CharacterHealth.GetAfflictionStrength("damage", rightFoot, true); + limpAmount = MathHelper.Clamp(limpAmount / 100.0f, 0.0f, 1.0f); + float walkCycleMultiplier = 1.0f; if (Stairs != null) { @@ -556,13 +561,17 @@ namespace Barotrauma float walkPosX = (float)Math.Cos(WalkPos); float walkPosY = (float)Math.Sin(WalkPos); - - + Vector2 stepSize = StepSize.Value; stepSize.X *= walkPosX; - stepSize.Y *= walkPosY; + stepSize.Y *= walkPosY; - float footMid = colliderPos.X;// (leftFoot.SimPosition.X + rightFoot.SimPosition.X) / 2.0f; + float footMid = colliderPos.X; + if (limpAmount > 0.0f) + { + //make the footpos oscillate when limping + footMid += ((float)Math.Max(Math.Abs(walkPosX) * limpAmount, 0.0f) * 0.3f); + } movement = overrideTargetMovement == Vector2.Zero ? MathUtils.SmoothStep(movement, TargetMovement, movementLerp) : @@ -576,7 +585,7 @@ namespace Barotrauma movement.Y = 0.0f; if (torso == null) { return; } - + bool isNotRemote = true; if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) isNotRemote = !character.IsRemotePlayer; @@ -680,7 +689,7 @@ namespace Barotrauma //make the character limp if the feet are damaged float footAfflictionStrength = character.CharacterHealth.GetAfflictionStrength("damage", foot, true); - footPos *= MathHelper.Lerp(1.0f, 0.5f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f)); + footPos.X *= MathHelper.Lerp(1.0f, 0.75f, MathHelper.Clamp(footAfflictionStrength / 50.0f, 0.0f, 1.0f)); if (onSlope && Stairs == null) { @@ -1114,12 +1123,12 @@ namespace Barotrauma #if CLIENT if (Math.Abs(leftFootPos - prevLeftFootPos) > stepHeight && leftFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval) { - SoundPlayer.PlaySound("footstep_armor_heavy", volume: 0.5f, range: 500.0f, position: leftFoot.WorldPosition); + SoundPlayer.PlaySound("footstep_armor_heavy", leftFoot.WorldPosition, hullGuess: currentHull); leftFoot.LastImpactSoundTime = (float)Timing.TotalTime; } if (Math.Abs(rightFootPos - prevRightFootPos) > stepHeight && rightFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval) { - SoundPlayer.PlaySound("footstep_armor_heavy", volume: 0.5f, range: 500.0f, position: rightFoot.WorldPosition); + SoundPlayer.PlaySound("footstep_armor_heavy", rightFoot.WorldPosition, hullGuess: currentHull); rightFoot.LastImpactSoundTime = (float)Timing.TotalTime; } #endif diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs index 3f32ca43d..81677d50f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs @@ -84,6 +84,7 @@ namespace Barotrauma var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty); if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default") { + //DebugConsole.NewMessage("[RagollParams] Using the default folder."); folder = GetDefaultFolder(speciesName); } return folder; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index a1c3bc6a6..8eaca9be2 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -692,7 +692,7 @@ namespace Barotrauma ImpactProjSpecific(impact, f1.Body); } - public void SeverLimbJoint(LimbJoint limbJoint) + public void SeverLimbJoint(LimbJoint limbJoint, bool playSound = true) { if (!limbJoint.CanBeSevered || limbJoint.IsSevered) { @@ -721,7 +721,7 @@ namespace Barotrauma } } - partial void SeverLimbJointProjSpecific(LimbJoint limbJoint); + partial void SeverLimbJointProjSpecific(LimbJoint limbJoint, bool playSound = true); private void GetConnectedLimbs(List connectedLimbs, List checkedJoints, Limb limb) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs b/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs index 0e84aa580..f89ae0d22 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs @@ -80,13 +80,13 @@ namespace Barotrauma public HitDetection HitDetectionType { get; private set; } [Serialize(AIBehaviorAfterAttack.FallBack, true), Editable(ToolTip = "The preferred AI behavior after the attack.")] - public AIBehaviorAfterAttack AfterAttack { get; private set; } + public AIBehaviorAfterAttack AfterAttack { get; set; } [Serialize(false, true), Editable(ToolTip = "Should the ai try to reverse when aiming with this attack?")] public bool Reverse { get; private set; } [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f, ToolTip = "Min distance from the attack limb to the target before the AI tries to attack.")] - public float Range { get; private set; } + public float Range { get; set; } [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f, ToolTip = "Min distance from the attack limb to the target to do damage. In distance based hit detection, the hit will be registered as soon as the target is within the damage range, unless the attack duration has expired.")] public float DamageRange { get; set; } @@ -95,19 +95,19 @@ namespace Barotrauma public float Duration { get; private set; } [Serialize(5f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "How long the AI waits between the attacks.")] - public float CoolDown { get; private set; } = 5; + public float CoolDown { get; set; } = 5; [Serialize(0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "Used as the attack cooldown between different kind of attacks. Does not have effect, if set to 0.")] - public float SecondaryCoolDown { get; private set; } = 0; + public float SecondaryCoolDown { get; set; } = 0; [Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2, ToolTip = "Random factor applied to all cooldowns. Example: 0.1 -> adds a random value between -10% and 10% of the cooldown. Min 0 (default), Max 1 (could disable or double the cooldown in extreme cases).")] public float CoolDownRandomFactor { get; private set; } = 0; [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)] - public float StructureDamage { get; private set; } + public float StructureDamage { get; set; } [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)] - public float ItemDamage { get; private set; } + public float ItemDamage { get; set; } /// /// Legacy support. Use Afflictions. @@ -283,6 +283,7 @@ namespace Barotrauma if (afflictionPrefab == null) { DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found."); + continue; } } else @@ -292,6 +293,7 @@ namespace Barotrauma if (afflictionPrefab == null) { DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found."); + continue; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 1f500d2ac..acf97f741 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -426,7 +426,7 @@ namespace Barotrauma } } - private bool canSpeak; + public bool CanSpeak; private bool speechImpedimentSet; @@ -436,7 +436,7 @@ 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 @@ -710,7 +710,7 @@ namespace Barotrauma displayName = TextManager.Get($"Character.{Path.GetFileName(Path.GetDirectoryName(file))}", true); IsHumanoid = doc.Root.GetAttributeBool("humanoid", false); - canSpeak = doc.Root.GetAttributeBool("canspeak", false); + CanSpeak = doc.Root.GetAttributeBool("canspeak", false); needsAir = doc.Root.GetAttributeBool("needsair", false); Noise = doc.Root.GetAttributeFloat("noise", 100f); @@ -1103,14 +1103,14 @@ namespace Barotrauma if (leftFoot != null) { float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", leftFoot, true); - speed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f)); + speed *= MathHelper.Lerp(1.0f, 0.4f, MathHelper.Clamp(footAfflictionStrength / 80.0f, 0.0f, 1.0f)); } var rightFoot = AnimController.GetLimb(LimbType.RightFoot); if (rightFoot != null) { float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", rightFoot, true); - speed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f)); + speed *= MathHelper.Lerp(1.0f, 0.4f, MathHelper.Clamp(footAfflictionStrength / 80.0f, 0.0f, 1.0f)); } return speed; @@ -1546,12 +1546,11 @@ namespace Barotrauma } } } - - - if (item.InteractDistance == 0.0f && !item.Prefab.Triggers.Any()) return false; + + if (item.InteractDistance == 0.0f && !item.Prefab.Triggers.Any()) { return false; } Pickable pickableComponent = item.GetComponent(); - if (pickableComponent != null && (pickableComponent.Picker != null && !pickableComponent.Picker.IsDead)) return false; + if (pickableComponent != null && (pickableComponent.Picker != null && !pickableComponent.Picker.IsDead)) { return false; } Vector2 characterDirection = Vector2.Transform(Vector2.UnitY, Matrix.CreateRotationZ(AnimController.Collider.Rotation)); @@ -1565,7 +1564,7 @@ namespace Barotrauma } bool insideTrigger = item.IsInsideTrigger(upperBodyPosition) || item.IsInsideTrigger(lowerBodyPosition); - if (item.Prefab.Triggers.Count > 0 && !insideTrigger) return false; + if (item.Prefab.Triggers.Count > 0 && !insideTrigger && item.Prefab.RequireBodyInsideTrigger) { return false; } Rectangle itemDisplayRect = new Rectangle(item.InteractionRect.X, item.InteractionRect.Y - item.InteractionRect.Height, item.InteractionRect.Width, item.InteractionRect.Height); @@ -2130,7 +2129,8 @@ namespace Barotrauma public void Speak(string message, ChatMessageType? messageType = null, float delay = 0.0f, string identifier = "", float minDurationBetweenSimilar = 0.0f) { - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return; + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } + if (string.IsNullOrEmpty(message)) { return; } //already sent a similar message a moment ago if (!string.IsNullOrEmpty(identifier) && minDurationBetweenSimilar > 0.0f && @@ -2139,7 +2139,6 @@ namespace Barotrauma { return; } - aiChatMessageQueue.Add(new AIChatMessage(message, messageType, identifier, delay)); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs index 8dcf98fce..cb7df70ec 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/CharacterInfo.cs @@ -399,7 +399,7 @@ namespace Barotrauma { ID = idCounter; idCounter++; - Name = element.GetAttributeString("name", "unnamed"); + Name = element.GetAttributeString("name", ""); string genderStr = element.GetAttributeString("gender", "male").ToLowerInvariant(); File = element.GetAttributeString("file", ""); SourceElement = GetConfig(File).Root; @@ -423,6 +423,29 @@ namespace Barotrauma element.GetAttributeInt("beardindex", -1), element.GetAttributeInt("moustacheindex", -1), element.GetAttributeInt("faceattachmentindex", -1)); + + if (string.IsNullOrEmpty(Name)) + { + if (SourceElement.Element("name") != null) + { + string firstNamePath = SourceElement.Element("name").GetAttributeString("firstname", ""); + if (firstNamePath != "") + { + firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male"); + Name = ToolBox.GetRandomLine(firstNamePath); + } + + string lastNamePath = SourceElement.Element("name").GetAttributeString("lastname", ""); + if (lastNamePath != "") + { + lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male"); + if (Name != "") Name += " "; + Name += ToolBox.GetRandomLine(lastNamePath); + } + } + } + + StartItemsGiven = element.GetAttributeBool("startitemsgiven", false); string personalityName = element.GetAttributeString("personality", ""); ragdollFileName = element.GetAttributeString("ragdoll", string.Empty); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs index 9a25fcd52..4f5769f34 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs @@ -231,7 +231,8 @@ namespace Barotrauma : afflictions.Concat(limbHealths.SelectMany(lh => lh.Afflictions.Where(limbHealthFilter))); } - private LimbHealth GetMathingLimbHealth(Affliction affliction) => limbHealths[Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb).HealthIndex]; + private LimbHealth GetMatchingLimbHealth(Limb limb) => limbHealths[limb.HealthIndex]; + private LimbHealth GetMathingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb)); /// /// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb. @@ -265,6 +266,12 @@ namespace Barotrauma public Affliction GetAffliction(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; + } foreach (Affliction affliction in limbHealths[limb.HealthIndex].Afflictions) { if (affliction.Prefab.AfflictionType == afflictionType) return affliction; @@ -466,7 +473,13 @@ namespace Barotrauma private void AddLimbAffliction(Limb limb, Affliction newAffliction) { - if (!newAffliction.Prefab.LimbSpecific) return; + if (!newAffliction.Prefab.LimbSpecific || limb == null) return; + 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; + } AddLimbAffliction(limbHealths[limb.HealthIndex], newAffliction); } @@ -611,6 +624,12 @@ namespace Barotrauma partial void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb, float deltaTime); + public void SetVitality(float newVitality) + { + maxVitality = newVitality; + CalculateVitality(); + } + public void CalculateVitality() { Vitality = MaxVitality; diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 3cdabed81..73736a5fd 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -228,7 +228,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); + GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnItem:Error", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + '\n' + e.Message + '\n' + e.StackTrace); } }, () => @@ -255,8 +255,10 @@ namespace Barotrauma commands.Add(new Command("disablecrewai", "disablecrewai: Disable the AI of the NPCs in the crew.", (string[] args) => { - ThrowError("Karma has not been fully implemented yet, and is disabled in this version of Barotrauma."); - return; + HumanAIController.DisableCrewAI = true; + NewMessage("Crew AI disabled", Color.Red); + // This is probably not where it should be? + //ThrowError("Karma has not been fully implemented yet, and is disabled in this version of Barotrauma."); /*if (GameMain.Server == null) return; GameMain.Server.KarmaEnabled = !GameMain.Server.KarmaEnabled;*/ })); @@ -264,7 +266,19 @@ namespace Barotrauma commands.Add(new Command("enablecrewai", "enablecrewai: Enable the AI of the NPCs in the crew.", (string[] args) => { HumanAIController.DisableCrewAI = false; - NewMessage("Crew AI enabled", Color.White); + NewMessage("Crew AI enabled", Color.Green); + }, isCheat: true)); + + commands.Add(new Command("disableenemyai", "disableenemyai: Disable the AI of the Enemy characters (monsters).", (string[] args) => + { + EnemyAIController.DisableEnemyAI = true; + NewMessage("Enemy AI disabled", Color.Red); + }, isCheat: true)); + + commands.Add(new Command("enableenemyai", "enableenemyai: Enable the AI of the Enemy characters (monsters).", (string[] args) => + { + EnemyAIController.DisableEnemyAI = false; + NewMessage("Enemy AI enabled", Color.Green); }, isCheat: true)); commands.Add(new Command("botcount", "botcount [x]: Set the number of bots in the crew in multiplayer.", null)); @@ -1447,12 +1461,26 @@ namespace Barotrauma if (spawnPos != null) { - Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos); - + if (Entity.Spawner == null) + { + new Item(itemPrefab, spawnPos.Value, null); + } + else + { + Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnPos.Value); + } } else if (spawnInventory != null) { - Entity.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory); + if (Entity.Spawner == null) + { + var spawnedItem = new Item(itemPrefab, Vector2.Zero, null); + spawnInventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots); + } + else + { + Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnInventory); + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs index e8f5e87fd..7df6131cd 100644 --- a/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs @@ -55,9 +55,9 @@ namespace Barotrauma get { return true; } } - public virtual Vector2 SonarPosition + public virtual IEnumerable SonarPositions { - get { return Vector2.Zero; } + get { return Enumerable.Empty(); } } public string SonarLabel @@ -91,6 +91,13 @@ namespace Barotrauma Messages[m] = Messages[m].Replace("[location" + (n + 1) + "]", locations[n].Name); } } + if (description != null) description = description.Replace("[reward]", Reward.ToString("N0")); + if (successMessage != null) successMessage = successMessage.Replace("[reward]", Reward.ToString("N0")); + if (failureMessage != null) failureMessage = failureMessage.Replace("[reward]", Reward.ToString("N0")); + for (int m = 0; m < Messages.Count; m++) + { + Messages[m] = Messages[m].Replace("[reward]", Reward.ToString("N0")); + } } public static Mission LoadRandom(Location[] locations, string seed, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer = false) { diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/MonsterMission.cs index e4dcdfc46..e37aeed7d 100644 --- a/Barotrauma/BarotraumaShared/Source/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/MonsterMission.cs @@ -1,4 +1,6 @@ using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; namespace Barotrauma { @@ -8,32 +10,38 @@ namespace Barotrauma private int state; - private Character monster; + private int monsterCount; - private Vector2 sonarPosition; + private readonly List monsters = new List(); + private readonly List sonarPositions = new List(); - public override Vector2 SonarPosition + public override IEnumerable SonarPositions { - get { return monster != null && !monster.IsDead ? sonarPosition : Vector2.Zero; } + get + { + return sonarPositions; + } } public MonsterMission(MissionPrefab prefab, Location[] locations) : base(prefab, locations) { monsterFile = prefab.ConfigElement.GetAttributeString("monsterfile", ""); + monsterCount = prefab.ConfigElement.GetAttributeInt("monstercount", 1); } public override void Start(Level level) { Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos); - bool isClient = false; -#if CLIENT - isClient = GameMain.Client != null; -#endif - monster = Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), null, isClient, true, false); - monster.Enabled = false; - sonarPosition = spawnPos; + bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient; + for (int i = 0; i < monsterCount; i++) + { + monsters.Add(Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), null, isClient, true, false)); + } + monsters.ForEach(m => m.Enabled = false); + SwarmBehavior.CreateSwarm(monsters.Cast()); + sonarPositions.Add(spawnPos); } public override void Update(float deltaTime) @@ -41,12 +49,23 @@ namespace Barotrauma switch (state) { case 0: - if (monster.Enabled) + sonarPositions.Clear(); + var activeMonsters = monsters.Where(m => m != null && !m.Removed && !m.IsDead); + if (activeMonsters.Any()) { - sonarPosition = monster.Position; + Vector2 centerOfMass = Vector2.Zero; + foreach (var monster in activeMonsters) + { + //don't add another label if there's another monster roughly at the same spot + if (sonarPositions.All(p => Vector2.DistanceSquared(p, monster.Position) > 1000.0f * 1000.0f)) + { + sonarPositions.Add(monster.Position); + } + } } - if (!monster.IsDead) return; + + if (activeMonsters.Any()) { return; } #if CLIENT ShowMessage(state); #endif @@ -57,10 +76,9 @@ namespace Barotrauma public override void End() { - if (!monster.IsDead) return; + if (!monsters.All(m => m.Removed || m.IsDead)) { return; } GiveReward(); - completed = true; } } diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs index d013158a2..7d5d16917 100644 --- a/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs @@ -1,6 +1,8 @@ using FarseerPhysics; using Microsoft.Xna.Framework; using System; +using System.Collections.Generic; +using System.Linq; namespace Barotrauma { @@ -14,11 +16,18 @@ namespace Barotrauma private int state; - public override Vector2 SonarPosition + public override IEnumerable SonarPositions { get { - return state > 0 ? Vector2.Zero : ConvertUnits.ToDisplayUnits(item.SimPosition); + if (state > 0 ) + { + Enumerable.Empty(); + } + else + { + yield return ConvertUnits.ToDisplayUnits(item.SimPosition); + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Events/MonsterEvent.cs b/Barotrauma/BarotraumaShared/Source/Events/MonsterEvent.cs index 236ce85b2..6c65cee87 100644 --- a/Barotrauma/BarotraumaShared/Source/Events/MonsterEvent.cs +++ b/Barotrauma/BarotraumaShared/Source/Events/MonsterEvent.cs @@ -13,7 +13,7 @@ namespace Barotrauma private int minAmount, maxAmount; - private Character[] monsters; + private List monsters; private bool spawnDeep; @@ -209,6 +209,7 @@ namespace Barotrauma //isActive = false; + bool spawnReady = false; if (spawnPending) { //wait until there are no submarines at the spawnpos @@ -219,25 +220,31 @@ namespace Barotrauma if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos) < minDist * minDist) return; } + spawnPending = false; + //+1 because Range returns an integer less than the max value int amount = Rand.Range(minAmount, maxAmount + 1, Rand.RandSync.Server); - monsters = new Character[amount]; - + monsters = new List(); + float offsetAmount = spawnPosType == Level.PositionType.MainPath ? 1000 : 100; for (int i = 0; i < amount; i++) { - bool isClient = false; -#if CLIENT - isClient = GameMain.Client != null; -#endif - - monsters[i] = Character.Create( - characterFile, spawnPos + Rand.Vector(100.0f, Rand.RandSync.Server), - i.ToString(), null, isClient, true, true); + CoroutineManager.InvokeAfter(() => + { + bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient; + monsters.Add(Character.Create(characterFile, spawnPos + Rand.Vector(offsetAmount, Rand.RandSync.Server), i.ToString(), null, isClient, true, true)); + if (monsters.Count == amount) + { + spawnReady = true; + //this will do nothing if the monsters have no swarm behavior defined, + //otherwise it'll make the spawned characters act as a swarm + SwarmBehavior.CreateSwarm(monsters.Cast()); + } + }, Rand.Range(0f, amount / 2, Rand.RandSync.Server)); } - - spawnPending = false; } + if (!spawnReady) { return; } + Entity targetEntity = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter); #if CLIENT if (Character.Controlled != null) targetEntity = (Entity)Character.Controlled; diff --git a/Barotrauma/BarotraumaShared/Source/Extensions/StringFormatter.cs b/Barotrauma/BarotraumaShared/Source/Extensions/StringFormatter.cs index 541879e2d..9aa6cdada 100644 --- a/Barotrauma/BarotraumaShared/Source/Extensions/StringFormatter.cs +++ b/Barotrauma/BarotraumaShared/Source/Extensions/StringFormatter.cs @@ -8,6 +8,22 @@ namespace Barotrauma { public static class StringFormatter { + public static string Replace(this string s, string replacement, Func predicate) + { + var newString = new string[s.Length]; + for (int i = 0; i < s.Length; i++) + { + char letter = s[i]; + string newLetter = letter.ToString(); + if (predicate(letter)) + { + newLetter = replacement; + } + newString[i] = newLetter; + } + return new string(newString.SelectMany(str => str.ToCharArray()).ToArray()); + } + public static string Remove(this string s, Func predicate) { return new string(s.ToCharArray().Where(c => !predicate(c)).ToArray()); diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs index aa0aa8fda..271dd6dc1 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/CargoManager.cs @@ -108,7 +108,7 @@ namespace Barotrauma } #if CLIENT - new GUIMessageBox("", TextManager.Get("CargoSpawnNotification").Replace("[roomname]", cargoRoom.RoomName)); + new GUIMessageBox("", TextManager.Get("CargoSpawnNotification").Replace("[roomname]", cargoRoom.DisplayName)); #endif Dictionary availableContainers = new Dictionary(); diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs index 220e756cd..f081d1c0e 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs @@ -13,6 +13,7 @@ namespace Barotrauma public bool CheatsEnabled; const int InitialMoney = 4700; + public const int HullRepairCost = 500, ItemRepairCost = 500; protected bool watchmenSpawned; protected Character startWatchman, endWatchman; @@ -20,6 +21,8 @@ namespace Barotrauma //key = dialog flag, double = Timing.TotalTime when the line was last said private Dictionary dialogLastSpoken = new Dictionary(); + public bool PurchasedHullRepairs, PurchasedItemRepairs; + protected Map map; public Map Map { @@ -70,6 +73,37 @@ namespace Barotrauma watchmenSpawned = false; startWatchman = null; endWatchman = null; + + if (PurchasedHullRepairs) + { + foreach (Structure wall in Structure.WallList) + { + if (wall.Submarine == null || wall.Submarine.IsOutpost) { continue; } + if (wall.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(wall.Submarine)) + { + for (int i = 0; i < wall.SectionCount; i++) + { + wall.AddDamage(i, -100000.0f); + } + } + } + PurchasedHullRepairs = false; + } + if (PurchasedItemRepairs) + { + foreach (Item item in Item.ItemList) + { + if (item.Submarine == null || item.Submarine.IsOutpost) { continue; } + if (item.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(item.Submarine)) + { + if (item.GetComponent() != null) + { + item.Condition = item.Health; + } + } + } + PurchasedItemRepairs = false; + } } public override void Update(float deltaTime) diff --git a/Barotrauma/BarotraumaShared/Source/GameSettings.cs b/Barotrauma/BarotraumaShared/Source/GameSettings.cs index 53d1d1ead..e85d877db 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSettings.cs @@ -44,6 +44,7 @@ namespace Barotrauma public bool ChromaticAberrationEnabled { get; set; } public bool MuteOnFocusLost { get; set; } + public bool UseDirectionalVoiceChat { get; set; } public enum VoiceMode { @@ -149,6 +150,9 @@ namespace Barotrauma public bool EnableMouseLook { get; set; } = true; + public bool CrewMenuOpen { get; set; } = true; + public bool ChatOpen { get; set; } = true; + private bool unsavedSettings; public bool UnsavedSettings { @@ -267,6 +271,8 @@ namespace Barotrauma public static bool VerboseLogging { get; set; } public static bool SaveDebugConsoleLogs { get; set; } + public bool CampaignDisclaimerShown, EditorDisclaimerShown; + private static bool sendUserStatistics; public static bool SendUserStatistics { @@ -826,6 +832,8 @@ namespace Barotrauma SoundVolume = audioSettings.GetAttributeFloat("soundvolume", SoundVolume); MusicVolume = audioSettings.GetAttributeFloat("musicvolume", MusicVolume); VoiceChatVolume = audioSettings.GetAttributeFloat("voicechatvolume", VoiceChatVolume); + MuteOnFocusLost = audioSettings.GetAttributeBool("muteonfocuslost", false); + UseDirectionalVoiceChat = audioSettings.GetAttributeBool("usedirectionalvoicechat", true); string voiceSettingStr = audioSettings.GetAttributeString("voicesetting", "Disabled"); VoiceCaptureDevice = audioSettings.GetAttributeString("voicecapturedevice", ""); NoiseGateThreshold = audioSettings.GetAttributeFloat("noisegatethreshold", -45); @@ -844,6 +852,12 @@ namespace Barotrauma AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", AimAssistAmount); EnableMouseLook = doc.Root.GetAttributeBool("enablemouselook", EnableMouseLook); + CrewMenuOpen = doc.Root.GetAttributeBool("crewmenuopen", CrewMenuOpen); + ChatOpen = doc.Root.GetAttributeBool("chatopen", ChatOpen); + + CampaignDisclaimerShown = doc.Root.GetAttributeBool("campaigndisclaimershown", false); + EditorDisclaimerShown = doc.Root.GetAttributeBool("editordisclaimershown", false); + foreach (XElement subElement in doc.Root.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -966,14 +980,8 @@ namespace Barotrauma ToolBox.IsProperFilenameCase(file.Path); } } - if (!SelectedContentPackages.Any()) - { - var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage); - if (availablePackage != null) - { - SelectedContentPackages.Add(availablePackage); - } - } + + EnsureCoreContentPackageSelected(); //save to get rid of the invalid selected packages in the config file if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); } @@ -992,6 +1000,25 @@ namespace Barotrauma .Replace("[gameversion]", GameMain.Version.ToString())); } } + + public void EnsureCoreContentPackageSelected() + { + if (SelectedContentPackages.Any(cp => cp.CorePackage)) { return; } + + if (GameMain.VanillaContent != null) + { + SelectedContentPackages.Add(GameMain.VanillaContent); + } + else + { + var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage); + if (availablePackage != null) + { + SelectedContentPackages.Add(availablePackage); + } + } + } + #endregion #region Save PlayerConfig @@ -1019,7 +1046,11 @@ namespace Barotrauma new XAttribute("requiresteamauthentication", requireSteamAuthentication), new XAttribute("autoupdateworkshopitems", AutoUpdateWorkshopItems), new XAttribute("aimassistamount", aimAssistAmount), - new XAttribute("enablemouselook", EnableMouseLook)); + new XAttribute("enablemouselook", EnableMouseLook), + new XAttribute("chatopen", ChatOpen), + new XAttribute("crewmenuopen", CrewMenuOpen), + new XAttribute("campaigndisclaimershown", CampaignDisclaimerShown), + new XAttribute("editordisclaimershown", EditorDisclaimerShown)); if (!ShowUserStatisticsPrompt) { @@ -1055,6 +1086,8 @@ namespace Barotrauma audio.ReplaceAttributes( new XAttribute("musicvolume", musicVolume), new XAttribute("soundvolume", soundVolume), + new XAttribute("muteonfocuslost", MuteOnFocusLost), + new XAttribute("usedirectionalvoicechat", UseDirectionalVoiceChat), new XAttribute("voicesetting", VoiceSetting), new XAttribute("voicecapturedevice", VoiceCaptureDevice ?? ""), new XAttribute("noisegatethreshold", NoiseGateThreshold)); diff --git a/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs index f35cd6375..4b8d00255 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs @@ -56,7 +56,7 @@ namespace Barotrauma switch (SlotTypes[i]) { //case InvSlotType.Head: - case InvSlotType.OuterClothes: + //case InvSlotType.OuterClothes: case InvSlotType.LeftHand: case InvSlotType.RightHand: hideEmptySlot[i] = true; @@ -176,6 +176,9 @@ namespace Barotrauma { if (allowedSlot.HasFlag(SlotTypes[i]) && Items[i] != null && Items[i] != item) { +#if CLIENT + if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; } +#endif if (!Items[i].AllowedSlots.Contains(InvSlotType.Any) || !TryPutItem(Items[i], character, new List { InvSlotType.Any }, true)) { free = false; @@ -195,6 +198,9 @@ namespace Barotrauma { if (allowedSlot.HasFlag(SlotTypes[i]) && Items[i] == null) { +#if CLIENT + if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; } +#endif bool removeFromOtherSlots = item.ParentInventory != this; if (placedInSlot == -1 && inWrongSlot) { @@ -248,7 +254,9 @@ namespace Barotrauma GameAnalyticsManager.AddErrorEventOnce("CharacterInventory.TryPutItem:IndexOutOfRange", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return false; } - +#if CLIENT + if (PersonalSlots.HasFlag(SlotTypes[index])) { hidePersonalSlots = false; } +#endif //there's already an item in the slot if (Items[index] != null) { @@ -273,7 +281,9 @@ namespace Barotrauma foreach (InvSlotType allowedSlot in allowedSlots) { if (!allowedSlot.HasFlag(SlotTypes[index])) continue; - +#if CLIENT + if (PersonalSlots.HasFlag(allowedSlot)) { hidePersonalSlots = false; } +#endif for (int i = 0; i < capacity; i++) { if (allowedSlot.HasFlag(SlotTypes[i]) && Items[i] != null && Items[i] != item) @@ -286,6 +296,8 @@ namespace Barotrauma } } + + if (!slotsFree) return false; return TryPutItem(item, user, new List() { placeToSlots }, createNetworkEvent); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 73826cd2c..973b7e655 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -10,6 +10,7 @@ using System.Xml.Linq; #if CLIENT using Barotrauma.Lights; #endif +using Barotrauma.Extensions; namespace Barotrauma.Items.Components { @@ -126,6 +127,9 @@ namespace Barotrauma.Items.Components OpenState = (isOpen) ? 1.0f : 0.0f; } } + + [Serialize(false, false)] + public bool HasIntegratedButtons { get; private set; } public float OpenState { @@ -210,34 +214,71 @@ namespace Barotrauma.Items.Components #endif } - public override bool HasRequiredItems(Character character, bool addMessage) + private string accessDeniedTxt = TextManager.Get("AccessDenied"); + private string cannotOpenText = TextManager.Get("DoorMsgCannotOpen"); + private bool hasValidIdCard; + public override bool HasRequiredItems(Character character, bool addMessage, string msg = null) { if (item.Condition <= RepairThreshold) return true; //For repairing + var idCard = character.Inventory.FindItemByIdentifier("idcard"); + hasValidIdCard = requiredItems.Any(ri => ri.Value.Any(r => r.MatchesItem(idCard))); + Msg = requiredItems.None() || hasValidIdCard ? "ItemMsgOpen" : "ItemMsgForceOpenCrowbar"; + ParseMsg(); + if (addMessage) + { + msg = msg ?? (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText); + } + //this is a bit pointless atm because if canBePicked is false it won't allow you to do Pick() anyway, however it's still good for future-proofing. - return requiredItems.Any() ? base.HasRequiredItems(character, addMessage) : canBePicked; + return requiredItems.Any() ? base.HasRequiredItems(character, addMessage, msg) : canBePicked; } public override bool Pick(Character picker) { - return item.Condition <= RepairThreshold ? true : base.Pick(picker); + if (item.Condition <= RepairThreshold) { return true; } + if (requiredItems.None()) { return false; } + if (HasRequiredItems(picker, false) && hasValidIdCard) { return false; } + return base.Pick(picker); } public override bool OnPicked(Character picker) { if (item.Condition <= RepairThreshold) return true; //repairs + if (requiredItems.Any() && !hasValidIdCard) + { + ForceOpen(ActionType.OnPicked); + } + return false; + } + private void ForceOpen(ActionType actionType) + { SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true); //crowbar function #if CLIENT - PlaySound(ActionType.OnPicked, item.WorldPosition, picker); + PlaySound(actionType, item.WorldPosition, picker); #endif - return false; } public override bool Select(Character character) { //can only be selected if the item is broken - return item.Condition <= RepairThreshold; + if (item.Condition <= RepairThreshold) return true; //repairs + bool hasRequiredItems = HasRequiredItems(character, false); + if (requiredItems.None() || hasRequiredItems && hasValidIdCard) + { + float originalPickingTime = PickingTime; + PickingTime = 0; + ForceOpen(ActionType.OnUse); + PickingTime = originalPickingTime; + } + else if (hasRequiredItems) + { +#if CLIENT + GUI.AddMessage(accessDeniedTxt, Color.Red); +#endif + } + return false; } public override void Update(float deltaTime, Camera cam) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ElectricalDischarger.cs index 501491c11..001155e91 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ElectricalDischarger.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ElectricalDischarger.cs @@ -34,6 +34,20 @@ namespace Barotrauma.Items.Components } } + public override bool IsActive + { + get { return base.IsActive; } + set + { + base.IsActive = value; + if (!value) + { + nodes.Clear(); + charactersInRange.Clear(); + } + } + } + [Serialize(100.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)] public float Range { @@ -126,14 +140,19 @@ namespace Barotrauma.Items.Components } else { - nodes.Clear(); - charactersInRange.Clear(); IsActive = false; } voltage = 0.0f; } + public override void UpdateBroken(float deltaTime, Camera cam) + { + base.UpdateBroken(deltaTime, cam); + nodes.Clear(); + charactersInRange.Clear(); + } + private void Discharge() { ApplyStatusEffects(ActionType.OnUse, 1.0f); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index fdc3b8a06..ffb107200 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -44,7 +44,11 @@ namespace Barotrauma.Items.Components public bool Attached { get { return attached && item.ParentInventory == null; } - set { attached = value; } + set + { + attached = value; + item.SetActiveSprite(); + } } [Serialize(true, true)] @@ -167,7 +171,7 @@ namespace Barotrauma.Items.Components if (item.Submarine.Loading) { AttachToWall(); - attached = false; + Attached = false; } else //the submarine is not being loaded, which means we're either in the sub editor or the item has been spawned mid-round { @@ -409,14 +413,14 @@ namespace Barotrauma.Items.Components PickKey = prevPickKey; requiredItems = new Dictionary>(prevRequiredItems); - attached = true; + Attached = true; } public void DeattachFromWall() { if (!attachable) return; - attached = false; + Attached = false; //make the item pickable with the default pick key and with no specific tools/items when it's deattached requiredItems.Clear(); @@ -529,6 +533,7 @@ namespace Barotrauma.Items.Components { if (item.Submarine != null && item.Submarine.Loading) return; OnMapLoaded(); + item.SetActiveSprite(); } public override void OnMapLoaded() diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs index 90a7a0665..d557dd19d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs @@ -11,6 +11,12 @@ namespace Barotrauma.Items.Components { private float lastSentDeattachTimer; + private PhysicsBody trigger; + + private Holdable holdable; + + private float deattachTimer; + [Serialize(1.0f, false)] public float DeattachDuration { @@ -49,13 +55,7 @@ namespace Barotrauma.Items.Components #endif } } - - private PhysicsBody trigger; - - private Holdable holdable; - - private float deattachTimer; - + public LevelResource(Item item, XElement element) : base(item, element) { IsActive = true; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs index dcf0c00a0..69aa69502 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs @@ -122,7 +122,14 @@ namespace Barotrauma.Items.Components pickTimer = 0.0f; while (pickTimer < requiredTime && Screen.Selected != GameMain.SubEditorScreen) { - if (picker.IsKeyDown(InputType.Aim) || !picker.CanInteractWith(item)) + //cancel if the item is currently selected + //attempting to pick does not select the item, so if it is selected at this point, another ItemComponent + //must have been selected and we should not keep deattaching (happens when for example interacting with + //an electrical component while holding both a screwdriver and a wrench). + if (picker.SelectedConstruction == item || + picker.IsKeyDown(InputType.Aim) || + !picker.CanInteractWith(item) || + item.Removed || item.ParentInventory != null) { StopPicking(picker); yield return CoroutineStatus.Success; @@ -135,9 +142,8 @@ namespace Barotrauma.Items.Components pickTimer / requiredTime, Color.Red, Color.Green); #endif - - picker.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f)); + picker.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f)); pickTimer += CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RangedWeapon.cs index 966bd5761..b15306910 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RangedWeapon.cs @@ -122,10 +122,12 @@ namespace Barotrauma.Items.Components foreach (Item subItem in containedSubItems) { projectile = subItem.GetComponent(); - //apply OnUse statuseffects to the container in case it has to react to it somehow //(play a sound, spawn more projectiles, reduce condition...) - subItem.GetComponent()?.Item.ApplyStatusEffects(ActionType.OnUse, deltaTime); + if (subItem.Condition > 0.0f) + { + subItem.GetComponent()?.Item.ApplyStatusEffects(ActionType.OnUse, deltaTime); + } if (projectile != null) break; } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs index c4fcb94ec..188d307c5 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs @@ -293,10 +293,35 @@ namespace Barotrauma.Items.Components //steer closer if almost in range if (dist > Range) { - Vector2 standPos = leak.IsHorizontal ? new Vector2(Math.Sign(-fromItemToLeak.X), 0.0f) : new Vector2(0.0f, Math.Sign(-fromItemToLeak.Y) * 0.5f); - standPos = leak.WorldPosition + standPos * Range; - Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition); - character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2); + Vector2 standPos = new Vector2(Math.Sign(-fromItemToLeak.X), Math.Sign(-fromItemToLeak.Y)) / 2; + if (!character.AnimController.InWater) + { + if (leak.IsHorizontal) + { + standPos.X *= 2; + standPos.Y = 0; + } + else + { + standPos.X = 0; + } + } + if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering) + { + if (indoorSteering.CurrentPath != null && !indoorSteering.IsPathDirty && indoorSteering.CurrentPath.Unreachable) + { + Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition); + character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2); + } + else + { + character.AIController.SteeringManager.SteeringSeek(standPos); + } + } + else + { + character.AIController.SteeringManager.SteeringSeek(standPos); + } } else { @@ -305,29 +330,29 @@ namespace Barotrauma.Items.Components // Too close -> steer away character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition) / 2); } - else + else if (dist <= Range) { + // In range character.AIController.SteeringManager.Reset(); } + else + { + return false; + } } - sinTime += deltaTime; character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist); if (item.RequireAimToUse) { character.SetInput(InputType.Aim, false, true); } - // Press the trigger only when the tool is approximately facing the target. - // If the character is climbing, ignore the check, because we cannot aim while climbing. - if (VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4) + var angle = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak); + if (angle < MathHelper.PiOver4) { + character.SetInput(InputType.Shoot, false, true); Use(deltaTime, character); } - else - { - sinTime -= deltaTime * 2; - } bool leakFixed = (leak.Open <= 0.0f || leak.Removed) && (leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1); @@ -337,11 +362,11 @@ namespace Barotrauma.Items.Components sinTime = 0; if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f)) { - character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leaksfixed", 10.0f); + character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.DisplayName), null, 0.0f, "leaksfixed", 10.0f); } else { - character.Speak(TextManager.Get("DialogLeakFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leakfixed", 10.0f); + character.Speak(TextManager.Get("DialogLeakFixed").Replace("[roomname]", leak.FlowTargetHull.DisplayName), null, 0.0f, "leakfixed", 10.0f); } } @@ -360,6 +385,27 @@ namespace Barotrauma.Items.Components { effect.Apply(actionType, deltaTime, item, targets); } +#if CLIENT + // Hard-coded progress bars for welding doors stuck. + // A general purpose system could be better, but it would most likely require changes in the way we define the status effects in xml. + foreach (ISerializableEntity target in targets) + { + if (target is Door door) + { + for (int i = 0; i < effect.propertyNames.Length; i++) + { + string propertyName = effect.propertyNames[i]; + if (propertyName != "stuck") { continue; } + if (door.SerializableProperties == null || !door.SerializableProperties.TryGetValue(propertyName, out SerializableProperty property)) { continue; } + object value = property.GetValue(target); + if (value.GetType() == typeof(float)) + { + user.UpdateHUDProgressBar(door, door.Item.WorldPosition, (float)value / 100, Color.DarkGray * 0.5f, Color.White); + } + } + } + } +#endif } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs index ca1677eac..fa9ff9e17 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs @@ -190,7 +190,7 @@ namespace Barotrauma.Items.Components get { return name; } } - [Editable, Serialize("", true)] + [Editable, Serialize("", true, translationTextTag: "ItemMsg")] public string Msg { get; @@ -219,6 +219,7 @@ namespace Barotrauma.Items.Components requiredSkills = new List(); #if CLIENT + hasSoundsOfType = new bool[Enum.GetValues(typeof(ActionType)).Length]; sounds = new Dictionary>(); #endif @@ -554,7 +555,7 @@ namespace Barotrauma.Items.Components public virtual void FlipY(bool relativeToSub) { } - public bool HasRequiredContainedItems(bool addMessage) + public bool HasRequiredContainedItems(bool addMessage, string msg = null) { if (!requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) return true; if (item.OwnInventory == null) return false; @@ -564,7 +565,11 @@ namespace Barotrauma.Items.Components if (!item.OwnInventory.Items.Any(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it))) { #if CLIENT - if (addMessage && !string.IsNullOrEmpty(ri.Msg)) GUI.AddMessage(ri.Msg, Color.Red); + msg = msg ?? ri.Msg; + if (addMessage && !string.IsNullOrEmpty(msg)) + { + GUI.AddMessage(msg, Color.Red); + } #endif return false; } @@ -573,39 +578,67 @@ namespace Barotrauma.Items.Components return true; } - public virtual bool HasRequiredItems(Character character, bool addMessage) + public virtual bool HasRequiredItems(Character character, bool addMessage, string msg = null) { if (!requiredItems.Any()) return true; if (character.Inventory == null) return false; - + bool hasRequiredItems = false; + bool canContinue = true; if (requiredItems.ContainsKey(RelatedItem.RelationType.Equipped)) { foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Equipped]) { - if (character.SelectedItems.FirstOrDefault(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it)) == null) - { -#if CLIENT - if (addMessage && !string.IsNullOrEmpty(ri.Msg)) GUI.AddMessage(ri.Msg, Color.Red); -#endif - return false; - } + canContinue = CheckItems(ri, character.SelectedItems); + if (!canContinue) { break; } } } - if (requiredItems.ContainsKey(RelatedItem.RelationType.Picked)) + if (canContinue) { - foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Picked]) + if (requiredItems.ContainsKey(RelatedItem.RelationType.Picked)) { - if (character.Inventory.Items.FirstOrDefault(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it)) == null) + foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Picked]) { -#if CLIENT - if (addMessage && !string.IsNullOrEmpty(ri.Msg)) GUI.AddMessage(ri.Msg, Color.Red); -#endif - return false; + if (!CheckItems(ri, character.Inventory.Items)) { break; } } } } - - return true; + +#if CLIENT + if (!hasRequiredItems && addMessage && !string.IsNullOrEmpty(msg)) + { + GUI.AddMessage(msg, Color.Red); + } +#endif + return hasRequiredItems; + + bool CheckItems(RelatedItem relatedItem, IEnumerable itemList) + { + bool Predicate(Item it) => it != null && it.Condition > 0.0f && relatedItem.MatchesItem(it); + bool shouldBreak = false; + if (relatedItem.IsOptional) + { + if (!hasRequiredItems) + { + hasRequiredItems = itemList.Any(Predicate); + } + } + else + { + hasRequiredItems = itemList.Any(Predicate); + if (!hasRequiredItems) + { + shouldBreak = true; + } + } + if (!hasRequiredItems) + { + if (msg == null && !string.IsNullOrEmpty(relatedItem.Msg)) + { + msg = relatedItem.Msg; + } + } + return !shouldBreak; + } } public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Character user = null) @@ -750,6 +783,7 @@ namespace Barotrauma.Items.Components { newRequiredItem.statusEffects = prevRequiredItem.statusEffects; newRequiredItem.Msg = prevRequiredItem.Msg; + newRequiredItem.IsOptional = prevRequiredItem.IsOptional; } if (!requiredItems.ContainsKey(newRequiredItem.Type)) @@ -767,10 +801,7 @@ namespace Barotrauma.Items.Components string msg = TextManager.Get(Msg, true); if (msg != null) { - foreach (InputType inputType in Enum.GetValues(typeof(InputType))) - { - msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString()); - } + msg = TextManager.ParseInputTypes(msg); DisplayMsg = msg; } else diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs index 9aab03135..69a209f97 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs @@ -205,6 +205,7 @@ namespace Barotrauma.Items.Components cam.TargetPos = focusTarget.WorldPosition; cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, (focusTarget as Item).Prefab.OffsetOnSelected, deltaTime * 10.0f); + HideHUDs(true); } #endif @@ -273,6 +274,10 @@ namespace Barotrauma.Items.Components if (character.SelectedConstruction == this.item) character.SelectedConstruction = null; character.AnimController.Anim = AnimController.Animation.None; + if (character == Character.Controlled) + { + HideHUDs(false); + } } public override bool Select(Character activator) @@ -338,5 +343,7 @@ namespace Barotrauma.Items.Components limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos); } } + + partial void HideHUDs(bool value); } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs index 6fe79715c..fb4392e3c 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs @@ -13,6 +13,11 @@ namespace Barotrauma.Items.Components private ItemContainer inputContainer, outputContainer; + public ItemContainer InputContainer + { + get { return inputContainer; } + } + public ItemContainer OutputContainer { get { return outputContainer; } @@ -71,8 +76,10 @@ namespace Barotrauma.Items.Components var targetItem = inputContainer.Inventory.Items.LastOrDefault(i => i != null); if (targetItem == null) { return; } - progressState = Math.Min(progressTimer / targetItem.Prefab.DeconstructTime, 1.0f); - if (progressTimer > targetItem.Prefab.DeconstructTime) + float deconstructTime = targetItem.Prefab.DeconstructItems.Any() ? targetItem.Prefab.DeconstructTime : 1.0f; + + progressState = Math.Min(progressTimer / deconstructTime, 1.0f); + if (progressTimer > deconstructTime) { foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems) { @@ -100,10 +107,24 @@ namespace Barotrauma.Items.Components } } - inputContainer.Inventory.RemoveItem(targetItem); - Entity.Spawner.AddToRemoveQueue(targetItem); - MoveInputQueue(); - PutItemsToLinkedContainer(); + if (targetItem.Prefab.DeconstructItems.Any()) + { + inputContainer.Inventory.RemoveItem(targetItem); + Entity.Spawner.AddToRemoveQueue(targetItem); + MoveInputQueue(); + PutItemsToLinkedContainer(); + } + else + { + if (outputContainer.Inventory.Items.All(i => i != null)) + { + targetItem.Drop(dropper: null); + } + else + { + outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true); + } + } if (inputContainer.Inventory.Items.Any(i => i != null)) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs index 7ade916d4..cd1b0e081 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs @@ -23,6 +23,16 @@ namespace Barotrauma.Items.Components private ItemContainer inputContainer, outputContainer; + public ItemContainer InputContainer + { + get { return inputContainer; } + } + + public ItemContainer OutputContainer + { + get { return outputContainer; } + } + private float progressState; public Fabricator(Item item, XElement element) @@ -98,7 +108,23 @@ namespace Barotrauma.Items.Components { return (picker != null); } - + + public void RemoveFabricationRecipes(List allowedIdentifiers) + { + for (int i = 0; i < fabricationRecipes.Count; i++) + { + if (!allowedIdentifiers.Contains(fabricationRecipes[i].TargetItem.Identifier)) + { + fabricationRecipes.RemoveAt(i); + i--; + } + } + + CreateRecipes(); + } + + partial void CreateRecipes(); + private void StartFabricating(FabricationRecipe selectedItem, Character user) { if (selectedItem == null) return; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs index a26222b4b..ee38171f6 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs @@ -473,6 +473,8 @@ namespace Barotrauma.Items.Components { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; } + IsActive = true; + float degreeOfSuccess = DegreeOfSuccess(character); //characters with insufficient skill levels don't refuel the reactor diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs index 40e97dfe5..144d8da1a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs @@ -259,7 +259,7 @@ namespace Barotrauma.Items.Components int clockDir = (int)Math.Round((angle / MathHelper.TwoPi) * 12); if (clockDir == 0) clockDir = 12; - return TextManager.Get("SubDirOClock").Replace("[dir]", clockDir.ToString()); + return TextManager.Get("roomname.subdiroclock").Replace("[dir]", clockDir.ToString()); } private Vector2 GetTransducerCenter() diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs index 157e5f8c8..097e26985 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs @@ -201,7 +201,7 @@ namespace Barotrauma.Items.Components if (sparkSounds.Count > 0) { var sparkSound = sparkSounds[Rand.Int(sparkSounds.Count)]; - SoundPlayer.PlaySound(sparkSound.Sound, sparkSound.Volume, sparkSound.Range, pt.item.WorldPosition, pt.item.CurrentHull); + SoundPlayer.PlaySound(sparkSound.Sound, pt.item.WorldPosition, sparkSound.Volume, sparkSound.Range, pt.item.CurrentHull); } Vector2 baseVel = Rand.Vector(300.0f); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs index 00ec9ae00..7f3ee64e6 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs @@ -103,7 +103,7 @@ namespace Barotrauma.Items.Components ApplyStatusEffects(ActionType.OnActive, deltaTime, null); if (!powerOnSoundPlayed && powerOnSound != null) { - SoundPlayer.PlaySound(powerOnSound.Sound, powerOnSound.Volume, powerOnSound.Range, item.WorldPosition, item.CurrentHull); + SoundPlayer.PlaySound(powerOnSound.Sound, item.WorldPosition, powerOnSound.Volume, powerOnSound.Range, item.CurrentHull); powerOnSoundPlayed = true; } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs index 1fd9ca9e6..48b98b710 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs @@ -8,11 +8,19 @@ namespace Barotrauma.Items.Components { partial class CustomInterface : ItemComponent, IClientSerializable, IServerSerializable { - class CustomInterfaceElement + class CustomInterfaceElement : ISerializableEntity { public bool ContinuousSignal; public bool State; - public string Label, Connection, Signal; + public string Connection; + [Serialize("", false, translationTextTag = "Label.")] + public string Label { get; set; } + [Serialize("1", false)] + public string Signal { get; set; } + + public string Name => "CustomInterfaceElement"; + + public Dictionary SerializableProperties { get; set; } public List StatusEffects = new List(); @@ -33,7 +41,7 @@ namespace Barotrauma.Items.Components } private string[] labels; - [Serialize("", true), Editable()] + [Serialize("", true)] public string Labels { get { return string.Join(",", labels); } @@ -48,7 +56,7 @@ namespace Barotrauma.Items.Components } } private string[] signals; - [Serialize("", true), Editable()] + [Serialize("", true)] public string Signals { //use semicolon as a separator because comma may be needed in the signals (for color or vector values for example) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs index b7d2c38c2..4b68000a9 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs @@ -133,6 +133,7 @@ namespace Barotrauma.Items.Components set { + if (base.IsActive == value) { return; } base.IsActive = value; #if CLIENT if (light == null) return; @@ -170,6 +171,7 @@ namespace Barotrauma.Items.Components UpdateOnActiveEffects(deltaTime); #if CLIENT + light.SpriteScale = Vector2.One * item.Scale; light.ParentSub = item.Submarine; if (item.Container != null) { @@ -217,7 +219,7 @@ namespace Barotrauma.Items.Components if (voltage > 0.1f && sparkSounds.Count > 0) { var sparkSound = sparkSounds[Rand.Int(sparkSounds.Count)]; - SoundPlayer.PlaySound(sparkSound.Sound, sparkSound.Volume, sparkSound.Range, item.WorldPosition, item.CurrentHull); + SoundPlayer.PlaySound(sparkSound.Sound, item.WorldPosition, sparkSound.Volume, sparkSound.Range, item.CurrentHull); } #endif lightBrightness = 0.0f; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs index eeee9efe1..1befb10a5 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Xml.Linq; using Barotrauma.Items.Components; +using Barotrauma.Extensions; namespace Barotrauma { @@ -66,6 +67,8 @@ namespace Barotrauma public LightComponent LightComponent { get; set; } + public int Variant { get; set; } + private Gender _gender; /// /// None = Any/Not Defined -> no effect. @@ -112,30 +115,65 @@ namespace Barotrauma /// /// Note: this constructor cannot initialize automatically, because the gender is unknown at this point. We only know it when the item is equipped. /// - public WearableSprite(XElement subElement, Wearable wearable) + public WearableSprite(XElement subElement, Wearable wearable, int variant = 0) { Type = WearableType.Item; WearableComponent = wearable; + Variant = Math.Max(variant, 0); SpritePath = ParseSpritePath(subElement.GetAttributeString("texture", string.Empty)); SourceElement = subElement; } private string ParseSpritePath(string texturePath) => texturePath.Contains("/") ? texturePath : $"{Path.GetDirectoryName(WearableComponent.Item.Prefab.ConfigFile)}/{texturePath}"; + public void RefreshPath() + { + if (Variant > 0) + { + // Restore the tag so that we can parse it again. + ReplaceNumbersWith("[VARIANT]"); + } + ParsePath(true); + } + + private void ReplaceNumbersWith(string replacement) + { + var fileName = Path.GetFileName(SpritePath); + var path = Path.GetDirectoryName(SpritePath); + fileName = fileName.Replace(replacement, c => char.IsNumber(c)); + SpritePath = Path.Combine(path, fileName); + } + + private void ParsePath(bool parseSpritePath) + { + if (_gender != Gender.None) + { + SpritePath = SpritePath.Replace("[GENDER]", (_gender == Gender.Female) ? "female" : "male"); + } + SpritePath = SpritePath.Replace("[VARIANT]", Variant.ToString()); + if (!File.Exists(SpritePath)) + { + // If the variant does not exist, parse the path so that it uses first variant. + Variant = 1; + ReplaceNumbersWith(Variant.ToString()); + } + if (parseSpritePath) + { + Sprite.ParseTexturePath(file: SpritePath); + } + } + public bool IsInitialized { get; private set; } public void Init(Gender gender = Gender.None) { if (IsInitialized) { return; } _gender = SpritePath.Contains("[GENDER]") ? gender : Gender.None; - if (_gender != Gender.None) - { - SpritePath = SpritePath.Replace("[GENDER]", (_gender == Gender.Female) ? "female" : "male"); - } + ParsePath(false); if (Sprite != null) { Sprite.Remove(); } - Sprite = new Sprite(SourceElement, "", SpritePath); + Sprite = new Sprite(SourceElement, file: SpritePath); Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true); HideLimb = SourceElement.GetAttributeBool("hidelimb", false); HideOtherWearables = SourceElement.GetAttributeBool("hideotherwearables", false); @@ -170,17 +208,18 @@ namespace Barotrauma.Items.Components get { return damageModifiers; } } - public Wearable (Item item, XElement element) : base(item, element) + public Wearable(Item item, XElement element) : base(item, element) { this.item = item; damageModifiers = new List(); int spriteCount = element.Elements().Count(x => x.Name.ToString() == "sprite"); + int variants = element.GetAttributeInt("variants", 0); + int variant = variants > 0 ? Rand.Range(1, variants + 1, Rand.RandSync.Server) : 1; wearableSprites = new WearableSprite[spriteCount]; limbType = new LimbType[spriteCount]; limb = new Limb[spriteCount]; - int i = 0; foreach (XElement subElement in element.Elements()) { @@ -196,7 +235,7 @@ namespace Barotrauma.Items.Components limbType[i] = (LimbType)Enum.Parse(typeof(LimbType), subElement.GetAttributeString("limb", "Head"), true); - wearableSprites[i] = new WearableSprite(subElement, this); + wearableSprites[i] = new WearableSprite(subElement, this, variant); foreach (XElement lightElement in subElement.Elements()) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs index e6e6a9541..85d352153 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -206,6 +206,16 @@ namespace Barotrauma return true; } + public bool IsFull() + { + for (int i = 0; i < capacity; i++) + { + if (Items[i] == null) return false; + } + + return true; + } + protected bool TrySwapping(int index, Item item, Character user, bool createNetworkEvent) { if (item?.ParentInventory == null || Items[index] == null) return false; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index ff324aca2..1622e76e8 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -76,6 +76,7 @@ namespace Barotrauma private List repairables; //a dictionary containing lists of the status effects in all the components of the item + private bool[] hasStatusEffectsOfType; private Dictionary> statusEffectLists; public Dictionary SerializableProperties { get; protected set; } @@ -177,7 +178,7 @@ namespace Barotrauma { get { - return (body == null) ? base.Position : ConvertUnits.ToDisplayUnits(SimPosition); + return (body == null) ? base.Position : body.Position; } } @@ -185,7 +186,7 @@ namespace Barotrauma { get { - return (body == null) ? base.SimPosition : body.SimPosition; + return (body == null) ? ConvertUnits.ToSimUnits(base.Position) : body.SimPosition; } } @@ -557,12 +558,15 @@ namespace Barotrauma } } + hasStatusEffectsOfType = new bool[Enum.GetValues(typeof(ActionType)).Length]; foreach (ItemComponent ic in components) { if (ic.statusEffectLists == null) continue; if (statusEffectLists == null) + { statusEffectLists = new Dictionary>(); + } //go through all the status effects of the component //and add them to the corresponding statuseffect list @@ -573,6 +577,7 @@ namespace Barotrauma { statusEffectList = new List(); statusEffectLists.Add(actionType, statusEffectList); + hasStatusEffectsOfType[(int)actionType] = true; } foreach (StatusEffect effect in componentEffectList) @@ -801,7 +806,12 @@ namespace Barotrauma if (findNewHull) FindHull(); } - partial void SetActiveSprite(); + public void SetActiveSprite() + { + SetActiveSpriteProjSpecific(); + } + + partial void SetActiveSpriteProjSpecific(); public override void Move(Vector2 amount) { @@ -823,16 +833,16 @@ namespace Barotrauma { return world ? new Rectangle( - WorldRect.X + trigger.X, - WorldRect.Y + trigger.Y, - (trigger.Width == 0) ? Rect.Width : trigger.Width, - (trigger.Height == 0) ? Rect.Height : trigger.Height) + (int)(WorldRect.X + trigger.X * Scale), + (int)(WorldRect.Y + trigger.Y * Scale), + (trigger.Width == 0) ? Rect.Width : (int)(trigger.Width * Scale), + (trigger.Height == 0) ? Rect.Height : (int)(trigger.Height * Scale)) : new Rectangle( - Rect.X + trigger.X, - Rect.Y + trigger.Y, - (trigger.Width == 0) ? Rect.Width : trigger.Width, - (trigger.Height == 0) ? Rect.Height : trigger.Height); + (int)(Rect.X + trigger.X * Scale), + (int)(Rect.Y + trigger.Y * Scale), + (trigger.Width == 0) ? Rect.Width : (int)(trigger.Width * Scale), + (trigger.Height == 0) ? Rect.Height : (int)(trigger.Height * Scale)); } /// @@ -918,14 +928,9 @@ namespace Barotrauma public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb limb = null, bool isNetworkEvent = false) { - if (statusEffectLists == null) return; - - if (!statusEffectLists.TryGetValue(type, out List statusEffects)) return; - - bool broken = condition <= 0.0f; - foreach (StatusEffect effect in statusEffects) + if (!hasStatusEffectsOfType[(int)type]) { return; } + foreach (StatusEffect effect in statusEffectLists[type]) { - if (broken && effect.type != ActionType.OnBroken) continue; ApplyStatusEffect(effect, type, deltaTime, character, limb, isNetworkEvent, false); } } @@ -1042,6 +1047,8 @@ namespace Barotrauma aiTarget.SoundRange -= deltaTime * 1000.0f; } + bool broken = condition <= 0.0f; + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { sendConditionUpdateTimer -= deltaTime; @@ -1076,17 +1083,16 @@ namespace Barotrauma if (!ic.IsActive) continue; - if (condition > 0.0f) + if (broken) { - ic.Update(deltaTime, cam); - -#if CLIENT - if (ic.IsActive) ic.PlaySound(ActionType.OnActive, WorldPosition); -#endif + ic.UpdateBroken(deltaTime, cam); } else { - ic.UpdateBroken(deltaTime, cam); + ic.Update(deltaTime, cam); +#if CLIENT + if (ic.IsActive) ic.PlaySound(ActionType.OnActive, WorldPosition); +#endif } } @@ -1118,7 +1124,10 @@ namespace Barotrauma container = container.Container; } } - ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime); + if (!broken) + { + ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime); + } if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; } @@ -1197,7 +1206,7 @@ namespace Barotrauma if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return true; } - if (ImpactTolerance > 0.0f && impact > ImpactTolerance) + if (ImpactTolerance > 0.0f && condition > 0.0f && impact > ImpactTolerance) { ApplyStatusEffects(ActionType.OnImpact, 1.0f); #if SERVER @@ -1432,13 +1441,13 @@ namespace Barotrauma selectHit = picker.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 (GameMain.Config.KeyBind(ic.PickKey).MouseButton == 0) pickHit = false; - if (GameMain.Config.KeyBind(ic.SelectKey).MouseButton == 0) selectHit = false; - } + //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 (GameMain.Config.KeyBind(ic.PickKey).MouseButton == 0) pickHit = false; + if (GameMain.Config.KeyBind(ic.SelectKey).MouseButton == 0) selectHit = false; + } #endif } } @@ -1608,18 +1617,22 @@ namespace Barotrauma if (remove) { Spawner?.AddToRemoveQueue(this); } } + List texts = new List(); public List GetHUDTexts(Character character) { - List texts = new List(); - + texts.Clear(); foreach (ItemComponent ic in components) { if (string.IsNullOrEmpty(ic.DisplayMsg)) continue; if (!ic.CanBePicked && !ic.CanBeSelected) continue; if (ic is Holdable holdable && !holdable.CanBeDeattached()) continue; - - Color color = Color.Red; - if (ic.HasRequiredSkills(character) && ic.HasRequiredItems(character, false)) color = Color.Orange; + + Color color = Color.Gray; + bool hasRequiredSkillsAndItems = ic.HasRequiredSkills(character) && ic.HasRequiredItems(character, false); + if (hasRequiredSkillsAndItems) + { + color = Color.Cyan; + } texts.Add(new ColoredText(ic.DisplayMsg, color, false)); } @@ -2058,6 +2071,8 @@ namespace Barotrauma public virtual void Reset() { SerializableProperties = SerializableProperty.DeserializeProperties(this, Prefab.ConfigElement); + Sprite.ReloadXML(); + SpriteDepth = Sprite.Depth; components.ForEach(c => c.Reset()); } diff --git a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs index c9b04ad1c..f79b5d338 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs @@ -138,8 +138,10 @@ namespace Barotrauma private Dictionary prices; - //an area next to the construction - //the construction can be Activated() by a Character inside the area + /// + /// Defines areas where the item can be interacted with. If RequireBodyInsideTrigger is set to true, the character + /// has to be within the trigger to interact. If it's set to false, having the cursor within the trigger is enough. + /// public List Triggers; private List fabricationRecipeElements = new List(); @@ -196,6 +198,15 @@ namespace Barotrauma private set; } + //if true and the item has trigger areas defined, characters need to be within the trigger to interact with the item + //if false, trigger areas define areas that can be used to highlight the item + [Serialize(true, false)] + public bool RequireBodyInsideTrigger + { + get; + private set; + } + //should the camera focus on the item when selected [Serialize(false, false)] @@ -449,11 +460,11 @@ namespace Barotrauma DeconstructItems = new List(); FabricationRecipes = new List(); DeconstructTime = 1.0f; - - Tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true).ToHashSet(); + + Tags = new HashSet(element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true)); if (Tags.None()) { - Tags = element.GetAttributeStringArray("Tags", new string[0], convertToLowerInvariant: true).ToHashSet(); + Tags = new HashSet(element.GetAttributeStringArray("Tags", new string[0], convertToLowerInvariant: true)); } if (element.Attribute("cargocontainername") != null) @@ -566,7 +577,7 @@ namespace Barotrauma break; #endif case "deconstruct": - DeconstructTime = subElement.GetAttributeFloat("time", 10.0f); + DeconstructTime = subElement.GetAttributeFloat("time", 1.0f); foreach (XElement deconstructItem in subElement.Elements()) { @@ -615,10 +626,25 @@ namespace Barotrauma string treatmentIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant(); - var matchingAffliction = AfflictionPrefab.List.Find(a => a.Identifier == treatmentIdentifier); - if (matchingAffliction != null) + List matchingAfflictions = AfflictionPrefab.List.FindAll(a => a.Identifier == treatmentIdentifier || a.AfflictionType == treatmentIdentifier); + if (matchingAfflictions.Count == 0) { - matchingAffliction.TreatmentSuitability.Add(identifier, subElement.GetAttributeFloat("suitability", 0.0f)); + DebugConsole.ThrowError("Error in item prefab \"" + Name + "\" - couldn't define as a treatment, no treatments with the identifier or type \"" + treatmentIdentifier + "\" were found."); + continue; + } + + float suitability = subElement.GetAttributeFloat("suitability", 0.0f); + foreach (AfflictionPrefab matchingAffliction in matchingAfflictions) + { + if (matchingAffliction.TreatmentSuitability.ContainsKey(identifier)) + { + matchingAffliction.TreatmentSuitability[identifier] = + Math.Max(matchingAffliction.TreatmentSuitability[identifier], suitability); + } + else + { + matchingAffliction.TreatmentSuitability.Add(identifier, suitability); + } } break; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/Source/Items/RelatedItem.cs index 6a7839e25..bbbfc6705 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/RelatedItem.cs @@ -16,6 +16,8 @@ namespace Barotrauma Container } + public bool IsOptional { get; set; } + private string[] identifiers; private string[] excludedIdentifiers; @@ -138,7 +140,8 @@ namespace Barotrauma { element.Add( new XAttribute("identifiers", JoinedIdentifiers), - new XAttribute("type", type.ToString())); + new XAttribute("type", type.ToString()), + new XAttribute("optional", IsOptional)); if (excludedIdentifiers.Length > 0) { @@ -218,7 +221,8 @@ namespace Barotrauma if (subElement.Name.ToString().ToLowerInvariant() != "statuseffect") continue; ri.statusEffects.Add(StatusEffect.Load(subElement, parentDebugName)); } - + + ri.IsOptional = element.GetAttributeBool("optional", false); return ri; } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/DummyFireSource.cs b/Barotrauma/BarotraumaShared/Source/Map/DummyFireSource.cs new file mode 100644 index 000000000..fe8d445ec --- /dev/null +++ b/Barotrauma/BarotraumaShared/Source/Map/DummyFireSource.cs @@ -0,0 +1,46 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Barotrauma +{ + partial class DummyFireSource : FireSource + { + private Vector2 maxSize; + + public bool Removed + { + get { return removed; } + } + + public DummyFireSource(Vector2 maxSize, Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false) : base(worldPosition, spawningHull, isNetworkMessage) + { + this.maxSize = maxSize; + } + + public override float DamageRange + { + get { return 5f; } + } + + protected override void LimitSize() + { + if (hull == null) return; + + position.X = Math.Max(hull.Rect.X, position.X); + position.Y = Math.Min(hull.Rect.Y, position.Y); + + size.X = Math.Min(maxSize.X, size.X); + size.Y = Math.Min(maxSize.Y, size.Y); + } + + protected override void AdjustXPos(float growModifier, float deltaTime) + { + + } + + protected override void ReduceOxygen(float deltaTime) + { + + } + } +} diff --git a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs index a73ac8907..40f2acd92 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs @@ -40,7 +40,7 @@ namespace Barotrauma flames = true; underwaterBubble = true; } - + public Explosion(XElement element, string parentDebugName) { attack = new Attack(element, parentDebugName + ", Explosion"); @@ -62,6 +62,16 @@ namespace Barotrauma CameraShake = element.GetAttributeFloat("camerashake", attack.Range * 0.1f); } + public void DisableParticles() + { + sparks = false; + shockwave = false; + smoke = false; + flash = false; + flames = false; + underwaterBubble = false; + } + public List> GetRecentExplosions(float maxSecondsAgo) { return prevExplosions.FindAll(e => e.Third >= Timing.TotalTime - maxSecondsAgo); diff --git a/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs b/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs index c252c399a..f3b97de1c 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/FireSource.cs @@ -14,15 +14,15 @@ namespace Barotrauma { const float OxygenConsumption = 50.0f; const float GrowSpeed = 5.0f; - - private Hull hull; - private Vector2 position; - private Vector2 size; + protected Hull hull; + + protected Vector2 position; + protected Vector2 size; private Entity Submarine; - private bool removed; + protected bool removed; #if CLIENT private List burnDecals = new List(); @@ -59,7 +59,7 @@ namespace Barotrauma } } - public float DamageRange + public virtual float DamageRange { get { return (float)Math.Sqrt(size.X) * 20.0f; } } @@ -94,7 +94,7 @@ namespace Barotrauma size = new Vector2(10.0f, 10.0f); } - private void LimitSize() + protected virtual void LimitSize() { if (hull == null) return; @@ -160,9 +160,9 @@ namespace Barotrauma if (removed) { return; } } - hull.Oxygen -= size.X * deltaTime * OxygenConsumption; + ReduceOxygen(deltaTime); - position.X -= GrowSpeed * growModifier * 0.5f * deltaTime; + AdjustXPos(growModifier, deltaTime); size.X += GrowSpeed * growModifier * deltaTime; size.Y = MathHelper.Clamp(size.Y + GrowSpeed * growModifier * deltaTime, 10.0f, 50.0f); @@ -182,6 +182,16 @@ namespace Barotrauma } } + protected virtual void ReduceOxygen(float deltaTime) + { + hull.Oxygen -= size.X * deltaTime * OxygenConsumption; + } + + protected virtual void AdjustXPos(float growModifier, float deltaTime) + { + position.X -= GrowSpeed * growModifier * 0.5f * deltaTime; + } + partial void UpdateProjSpecific(float growModifier); private void OnChangeHull(Vector2 pos, Hull particleHull) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs index 5ab382995..23fc81dce 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -63,12 +63,24 @@ namespace Barotrauma return "Hull"; } } - - [Editable, Serialize("", true)] - public string RoomName + + public string DisplayName { get; - set; + private set; + } + + private string roomName; + [Editable, Serialize("", true, translationTextTag: "RoomName.")] + public string RoomName + { + get { return roomName; } + set + { + if (roomName == value) { return; } + roomName = value; + DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName; + } } public override Rectangle Rect @@ -405,11 +417,6 @@ namespace Barotrauma public void AddFireSource(FireSource fireSource) { FireSources.Add(fireSource); - - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && !IdFreed) - { - GameMain.NetworkMember.CreateEntityEvent(this); - } } public override void Update(float deltaTime, Camera cam) @@ -577,11 +584,6 @@ namespace Barotrauma public void RemoveFire(FireSource fire) { FireSources.Remove(fire); - - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && !Removed && !IdFreed) - { - GameMain.NetworkMember.CreateEntityEvent(this); - } } public IEnumerable GetConnectedHulls(int? searchDepth) @@ -798,17 +800,17 @@ namespace Barotrauma } if (roomItems.Contains("reactor")) - return TextManager.Get("ReactorRoom"); + return "RoomName.ReactorRoom"; else if (roomItems.Contains("engine")) - return TextManager.Get("EngineRoom"); + return "RoomName.EngineRoom"; else if (roomItems.Contains("steering") && roomItems.Contains("sonar")) - return TextManager.Get("CommandRoom"); + return "RoomName.CommandRoom"; else if (roomItems.Contains("ballast")) - return TextManager.Get("Ballast"); + return "RoomName.Ballast"; if (ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor != null)) { - return TextManager.Get("Airlock"); + return "RoomName.Airlock"; } Rectangle subRect = Submarine.CalculateDimensions(); @@ -828,7 +830,7 @@ namespace Barotrauma else roomPos |= Alignment.Right; - return TextManager.Get("Sub" + roomPos.ToString()); + return "RoomName.Sub" + roomPos.ToString(); } public static Hull Load(XElement element, Submarine submarine) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index 5bace8352..695b56df0 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -138,6 +138,9 @@ namespace Barotrauma public Submarine StartOutpost { get; private set; } public Submarine EndOutpost { get; private set; } + private Submarine preSelectedStartOutpost; + private Submarine preSelectedEndOutpost; + public string Seed { get { return seed; } @@ -209,7 +212,7 @@ namespace Barotrauma /// /// A scalar between 0-100 /// A scalar between 0-1 (0 = the minimum width defined in the generation params is used, 1 = the max width is used) - public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome) + public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome, Submarine startOutpost = null, Submarine endOutPost = null) : base(null) { @@ -225,6 +228,9 @@ namespace Barotrauma (width / GridCellSize) * GridCellSize, (generationParams.Height / GridCellSize) * GridCellSize); + preSelectedStartOutpost = startOutpost; + preSelectedEndOutpost = endOutPost; + //remove from entity dictionary base.Remove(); } @@ -1510,14 +1516,24 @@ namespace Barotrauma continue; } - //only create a starting outpost in campaign mode - if (GameMain.GameSession?.GameMode as CampaignMode == null && ((i == 0) == !Mirrored)) + //only create a starting outpost in campaign and tutorial modes + if (!IsModeStartOutpostCompatible() && ((i == 0) == !Mirrored)) { continue; } - - string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server); - var outpost = new Submarine(outpostFile, tryLoad: false); + + Submarine outpost = null; + + if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null) + { + string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server); + outpost = new Submarine(outpostFile, tryLoad: false); + } + else + { + outpost = (i == 0) ? preSelectedStartOutpost : preSelectedEndOutpost; + } + outpost.Load(unloadPrevious: false); outpost.MakeOutpost(); @@ -1569,6 +1585,15 @@ namespace Barotrauma } } + private bool IsModeStartOutpostCompatible() + { +#if CLIENT + return GameMain.GameSession?.GameMode as CampaignMode != null || GameMain.GameSession?.GameMode as TutorialMode != null; +#else + return GameMain.GameSession?.GameMode as CampaignMode != null; +#endif + } + public override void Remove() { base.Remove(); diff --git a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs index 569b5ccff..a239508b9 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs @@ -30,13 +30,15 @@ namespace Barotrauma //the position and dimensions of the entity protected Rectangle rect; - + + public bool ExternalHighlight = false; + //is the mouse inside the rect - protected bool isHighlighted; + private bool isHighlighted; public bool IsHighlighted { - get { return isHighlighted; } + get { return isHighlighted || ExternalHighlight; } set { isHighlighted = value; } } @@ -520,15 +522,9 @@ namespace Barotrauma } } } - - // The value should always be copied from the prefab. Editing is enabled only for testing the scale in the sub editor (changes are not saved). - -#if DEBUG + [Serialize(1f, false), Editable(0.1f, 10f, DecimalCount = 3, ValueStep = 0.1f)] -#else - [Serialize(1f, false)] -#endif - public float Scale { get; set; } = 1; + public virtual float Scale { get; set; } = 1; #endregion } } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs index 4ae545ab1..022ce6b33 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs @@ -144,6 +144,46 @@ namespace Barotrauma get { return spriteColor; } set { spriteColor = value; } } + + [Editable, Serialize(false, true)] + public bool UseDropShadow + { + get; + private set; + } + + [Serialize("0,0", true), Editable(ToolTip = "The position of the drop shadow relative to the structure. If set to zero, the shadow is positioned automatically so that it points towards the sub's center of mass.")] + public Vector2 DropShadowOffset + { + get; + private set; + } + + private float scale = 1.0f; + public override float Scale + { + get { return scale; } + set + { + if (scale == value) { return; } + scale = MathHelper.Clamp(value, 0.1f, 10.0f); + + float relativeScale = scale / prefab.Scale; + + if (!ResizeHorizontal || !ResizeVertical) + { + int newWidth = ResizeHorizontal ? rect.Width : (int)(defaultRect.Width * relativeScale); + int newHeight = ResizeVertical ? rect.Height : (int)(defaultRect.Height * relativeScale); + Rect = new Rectangle(rect.X, rect.Y, newWidth, newHeight); + if (Sections != null) + { + UpdateSections(); + } + } + } + } + + private Rectangle defaultRect; public override Rectangle Rect { @@ -155,9 +195,13 @@ namespace Barotrauma { Rectangle oldRect = Rect; base.Rect = value; - if (Prefab.Body) CreateSections(); + if (Prefab.Body) + { + CreateSections(); + } else { + if (Sections == null) { return; } foreach (WallSection sec in Sections) { Rectangle secRect = sec.rect; @@ -175,11 +219,11 @@ namespace Barotrauma public float BodyWidth { - get { return Prefab.BodyWidth > 0.0f ? Prefab.BodyWidth : rect.Width; } + get { return Prefab.BodyWidth > 0.0f ? Prefab.BodyWidth * scale : rect.Width; } } public float BodyHeight { - get { return Prefab.BodyHeight > 0.0f ? Prefab.BodyHeight : rect.Height; } + get { return Prefab.BodyHeight > 0.0f ? Prefab.BodyHeight * scale : rect.Height; } } /// @@ -247,8 +291,9 @@ namespace Barotrauma public Structure(Rectangle rectangle, StructurePrefab sp, Submarine submarine) : base(sp, submarine) { - if (rectangle.Width == 0 || rectangle.Height == 0) return; System.Diagnostics.Debug.Assert(rectangle.Width > 0 && rectangle.Height > 0); + if (rectangle.Width == 0 || rectangle.Height == 0) return; + defaultRect = rectangle; rect = rectangle; #if CLIENT @@ -299,8 +344,8 @@ namespace Barotrauma } } - // Only add ai targets automatically to walls - if (aiTarget == null && HasBody && Tags.Contains("wall")) + // Only add ai targets automatically to submarine/outpost walls + if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null) { aiTarget = new AITarget(this); } @@ -947,6 +992,7 @@ namespace Barotrauma private void UpdateSections() { + if (Bodies == null) return; foreach (Body b in Bodies) { GameMain.World.RemoveBody(b); @@ -1010,9 +1056,9 @@ namespace Barotrauma if (BodyWidth > 0.0f) rect.Width = (int)BodyWidth; if (BodyHeight > 0.0f) rect.Height = Math.Max((int)Math.Round(BodyHeight * (rect.Height / (float)this.rect.Height)), 1); } - if (FlippedX) diffFromCenter = -diffFromCenter; + if (FlippedX) { diffFromCenter = -diffFromCenter; } - Vector2 bodyOffset = ConvertUnits.ToSimUnits(Prefab.BodyOffset); + Vector2 bodyOffset = ConvertUnits.ToSimUnits(Prefab.BodyOffset) * scale; if (FlippedX) { bodyOffset.X = -bodyOffset.X; } if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; } @@ -1032,7 +1078,8 @@ namespace Barotrauma { newBody.Position = structureCenter + bodyOffset + new Vector2( (float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation), - (float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * ConvertUnits.ToSimUnits(diffFromCenter); + (float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) + * ConvertUnits.ToSimUnits(diffFromCenter); newBody.Rotation = -BodyRotation; } else @@ -1139,6 +1186,13 @@ namespace Barotrauma if (element.GetAttributeBool("flippedx", false)) s.FlipX(false); if (element.GetAttributeBool("flippedy", false)) s.FlipY(false); SerializableProperty.DeserializeProperties(s, element); + + //structures with a body drop a shadow by default + if (element.Attribute("usedropshadow") == null) + { + s.UseDropShadow = prefab.Body; + } + return s; } @@ -1166,6 +1220,9 @@ namespace Barotrauma { XElement element = new XElement("Structure"); + int width = ResizeHorizontal ? rect.Width : defaultRect.Width; + int height = ResizeVertical ? rect.Height : defaultRect.Height; + element.Add( new XAttribute("name", prefab.Name), new XAttribute("identifier", prefab.Identifier), @@ -1173,7 +1230,7 @@ namespace Barotrauma new XAttribute("rect", (int)(rect.X - Submarine.HiddenSubPosition.X) + "," + (int)(rect.Y - Submarine.HiddenSubPosition.Y) + "," + - rect.Width + "," + rect.Height)); + width + "," + height)); if (FlippedX) element.Add(new XAttribute("flippedx", true)); if (FlippedY) element.Add(new XAttribute("flippedy", true)); diff --git a/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs b/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs index 06d9576f9..eb101d98b 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/StructurePrefab.cs @@ -194,7 +194,14 @@ namespace Barotrauma break; case "backgroundsprite": sp.BackgroundSprite = new Sprite(subElement, lazyLoad: true); - + if (subElement.Attribute("sourcerect") == null && sp.sprite != null) + { + sp.BackgroundSprite.SourceRect = sp.sprite.SourceRect; + sp.BackgroundSprite.size = sp.sprite.size; + sp.BackgroundSprite.size.X *= sp.sprite.SourceRect.Width; + sp.BackgroundSprite.size.Y *= sp.sprite.SourceRect.Height; + sp.BackgroundSprite.RelativeOrigin = subElement.GetAttributeVector2("origin", new Vector2(0.5f, 0.5f)); + } if (subElement.GetAttributeBool("fliphorizontal", false)) sp.BackgroundSprite.effects = SpriteEffects.FlipHorizontally; if (subElement.GetAttributeBool("flipvertical", false)) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index ee3c527fc..b686f5dac 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -417,7 +417,10 @@ namespace Barotrauma if (me.Submarine != this) { continue; } if (me is Item item) { - item.Indestructible = true; + if (item.GetComponent() != null) + { + item.Indestructible = true; + } foreach (ItemComponent ic in item.Components) { if (ic is ConnectionPanel connectionPanel) @@ -428,6 +431,12 @@ namespace Barotrauma else if (ic is Pickable pickable) { //prevent picking up (or deattaching) items +#if CLIENT + if (GameMain.GameSession.GameMode is TutorialMode) + { + continue; + } +#endif pickable.CanBePicked = false; pickable.CanBeSelected = false; } @@ -1114,6 +1123,7 @@ namespace Barotrauma } } savedSubmarines.Add(new Submarine(filePath)); + savedSubmarines = savedSubmarines.OrderBy(s => s.filePath ?? "").ToList(); } public static void RefreshSavedSubs() @@ -1422,7 +1432,7 @@ namespace Barotrauma doc.Root.Add(new XAttribute("md5hash", hash.Hash)); if (previewImage != null) { - doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray()))); + //doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray()))); } try diff --git a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs index f932497b4..8b69e2434 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/WayPoint.cs @@ -299,7 +299,7 @@ namespace Barotrauma { for (int dir = -1; dir <= 1; dir += 2) { - WayPoint closest = stairPoints[i].FindClosest(dir, true, new Vector2(-30.0f,30f)); + WayPoint closest = stairPoints[i].FindClosest(dir, true, new Vector2(-30.0f, 30f)); if (closest == null) continue; stairPoints[i].ConnectTo(closest); } @@ -412,7 +412,7 @@ namespace Barotrauma WayPoint closest = wayPoint.FindClosest( dir, true, new Vector2(-tolerance, tolerance), - gap.ConnectedDoor == null ? null : gap.ConnectedDoor.Body.FarseerBody); + gap.ConnectedDoor?.Body.FarseerBody); if (closest != null) { @@ -423,13 +423,26 @@ namespace Barotrauma foreach (Gap gap in Gap.GapList) { - if (gap.IsHorizontal || gap.IsRoomToRoom) continue; + if (gap.IsHorizontal || gap.IsRoomToRoom || !gap.linkedTo.Any(l => l is Hull)) { continue; } //too small to walk through - if (gap.Rect.Width < 100.0f) continue; + if (gap.Rect.Width < 100.0f) { continue; } var wayPoint = new WayPoint( new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height / 2), SpawnType.Path, submarine, gap); + + float tolerance = outSideWaypointInterval / 2.0f; + Hull connectedHull = (Hull)gap.linkedTo.First(l => l is Hull); + int dir = Math.Sign(connectedHull.Position.Y - gap.Position.Y); + + WayPoint closest = wayPoint.FindClosest( + dir, false, new Vector2(-tolerance, tolerance), + gap.ConnectedDoor?.Body.FarseerBody); + + if (closest != null) + { + wayPoint.ConnectTo(closest); + } } var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && !w.linkedTo.Any()); diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs index 40096c389..e1be53f6c 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs @@ -25,6 +25,8 @@ namespace Barotrauma.Networking //(the index of an itemcomponent for example) protected object[] Data; + public bool Sent; + protected NetEntityEvent(INetSerializable entity, UInt16 id) { this.ID = id; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/OrderChatMessage.cs b/Barotrauma/BarotraumaShared/Source/Networking/OrderChatMessage.cs index 0f8f53bd7..85523d2fa 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/OrderChatMessage.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/OrderChatMessage.cs @@ -20,7 +20,7 @@ namespace Barotrauma.Networking public OrderChatMessage(Order order, string orderOption, Entity targetEntity, Character targetCharacter, Character sender) : this(order, orderOption, - order.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.RoomName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption), + order.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption), targetEntity, targetCharacter, sender) { } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs index 3a4f36a23..0a5ca7bb0 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs @@ -377,6 +377,12 @@ namespace Barotrauma.Networking private set; } + [Serialize(true, true)] + public bool VoipEnabled { + get; + private set; + } + [Serialize(true, true)] public bool EndRoundAtLevelEnd { diff --git a/Barotrauma/BarotraumaShared/Source/Networking/SteamManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/SteamManager.cs index 3928675b6..19d821993 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/SteamManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/SteamManager.cs @@ -66,6 +66,16 @@ namespace Barotrauma.Steam if (!USE_STEAM) return; instance = new SteamManager(); } + + public static void OverlayCustomURL(string url) + { + if (instance == null || !instance.isInitialized || instance.client == null) + { + return; + } + + instance.client.Overlay.OpenUrl(url); + } public static bool UnlockAchievement(string achievementName) { diff --git a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs index 4113750c9..17d762f18 100644 --- a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs @@ -732,7 +732,7 @@ namespace Barotrauma } } - SetTransform((Vector2)targetPosition, targetRotation == null ? body.Rotation : (float)targetRotation); + SetTransformIgnoreContacts((Vector2)targetPosition, targetRotation == null ? body.Rotation : (float)targetRotation); targetPosition = null; targetRotation = null; } diff --git a/Barotrauma/BarotraumaShared/Source/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/Source/Screens/GameScreen.cs index 72bec4a5e..e7a5815ad 100644 --- a/Barotrauma/BarotraumaShared/Source/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/Source/Screens/GameScreen.cs @@ -44,6 +44,7 @@ namespace Barotrauma base.Deselect(); #if CLIENT + GameMain.Config.SaveNewPlayerConfig(); GameMain.SoundManager.SetCategoryMuffle("default", false); GUI.ClearMessages(); #endif diff --git a/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs index b2dbc8a56..1ae108e35 100644 --- a/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/Source/Serialization/SerializableProperty.cs @@ -55,11 +55,20 @@ namespace Barotrauma { public object defaultValue; public bool isSaveable; + public string translationTextTag; - public Serialize(object defaultValue, bool isSaveable) + /// + /// Makes the property serializable to/from XML + /// + /// The property is set to this value during deserialization if the value is not defined in XML. + /// Is the value saved to XML when serializing. + /// If set to anything else than null, SerializableEntityEditors will show what the text gets translated to or warn if the text is not found in the language files. + /// Setting the value to a non-empty string will let the user select the text from one whose tag starts with the given string (e.g. RoomName. would show all texts with a RoomName.* tag) + public Serialize(object defaultValue, bool isSaveable, string translationTextTag = null) { this.defaultValue = defaultValue; this.isSaveable = isSaveable; + this.translationTextTag = translationTextTag; } } diff --git a/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs index 8f9090ae9..10b3691c6 100644 --- a/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs @@ -46,7 +46,15 @@ namespace Barotrauma if (File.Exists(filePath)) { - doc = XDocument.Load(filePath, LoadOptions.SetBaseUri); + try + { + doc = XDocument.Load(filePath, LoadOptions.SetBaseUri); + } + catch + { + return null; + } + if (doc.Root == null) return null; } diff --git a/Barotrauma/BarotraumaShared/Source/Sprite/Sprite.cs b/Barotrauma/BarotraumaShared/Source/Sprite/Sprite.cs index 7862e4e82..064b5d1e2 100644 --- a/Barotrauma/BarotraumaShared/Source/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaShared/Source/Sprite/Sprite.cs @@ -87,7 +87,6 @@ namespace Barotrauma public string FullPath { get; private set; } - public override string ToString() { return FilePath + ": " + sourceRect; @@ -107,25 +106,7 @@ namespace Barotrauma { this.lazyLoad = lazyLoad; SourceElement = element; - if (file == "") - { - file = SourceElement.GetAttributeString("texture", ""); - } - if (file == "") - { - DebugConsole.ThrowError("Sprite " + SourceElement + " doesn't have a texture specified!"); - return; - } - if (!string.IsNullOrEmpty(path)) - { - if (!path.EndsWith("/")) path += "/"; - } - FilePath = path + file; - if (!string.IsNullOrEmpty(FilePath)) - { - FullPath = Path.GetFullPath(FilePath); - } - + if (!ParseTexturePath(path, file)) { return; } Name = SourceElement.GetAttributeString("name", null); Vector4 sourceVector = SourceElement.GetAttributeVector4("sourcerect", Vector4.Zero); preMultipliedAlpha = preMultiplyAlpha ?? SourceElement.GetAttributeBool("premultiplyalpha", true); @@ -269,6 +250,29 @@ namespace Barotrauma ID = GetID(SourceElement); } } + + public bool ParseTexturePath(string path = "", string file = "") + { + if (file == "") + { + file = SourceElement.GetAttributeString("texture", ""); + } + if (file == "") + { + DebugConsole.ThrowError("Sprite " + SourceElement + " doesn't have a texture specified!"); + return false; + } + if (!string.IsNullOrEmpty(path)) + { + if (!path.EndsWith("/")) path += "/"; + } + FilePath = path + file; + if (!string.IsNullOrEmpty(FilePath)) + { + FullPath = Path.GetFullPath(FilePath); + } + return true; + } } } diff --git a/Barotrauma/BarotraumaShared/Source/SteamAchievementManager.cs b/Barotrauma/BarotraumaShared/Source/SteamAchievementManager.cs index d707be008..9dfe58a9e 100644 --- a/Barotrauma/BarotraumaShared/Source/SteamAchievementManager.cs +++ b/Barotrauma/BarotraumaShared/Source/SteamAchievementManager.cs @@ -336,9 +336,12 @@ namespace Barotrauma UnlockAchievement("survivereactormeltdown"); } #endif - - var charactersInSub = Character.CharacterList.FindAll(c => !c.IsDead && + var charactersInSub = Character.CharacterList.FindAll(c => + !c.IsDead && + c.TeamID != Character.TeamType.FriendlyNPC && + !(c.AIController is EnemyAIController) && (c.Submarine == gameSession.Submarine || (Level.Loaded?.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost))); + if (charactersInSub.Count == 1) { //there must be some non-enemy casualties to get the last mant standing achievement @@ -346,7 +349,11 @@ namespace Barotrauma { UnlockAchievement(charactersInSub[0], "lastmanstanding"); } - else if (!Character.CharacterList.Any(c => !(c.AIController is EnemyAIController))) + //lone sailor achievement if alone in the sub and there are no other characters with the same team ID + else if (!Character.CharacterList.Any(c => + c != charactersInSub[0] && + c.TeamID == charactersInSub[0].TeamID && + !(c.AIController is EnemyAIController))) { UnlockAchievement(charactersInSub[0], "lonesailor"); } diff --git a/Barotrauma/BarotraumaShared/Source/TextManager.cs b/Barotrauma/BarotraumaShared/Source/TextManager.cs index 1d5d074c8..ff727c72b 100644 --- a/Barotrauma/BarotraumaShared/Source/TextManager.cs +++ b/Barotrauma/BarotraumaShared/Source/TextManager.cs @@ -118,6 +118,16 @@ namespace Barotrauma } } + public static string ParseInputTypes(string text) + { + foreach (InputType inputType in Enum.GetValues(typeof(InputType))) + { + text = text.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString()); + text = text.Replace("[InputType." + inputType.ToString() + "]", GameMain.Config.KeyBind(inputType).ToString()); + } + return text; + } + public static string GetFormatted(string textTag, bool returnNull = false, params object[] args) { string text = Get(textTag, returnNull); @@ -252,6 +262,28 @@ namespace Barotrauma return null; } + public static List> GetAllTagTextPairs() + { + if (!textPacks.ContainsKey(Language)) + { + DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); + Language = "English"; + if (!textPacks.ContainsKey(Language)) + { + throw new Exception("No text packs available in English!"); + } + } + + List> allText = new List>(); + + foreach (TextPack textPack in textPacks[Language]) + { + allText.AddRange(textPack.GetAllTagTextPairs()); + } + + return allText; + } + public static string ReplaceGenderPronouns(string text, Gender gender) { if (gender == Gender.Male) diff --git a/Barotrauma/BarotraumaShared/Source/TextPack.cs b/Barotrauma/BarotraumaShared/Source/TextPack.cs index 91366d043..f1c2c3a76 100644 --- a/Barotrauma/BarotraumaShared/Source/TextPack.cs +++ b/Barotrauma/BarotraumaShared/Source/TextPack.cs @@ -37,6 +37,7 @@ namespace Barotrauma text = text.Replace("&", "&"); text = text.Replace("<", "<"); text = text.Replace(">", ">"); + text = text.Replace(""", "\""); infoList.Add(text); } } @@ -62,6 +63,20 @@ namespace Barotrauma return textList; } + public List> GetAllTagTextPairs() + { + var pairs = new List>(); + foreach (KeyValuePair> kvp in texts) + { + foreach (string line in kvp.Value) + { + pairs.Add(new KeyValuePair(kvp.Key, line)); + } + } + + return pairs; + } + #if DEBUG public void CheckForDuplicates(int index) { diff --git a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub index 94e47fc22..3d7eb5493 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub and b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Berilia_ManualDoorTest.sub b/Barotrauma/BarotraumaShared/Submarines/Berilia_ManualDoorTest.sub new file mode 100644 index 000000000..75f9fa371 Binary files /dev/null and b/Barotrauma/BarotraumaShared/Submarines/Berilia_ManualDoorTest.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub b/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub index ee8d2d85a..6ba4eb52d 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub and b/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub index 511746b0c..94becc0e0 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub and b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Dugong_Tutorial.sub b/Barotrauma/BarotraumaShared/Submarines/Dugong_Tutorial.sub new file mode 100644 index 000000000..e52f554ea Binary files /dev/null and b/Barotrauma/BarotraumaShared/Submarines/Dugong_Tutorial.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 8b43c675f..373ae025e 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca.sub b/Barotrauma/BarotraumaShared/Submarines/Orca.sub index c20881e5c..e0e6c5f3c 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index 2383ef1a9..0e552098c 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Remora.sub and b/Barotrauma/BarotraumaShared/Submarines/Remora.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub index 16b9dd843..f546031bf 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub and b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Selkie.sub b/Barotrauma/BarotraumaShared/Submarines/Selkie.sub index 23c9548e5..72e8f6ace 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Selkie.sub and b/Barotrauma/BarotraumaShared/Submarines/Selkie.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/TutorialOutpost.sub b/Barotrauma/BarotraumaShared/Submarines/TutorialOutpost.sub new file mode 100644 index 000000000..3e5085c2e Binary files /dev/null and b/Barotrauma/BarotraumaShared/Submarines/TutorialOutpost.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub index 1113c5152..094ad050d 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Venture.sub b/Barotrauma/BarotraumaShared/Submarines/Venture.sub index d72b72043..e6df2ee70 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Venture.sub and b/Barotrauma/BarotraumaShared/Submarines/Venture.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index a8830200b..beb985789 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,62 @@ +--------------------------------------------------------------------------------------------------------- +v0.8.10.0 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- Completely remade tutorials (separate tutorial for each job). +- Added a door and hatch variants with integrated buttons. +- New outpost graphics. +- Added swarm behavior to crawlers. +- Added a new mission where you have to kill a swarm of crawlers. +- Numerous crew AI improvements. +- Balanced item deterioration values. +- Mineral sprites change when collected from the environment. +- Added an option to disable directional voice chat. +- Added automatic submarine repair option to the campaign. +- Added "all" tab to the entity list in the submarine editor. +- Hide the crew area, chat box and server buttons when operating a turret or searchlight. +- Doors can be repaired with a wrench. +- Some new lamp variants. +- Display a progress bar when welding doors shut. +- Items that don't give any materials when deconstructed cannot be deconstructed. +- Added a console command that resets selected items and structures to prefab values ("resetselected"). +- Option to toggle structure drop shadows and edit the position of the shadow in the sub editor. +- Minor physics optimizations. +- Disable background music & ambience in the character editor. +- More pronounced limping animation when a character's legs are injured. +- The inventory slots next to the character portrait (ID card, uniform, etc) can be hidden. +- Some new sound effects and background music. +- All walls can be scaled in the submarine editor. +- Structure damage is visualized when using debugdraw. +- Improved font scaling on different resolutions. +- Added Steam overlay support to Workshop. +- Server list shows which servers have voice chat enabled. +- Show a message box notifying respawning traitors that they're no longer a traitor. +- Added a search bar to the store menu. +- Added search bars to sub lists in campaign setup UI and sub editor. + +Bugfixes: +- Don't allow rewiring and deattaching an item at the same time (happened when interacting with an item +while holding both a screwdriver and a wrench). +- Fixed bots being unable to complete almost any task in the multiplayer due to a bug that caused +them to interpret the sub as another crew's submarine. +- When spawning multiple monsters at the same time, spread them around a bit to prevent the players +from getting attacked by a ball of overlapping crawlers. +- Fixed huge lag spikes when a character tries to escape from an enemy but can't find a path away from it. +- Fixed file transfer progress bars not being visible in the server lobby. +- Fixed crashing when attempting to start a mission round with mission type set to None. +- Fixed ElectricalDischarger electricity effect staying visible if the item breaks or the component +is deactivated from outside (e.g. via a StatusEffect or the parent component). +- Fixed specular maps being rendered on top of characters when outside the sub. +- Fixed excessively bright lights around sonar flora and lava vents. +- Fixes to item collider sizes. +- Fixed inability to scroll through long texts in the sub editor's textboxes. +- Fixed clients not being able to see other characters in spectator if they've died far away from the sub. +- Fixed non-latin characters not being displayed correctly in Workshop item texts. +- Don't prevent selecting items in the sub editor when the cursor is on a wire node, because it makes it +very difficult (or impossible) to select small items in the wiring mode. +- Fixed crashing when attempting to use the "spawnitem" command when a round is not running. + --------------------------------------------------------------------------------------------------------- v0.8.9.10 --------------------------------------------------------------------------------------------------------- diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index 2de28fc35..2b4a807ce 100644 --- a/Barotrauma/BarotraumaShared/serversettings.xml +++ b/Barotrauma/BarotraumaShared/serversettings.xml @@ -14,6 +14,7 @@ minrespawnratio="0.2" autorestartinterval="60" allowspectating="True" + voicechatenabled="True" endroundatlevelend="True" saveserverlogs="True" allowragdollbutton="True"