From 3587b4a4bb8002e31e8c16a2e833947cdc40c56c Mon Sep 17 00:00:00 2001 From: Regalis Date: Fri, 25 Sep 2015 12:42:42 +0300 Subject: [PATCH] Status icons, skill/character refactoring --- .../TigerThresher/tigerthresher.xml | 2 +- Subsurface/Content/Jobs.xml | 27 ++- Subsurface/Content/UI/statusIcons.png | Bin 0 -> 3264 bytes Subsurface/Source/Characters/Character.cs | 185 +++++++++--------- Subsurface/Source/Characters/CharacterHUD.cs | 142 ++++++++++++++ Subsurface/Source/Characters/Jobs/Job.cs | 28 +-- .../Source/Characters/Jobs/JobPrefab.cs | 65 ++++-- Subsurface/Source/Characters/Jobs/Skill.cs | 57 ++++++ .../Source/Characters/Jobs/SkillPrefab.cs | 51 +++++ Subsurface/Source/Characters/Limb.cs | 10 +- Subsurface/Source/GUI/GUI.cs | 2 +- Subsurface/Source/GUI/GUIImage.cs | 11 +- .../Items/Components/Machines/Controller.cs | 17 +- Subsurface/Source/Items/Item.cs | 4 +- Subsurface/Source/Map/Explosion.cs | 2 - Subsurface/Source/Map/Gap.cs | 4 +- .../{MainMenu.cs => MainMenuScreen.cs} | 2 +- Subsurface/Source/Screens/NetLobbyScreen.cs | 54 +++-- Subsurface/Subsurface.csproj | 8 +- Subsurface_Solution.v12.suo | Bin 719360 -> 739328 bytes 20 files changed, 483 insertions(+), 188 deletions(-) create mode 100644 Subsurface/Content/UI/statusIcons.png create mode 100644 Subsurface/Source/Characters/CharacterHUD.cs create mode 100644 Subsurface/Source/Characters/Jobs/Skill.cs create mode 100644 Subsurface/Source/Characters/Jobs/SkillPrefab.cs rename Subsurface/Source/Screens/{MainMenu.cs => MainMenuScreen.cs} (99%) diff --git a/Subsurface/Content/Characters/TigerThresher/tigerthresher.xml b/Subsurface/Content/Characters/TigerThresher/tigerthresher.xml index 6a0aae69a..03f133667 100644 --- a/Subsurface/Content/Characters/TigerThresher/tigerthresher.xml +++ b/Subsurface/Content/Characters/TigerThresher/tigerthresher.xml @@ -1,5 +1,5 @@  - + diff --git a/Subsurface/Content/Jobs.xml b/Subsurface/Content/Jobs.xml index 8316e2e55..a9dc5be55 100644 --- a/Subsurface/Content/Jobs.xml +++ b/Subsurface/Content/Jobs.xml @@ -1,6 +1,6 @@  - + @@ -11,9 +11,9 @@ - + - + @@ -22,9 +22,9 @@ - + - + @@ -33,9 +33,20 @@ - + - + + + + + + + + + + + + @@ -44,5 +55,5 @@ - + \ No newline at end of file diff --git a/Subsurface/Content/UI/statusIcons.png b/Subsurface/Content/UI/statusIcons.png new file mode 100644 index 0000000000000000000000000000000000000000..47cd340f348b75c890014cdee6df300fe0bbdf0f GIT binary patch literal 3264 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 2.0f) selectedCharacter = null; + } + + if (GetInputState(InputType.Select)) + { + if (selectedCharacter!=null) + { + selectedCharacter = null; + } + else if (closestCharacter!=null && closestCharacter.isDead && closestCharacter.isHumanoid) + { + selectedCharacter = closestCharacter; + } } } @@ -686,7 +729,11 @@ namespace Subsurface return; } - if (controlled == this) ControlLocalPlayer(cam); + if (controlled == this) + { + CharacterHUD.Update(deltaTime,this); + ControlLocalPlayer(cam); + } Control(deltaTime, cam); @@ -711,15 +758,11 @@ namespace Subsurface PressureProtection -= deltaTime*100.0f; } - - - //foreach (Limb limb in animController.limbs) - //{ - Health = health - bleeding * deltaTime; - //} - + Health = health - bleeding * deltaTime; } + + private void UpdateSightRange() { aiTarget.SightRange = 0.0f; @@ -747,6 +790,11 @@ namespace Subsurface // ConvertUnits.ToDisplayUnits(animController.targetMovement.X, animController.targetMovement.Y), Color.Green); } + public void DrawHUD(SpriteBatch spriteBatch, Camera cam) + { + CharacterHUD.Draw(spriteBatch, this, cam); + } + public void DrawFront(SpriteBatch spriteBatch) { Vector2 pos = ConvertUnits.ToDisplayUnits(AnimController.Limbs[0].SimPosition); @@ -772,67 +820,7 @@ namespace Subsurface } - private static GUIProgressBar drowningBar, healthBar; - public void DrawHud(SpriteBatch spriteBatch, Camera cam) - { - if (drowningBar == null) - { - int width = 100, height = 20; - drowningBar = new GUIProgressBar(new Rectangle(20, GameMain.GraphicsHeight / 2, width, height), Color.Blue, 1.0f); - healthBar = new GUIProgressBar(new Rectangle(20, GameMain.GraphicsHeight / 2 + 30, width, height), Color.Red, 1.0f); - } - - drowningBar.BarSize = Controlled.Oxygen / 100.0f; - if (drowningBar.BarSize < 0.95f) drowningBar.Draw(spriteBatch); - - healthBar.BarSize = health / maxHealth; - if (healthBar.BarSize < 1.0f) healthBar.Draw(spriteBatch); - - if (Controlled.Inventory != null) Controlled.Inventory.DrawOwn(spriteBatch); - - Color color = Color.Orange; - - if (closestCharacter != null && closestCharacter.isDead && closestCharacter.isHumanoid) - { - Vector2 startPos = Position + (closestCharacter.Position - Position) * 0.7f; - startPos = cam.WorldToScreen(startPos); - - Vector2 textPos = startPos; - - float stringWidth = GUI.Font.MeasureString(closestCharacter.Info.Name).X; - textPos -= new Vector2(stringWidth / 2, 20); - spriteBatch.DrawString(GUI.Font, closestCharacter.Info.Name, textPos, Color.Black); - spriteBatch.DrawString(GUI.Font, closestCharacter.Info.Name, textPos + new Vector2(1, -1), Color.Orange); - - if (selectedCharacter==closestCharacter) closestCharacter.inventory.Draw(spriteBatch); - } - else if (closestItem != null && selectedConstruction==null) - { - - Vector2 startPos = Position + (closestItem.Position - Position) * 0.7f; - startPos = cam.WorldToScreen(startPos); - - Vector2 textPos = startPos; - - float stringWidth = GUI.Font.MeasureString(closestItem.Prefab.Name).X; - textPos -= new Vector2(stringWidth / 2, 20); - spriteBatch.DrawString(GUI.Font, closestItem.Prefab.Name, textPos, Color.Black); - spriteBatch.DrawString(GUI.Font, closestItem.Prefab.Name, textPos + new Vector2(1, -1), Color.Orange); - - textPos.Y += 50.0f; - foreach (ColoredText coloredText in closestItem.GetHUDTexts(Controlled)) - { - textPos.X = startPos.X - GUI.Font.MeasureString(coloredText.Text).X / 2; - - spriteBatch.DrawString(GUI.Font, coloredText.Text, textPos, Color.Black); - spriteBatch.DrawString(GUI.Font, coloredText.Text, textPos + new Vector2(1, -1), coloredText.Color); - - textPos.Y += 25; - } - } - - } public void PlaySound(AIController.AiState state) { @@ -856,20 +844,23 @@ namespace Subsurface } } - public virtual AttackResult AddDamage(IDamageable attacker, Vector2 position, Attack attack, bool playSound = false) + public virtual AttackResult AddDamage(IDamageable attacker, Vector2 simPosition, Attack attack, bool playSound = false) { - return AddDamage(position, attack.DamageType, attack.Damage, attack.BleedingDamage, attack.Stun, playSound); + return AddDamage(simPosition, attack.DamageType, attack.Damage, attack.BleedingDamage, attack.Stun, playSound); } - public AttackResult AddDamage(Vector2 position, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound) + public AttackResult AddDamage(Vector2 simPosition, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound) { AnimController.StunTimer = Math.Max(AnimController.StunTimer, stun); + if (controlled == this) CharacterHUD.TakeDamage(); + + Limb closestLimb = null; float closestDistance = 0.0f; foreach (Limb limb in AnimController.Limbs) { - float distance = Vector2.Distance(position, limb.SimPosition); + float distance = Vector2.Distance(simPosition, limb.SimPosition); if (closestLimb == null || distance < closestDistance) { closestLimb = limb; @@ -877,12 +868,12 @@ namespace Subsurface } } - Vector2 pull = position - closestLimb.SimPosition; + Vector2 pull = simPosition - closestLimb.SimPosition; if (pull != Vector2.Zero) pull = Vector2.Normalize(pull); closestLimb.body.ApplyForce(pull*Math.Min(amount*100.0f, 100.0f)); - AttackResult attackResult = closestLimb.AddDamage(position, damageType, amount, bleedingAmount, playSound); + AttackResult attackResult = closestLimb.AddDamage(simPosition, damageType, amount, bleedingAmount, playSound); health -= attackResult.Damage; bleeding += attackResult.Bleeding; @@ -938,6 +929,8 @@ namespace Subsurface private IEnumerable DeathAnim(Camera cam) { + if (controlled != this) yield return CoroutineStatus.Success; + float dimDuration = 8.0f; float timer = 0.0f; @@ -967,9 +960,9 @@ namespace Subsurface } float lerpLightBack = 0.0f; - while (lerpLightBack<1.0f) + while (lerpLightBack < 1.0f) { - lerpLightBack = Math.Min(lerpLightBack+0.05f,1.0f); + lerpLightBack = Math.Min(lerpLightBack + 0.05f, 1.0f); GameMain.LightManager.AmbientLight = Color.Lerp(Color.DarkGray, prevAmbientLight, lerpLightBack); yield return CoroutineStatus.Running; diff --git a/Subsurface/Source/Characters/CharacterHUD.cs b/Subsurface/Source/Characters/CharacterHUD.cs new file mode 100644 index 000000000..703fc9108 --- /dev/null +++ b/Subsurface/Source/Characters/CharacterHUD.cs @@ -0,0 +1,142 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Subsurface +{ + class CharacterHUD + { + + private static Sprite statusIcons; + + private static GUIProgressBar drowningBar, healthBar; + + private static float pressureTimer; + + public static void TakeDamage() + { + healthBar.Flash(); + } + + public static void Update(float deltaTime, Character character) + { + if (drowningBar != null) + { + drowningBar.Update(deltaTime); + if (character.Oxygen < 10.0f) drowningBar.Flash(); + } + if (healthBar != null) healthBar.Update(deltaTime); + + pressureTimer += ((character.AnimController.CurrentHull == null) ? + 100.0f : character.AnimController.CurrentHull.LethalPressure)*deltaTime; + } + + public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam) + { + if (statusIcons==null) + { + statusIcons = new Sprite("Content/UI/statusIcons.png", Vector2.Zero); + } + + DrawStatusIcons(spriteBatch, character); + + if (character.Inventory != null) character.Inventory.DrawOwn(spriteBatch); + + Color color = Color.Orange; + + if (character.SelectedCharacter != null && character.SelectedCharacter.Inventory!=null) + { + character.SelectedCharacter.Inventory.Draw(spriteBatch); + + //if (Vector2.Distance(selectedCharacter.SimPosition, SimPosition) > 2.0f) selectedCharacter = null; + } + + if (character.ClosestCharacter != null && character.ClosestCharacter.IsDead) + { + Vector2 startPos = character.Position + (character.ClosestCharacter.Position - character.Position) * 0.7f; + startPos = cam.WorldToScreen(startPos); + + Vector2 textPos = startPos; + + float stringWidth = GUI.Font.MeasureString(character.ClosestCharacter.Info.Name).X; + textPos -= new Vector2(stringWidth / 2, 20); + spriteBatch.DrawString(GUI.Font, character.ClosestCharacter.Info.Name, textPos, Color.Black); + spriteBatch.DrawString(GUI.Font, character.ClosestCharacter.Info.Name, textPos + new Vector2(1, -1), Color.Orange); + } + else if (character.SelectedCharacter == null && character.ClosestItem != null && character.SelectedConstruction == null) + { + + Vector2 startPos = character.Position + (character.ClosestItem.Position - character.Position) * 0.7f; + startPos = cam.WorldToScreen(startPos); + + Vector2 textPos = startPos; + + float stringWidth = GUI.Font.MeasureString(character.ClosestItem.Prefab.Name).X; + textPos -= new Vector2(stringWidth / 2, 20); + spriteBatch.DrawString(GUI.Font, character.ClosestItem.Prefab.Name, textPos, Color.Black); + spriteBatch.DrawString(GUI.Font, character.ClosestItem.Prefab.Name, textPos + new Vector2(1, -1), Color.Orange); + + textPos.Y += 50.0f; + foreach (ColoredText coloredText in character.ClosestItem.GetHUDTexts(character)) + { + textPos.X = startPos.X - GUI.Font.MeasureString(coloredText.Text).X / 2; + + spriteBatch.DrawString(GUI.Font, coloredText.Text, textPos, Color.Black); + spriteBatch.DrawString(GUI.Font, coloredText.Text, textPos + new Vector2(1, -1), coloredText.Color); + + textPos.Y += 25; + } + } + } + + private static void DrawStatusIcons(SpriteBatch spriteBatch, Character character) + { + if (drowningBar == null) + { + int width = 100, height = 20; + + drowningBar = new GUIProgressBar(new Rectangle(30, GameMain.GraphicsHeight - 200, width, height), Color.Blue, 1.0f); + new GUIImage(new Rectangle(-27, -7, 20, 20), new Rectangle(17, 0, 20, 24), statusIcons, Alignment.TopLeft, drowningBar); + + healthBar = new GUIProgressBar(new Rectangle(30, GameMain.GraphicsHeight - 230, width, height), Color.Red, 1.0f); + new GUIImage(new Rectangle(-26, -7, 20, 20), new Rectangle(0, 0, 13, 24), statusIcons, Alignment.TopLeft, healthBar); + } + + drowningBar.BarSize = character.Oxygen / 100.0f; + if (drowningBar.BarSize < 0.99f) + { + drowningBar.Draw(spriteBatch); + } + + healthBar.BarSize = character.Health / character.MaxHealth; + if (healthBar.BarSize < 1.0f) + { + healthBar.Draw(spriteBatch); + } + + int bloodDropCount = (int)Math.Floor(character.Bleeding); + bloodDropCount = MathHelper.Clamp(bloodDropCount, 0, 5); + for (int i = 1; i < bloodDropCount; i++) + { + spriteBatch.Draw(statusIcons.Texture, new Vector2(5.0f + 20 * i, healthBar.Rect.Y - 20.0f), new Rectangle(39, 3, 15, 19), Color.White * 0.8f); + } + + float pressureFactor = (character.AnimController.CurrentHull == null) ? + 100.0f : Math.Min(character.AnimController.CurrentHull.LethalPressure,100.0f); + if (character.PressureProtection > 0.0f) pressureFactor = 0.0f; + + if (pressureFactor>0.0f) + { + float indicatorAlpha = ((float)Math.Sin(pressureTimer * 0.1f) + 1.0f) * 0.5f; + + indicatorAlpha = MathHelper.Clamp(indicatorAlpha, 0.1f, pressureFactor/100.0f); + + spriteBatch.Draw(statusIcons.Texture, new Vector2(10.0f, healthBar.Rect.Y - 60.0f), new Rectangle(0, 24, 24, 25), Color.White * indicatorAlpha); + + } + } + } +} diff --git a/Subsurface/Source/Characters/Jobs/Job.cs b/Subsurface/Source/Characters/Jobs/Job.cs index d6d5addd0..616db3808 100644 --- a/Subsurface/Source/Characters/Jobs/Job.cs +++ b/Subsurface/Source/Characters/Jobs/Job.cs @@ -7,28 +7,6 @@ using System.Xml.Linq; namespace Subsurface { - class Skill - { - string name; - int level; - - public string Name - { - get { return name; } - } - - public int Level - { - get { return level; } - } - - public Skill(string name, int level) - { - this.name = name; - this.level = level; - } - } - class Job { @@ -76,11 +54,9 @@ namespace Subsurface prefab = jobPrefab; skills = new Dictionary(); - foreach (KeyValuePair skill in prefab.Skills) + foreach (SkillPrefab skillPrefab in prefab.Skills) { - skills.Add( - skill.Key, - new Skill( skill.Key, (int)Rand.Range(skill.Value.X, skill.Value.Y, false))); + skills.Add(skillPrefab.Name, new Skill(skillPrefab)); } } diff --git a/Subsurface/Source/Characters/Jobs/JobPrefab.cs b/Subsurface/Source/Characters/Jobs/JobPrefab.cs index b5f3a58aa..af0919b85 100644 --- a/Subsurface/Source/Characters/Jobs/JobPrefab.cs +++ b/Subsurface/Source/Characters/Jobs/JobPrefab.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Xml.Linq; +using System.Linq; namespace Subsurface { @@ -30,7 +31,7 @@ namespace Subsurface public List ItemNames; public List EquipItem; - public Dictionary Skills; + public List Skills; public string Name { @@ -54,7 +55,7 @@ namespace Subsurface public JobPrefab(XElement element) { - name = element.Name.ToString(); + name = ToolBox.GetAttributeString(element, "name", "name not found"); description = ToolBox.GetAttributeString(element, "description", ""); @@ -66,7 +67,7 @@ namespace Subsurface ItemNames = new List(); EquipItem = new List(); - Skills = new Dictionary(); + Skills = new List(); foreach (XElement subElement in element.Elements()) { @@ -82,10 +83,15 @@ namespace Subsurface } break; case "skills": - LoadSkills(subElement); + foreach (XElement skillElement in subElement.Elements()) + { + Skills.Add(new SkillPrefab(skillElement)); + } break; } } + + Skills.Sort((x,y) => y.LevelRange.X.CompareTo(x.LevelRange.X)); } public static JobPrefab Random() @@ -93,28 +99,51 @@ namespace Subsurface return List[Rand.Int(List.Count)]; } - private void LoadSkills(XElement element) + public GUIFrame CreateInfoFrame() { - foreach (XElement subElement in element.Elements()) + int width = 500, height = 400; + + GUIFrame frame = new GUIFrame(new Rectangle(GameMain.GraphicsWidth / 2 - width / 2, GameMain.GraphicsHeight / 2 - height / 2, width, height), GUI.Style); + frame.Padding = new Vector4(30.0f, 30.0f, 30.0f, 30.0f); + + new GUITextBlock(new Rectangle(0,0,100,20), name, GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.LargeFont); + + var descriptionBlock = new GUITextBlock(new Rectangle(0, 40, 0, 0), description, GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, true, GUI.SmallFont); + + new GUITextBlock(new Rectangle(0, 40 + descriptionBlock.Rect.Height + 20, 100, 20), "Skills: ", GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.LargeFont); + + int y = 40 + descriptionBlock.Rect.Height + 50; + foreach (SkillPrefab skill in Skills) { - string skillName = ToolBox.GetAttributeString(subElement, "name", ""); + string skillDescription = Skill.GetLevelName((int)skill.LevelRange.X); + string skillDescription2 = Skill.GetLevelName((int)skill.LevelRange.Y); - if (string.IsNullOrEmpty(skillName) || Skills.ContainsKey(skillName)) continue; - - var levelString = ToolBox.GetAttributeString(subElement, "level", ""); - if (levelString.Contains(",")) + if (skillDescription2!= skillDescription) { - Skills.Add(skillName, ToolBox.ParseToVector2(levelString, false)); - } - else - { - float skillLevel = float.Parse(levelString, CultureInfo.InvariantCulture); - Skills.Add(skillName, new Vector2(skillLevel, skillLevel)); + skillDescription += "/"+skillDescription2; } + new GUITextBlock(new Rectangle(0, y, 100, 20), + " - " + skill.Name + ": " + skillDescription, GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.SmallFont); + y += 20; } - } + new GUITextBlock(new Rectangle(250, 40 + descriptionBlock.Rect.Height + 20, 0, 20), "Items: ", GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.LargeFont); + + y = 40 + descriptionBlock.Rect.Height + 50; + foreach (string itemName in ItemNames) + { + new GUITextBlock(new Rectangle(250, y, 100, 20), + " - " + itemName, GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.SmallFont); + + y += 20; + } + + + + + return frame; + } public static void LoadAll(List filePaths) { diff --git a/Subsurface/Source/Characters/Jobs/Skill.cs b/Subsurface/Source/Characters/Jobs/Skill.cs new file mode 100644 index 000000000..5ae509916 --- /dev/null +++ b/Subsurface/Source/Characters/Jobs/Skill.cs @@ -0,0 +1,57 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Subsurface +{ + class Skill + { + SkillPrefab prefab; + + string name; + int level; + + static string[] levelNames = new string[] { + "Untrained", "Incompetent", "Novice", + "Adequate", "Competent", "Proficient", + "Professional", "Master", "Legendary" }; + + public string Name + { + get { return name; } + } + + public int Level + { + get { return level; } + } + + public Skill(SkillPrefab prefab) + { + this.prefab = prefab; + this.name = prefab.Name; + + this.level = (int)Rand.Range(prefab.LevelRange.X, prefab.LevelRange.Y); + } + + public Skill(string name, int level) + { + this.name = name; + + this.level = level; + } + + /// + /// returns the "name" of some skill level (0-10 -> untrained, etc) + /// + public static string GetLevelName(int level) + { + level = MathHelper.Clamp(level, 0, 100); + int scaledLevel = (int)Math.Floor((level / 100.0f) * levelNames.Length); + + return levelNames[Math.Min(scaledLevel, levelNames.Length - 1)]; + } + } +} diff --git a/Subsurface/Source/Characters/Jobs/SkillPrefab.cs b/Subsurface/Source/Characters/Jobs/SkillPrefab.cs new file mode 100644 index 000000000..82737a87f --- /dev/null +++ b/Subsurface/Source/Characters/Jobs/SkillPrefab.cs @@ -0,0 +1,51 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace Subsurface +{ + class SkillPrefab + { + string name; + + string description; + + private Vector2 levelRange; + + public string Name + { + get { return name; } + } + + public string Description + { + get { return description; } + } + + public Vector2 LevelRange + { + get { return levelRange; } + } + + public SkillPrefab(XElement element) + { + name = ToolBox.GetAttributeString(element, "name", ""); + + var levelString = ToolBox.GetAttributeString(element, "level", ""); + if (levelString.Contains(",")) + { + levelRange = ToolBox.ParseToVector2(levelString, false); + } + else + { + float skillLevel = float.Parse(levelString, System.Globalization.CultureInfo.InvariantCulture); + levelRange = new Vector2(skillLevel, skillLevel); + } + } + + + } +} diff --git a/Subsurface/Source/Characters/Limb.cs b/Subsurface/Source/Characters/Limb.cs index e83497293..243105f59 100644 --- a/Subsurface/Source/Characters/Limb.cs +++ b/Subsurface/Source/Characters/Limb.cs @@ -285,7 +285,7 @@ namespace Subsurface body.ApplyLinearImpulse((deltaPos - vel * 0.5f) * body.Mass, pullPos); } - public AttackResult AddDamage(Vector2 position, DamageType damageType, float amount, float bleedingAmount, bool playSound) + public AttackResult AddDamage(Vector2 simPosition, DamageType damageType, float amount, float bleedingAmount, bool playSound) { DamageSoundType damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.LimbBlunt : DamageSoundType.LimbSlash; @@ -299,7 +299,7 @@ namespace Subsurface float mid = (armorLimits.X + armorLimits.Y) / 2.0f; - float angleDiff = MathUtils.GetShortestAngle(MathUtils.VectorToAngle(position - SimPosition), mid); + float angleDiff = MathUtils.GetShortestAngle(MathUtils.VectorToAngle(simPosition - SimPosition), mid); if (Math.Abs(angleDiff) < (armorSector.Y - armorSector.X) / 2.0f) { @@ -310,9 +310,9 @@ namespace Subsurface } } - if (playSound) + if (playSound && amount>0.0f) { - AmbientSoundManager.PlayDamageSound(damageSoundType, amount, position); + AmbientSoundManager.PlayDamageSound(damageSoundType, amount, ConvertUnits.ToDisplayUnits(simPosition)); } //Bleeding += bleedingAmount; @@ -322,7 +322,7 @@ namespace Subsurface for (int i = 0; i < bloodAmount; i++) { - Vector2 particleVel = SimPosition - position; + Vector2 particleVel = SimPosition - simPosition; if (particleVel != Vector2.Zero) particleVel = Vector2.Normalize(particleVel); GameMain.ParticleManager.CreateParticle("blood", diff --git a/Subsurface/Source/GUI/GUI.cs b/Subsurface/Source/GUI/GUI.cs index 4f6fdddef..8ae08d3d6 100644 --- a/Subsurface/Source/GUI/GUI.cs +++ b/Subsurface/Source/GUI/GUI.cs @@ -320,7 +320,7 @@ namespace Subsurface } } - if (Character.Controlled != null && cam!=null) Character.Controlled.DrawHud(spriteBatch, cam); + if (Character.Controlled != null && cam!=null) Character.Controlled.DrawHUD(spriteBatch, cam); if (GameMain.NetworkMember != null) GameMain.NetworkMember.Draw(spriteBatch); DrawMessages(spriteBatch, (float)deltaTime); diff --git a/Subsurface/Source/GUI/GUIImage.cs b/Subsurface/Source/GUI/GUIImage.cs index d5eb06d38..379595291 100644 --- a/Subsurface/Source/GUI/GUIImage.cs +++ b/Subsurface/Source/GUI/GUIImage.cs @@ -47,6 +47,11 @@ namespace Subsurface } public GUIImage(Rectangle rect, Sprite sprite, Alignment alignment, GUIComponent parent = null) + : this(rect, sprite.SourceRect, sprite, alignment, parent) + { + } + + public GUIImage(Rectangle rect, Rectangle sourceRect, Sprite sprite, Alignment alignment, GUIComponent parent = null) : base(null) { this.rect = rect; @@ -60,12 +65,12 @@ namespace Subsurface Scale = 1.0f; this.sprite = sprite; - + if (rect.Width == 0) this.rect.Width = (int)sprite.size.X; if (rect.Height == 0) this.rect.Height = (int)Math.Min(sprite.size.Y, sprite.size.Y * (this.rect.Width / sprite.size.X)); - sourceRect = sprite.SourceRect; - + this.sourceRect = sourceRect; + if (parent != null) parent.AddChild(this); } diff --git a/Subsurface/Source/Items/Components/Machines/Controller.cs b/Subsurface/Source/Items/Components/Machines/Controller.cs index 9bd231f5a..8959a38ea 100644 --- a/Subsurface/Source/Items/Components/Machines/Controller.cs +++ b/Subsurface/Source/Items/Components/Machines/Controller.cs @@ -131,13 +131,11 @@ namespace Subsurface.Items.Components public override bool Use(float deltaTime, Character activator = null) { - //character = activator; - //foreach (MapEntity e in item.linkedTo) - //{ - // Item linkedItem = e as Item; - // if (linkedItem == null) continue; - // linkedItem.Use(deltaTime, activator); - //} + if (character.SelectedConstruction != item) + { + character = null; + return false; + } item.SendSignal("1", "trigger_out"); @@ -149,6 +147,11 @@ namespace Subsurface.Items.Components public override void SecondaryUse(float deltaTime, Character character = null) { if (character == null) return; + if (character.SelectedConstruction!=item) + { + character = null; + return; + } foreach (Connection c in item.Connections) { diff --git a/Subsurface/Source/Items/Item.cs b/Subsurface/Source/Items/Item.cs index b607bced5..38029eec5 100644 --- a/Subsurface/Source/Items/Item.cs +++ b/Subsurface/Source/Items/Item.cs @@ -420,7 +420,7 @@ namespace Subsurface { foreach (Item containedItem in containedItems) { - if (containedItem == null || containedItem.condition==0.0f) continue; + if (containedItem == null) continue; if (effect.TargetNames != null && !effect.TargetNames.Contains(containedItem.Name)) continue; hasTargets = true; @@ -719,7 +719,7 @@ namespace Subsurface return; } - if (!HasInGameEditableProperties) + if (HasInGameEditableProperties) { if (editingHUD == null || editingHUD.UserData as Item != this) { diff --git a/Subsurface/Source/Map/Explosion.cs b/Subsurface/Source/Map/Explosion.cs index 6d3355c2e..6669418f0 100644 --- a/Subsurface/Source/Map/Explosion.cs +++ b/Subsurface/Source/Map/Explosion.cs @@ -60,8 +60,6 @@ namespace Subsurface Rand.Vector(Rand.Range(50f, 100.0f)), 0.0f); } - - float displayRange = ConvertUnits.ToDisplayUnits(attack.Range); light = new LightSource(displayPosition, displayRange, Color.LightYellow); diff --git a/Subsurface/Source/Map/Gap.cs b/Subsurface/Source/Map/Gap.cs index 072e6a328..c6ddf01d3 100644 --- a/Subsurface/Source/Map/Gap.cs +++ b/Subsurface/Source/Map/Gap.cs @@ -239,7 +239,6 @@ namespace Subsurface public override void Update(Camera cam, float deltaTime) { - soundVolume = soundVolume + ((flowForce.Length() < 100.0f) ? -deltaTime * 0.5f : deltaTime * 0.5f); soundVolume = MathHelper.Clamp(soundVolume, 0.0f, 1.0f); @@ -254,7 +253,6 @@ namespace Subsurface UpdateOxygen(); - if (linkedTo.Count == 1) { //gap leading from a room to outside @@ -266,7 +264,7 @@ namespace Subsurface UpdateRoomToRoom(deltaTime); } - lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, 0.1f); + lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, 0.05f); if (FlowForce.Length() > 150.0f && flowTargetHull != null && flowTargetHull.Volume < flowTargetHull.FullVolume) { diff --git a/Subsurface/Source/Screens/MainMenu.cs b/Subsurface/Source/Screens/MainMenuScreen.cs similarity index 99% rename from Subsurface/Source/Screens/MainMenu.cs rename to Subsurface/Source/Screens/MainMenuScreen.cs index 29531d7c0..9d17a98b8 100644 --- a/Subsurface/Source/Screens/MainMenu.cs +++ b/Subsurface/Source/Screens/MainMenuScreen.cs @@ -147,7 +147,7 @@ namespace Subsurface + " However, UPnP isn't supported by all routers, so you may need to setup port forwards manually" +" if players are unable to join the server (see the readme for instructions)."; - GUIButton hostButton = new GUIButton(new Rectangle(0, 0, 200, 30), "Start", Alignment.BottomRight, GUI.Style, menuTabs[(int)Tabs.HostServer]); + GUIButton hostButton = new GUIButton(new Rectangle(0, 0, 100, 30), "Start", Alignment.BottomRight, GUI.Style, menuTabs[(int)Tabs.HostServer]); hostButton.OnClicked = HostServerClicked; this.game = game; diff --git a/Subsurface/Source/Screens/NetLobbyScreen.cs b/Subsurface/Source/Screens/NetLobbyScreen.cs index b50245723..0779ac6cf 100644 --- a/Subsurface/Source/Screens/NetLobbyScreen.cs +++ b/Subsurface/Source/Screens/NetLobbyScreen.cs @@ -27,6 +27,8 @@ namespace Subsurface private GUIFrame playerFrame; + private GUIFrame jobInfoFrame; + private float camAngle; public bool IsServer; @@ -305,7 +307,7 @@ namespace Subsurface if (IsServer && GameMain.Server != null) { - var playYourself = new GUITickBox(new Rectangle(0, -20, 20, 20), "Play yourself", Alignment.TopLeft, playerFrame); + var playYourself = new GUITickBox(new Rectangle(-30, -30, 20, 20), "Play yourself", Alignment.TopLeft, playerFrame); playYourself.Selected = GameMain.Server.CharacterInfo != null; playYourself.OnSelected = TogglePlayYourself; playYourself.UserData = "playyourself"; @@ -332,21 +334,25 @@ namespace Subsurface new GUITextBlock(new Rectangle(0, 150, 200, 30), "Job preferences:", GUI.Style, playerFrame); - jobList = new GUIListBox(new Rectangle(0, 180, 180, 0), GUI.Style, playerFrame); + jobList = new GUIListBox(new Rectangle(0, 180, 250, 0), GUI.Style, playerFrame); jobList.Enabled = false; int i = 1; foreach (JobPrefab job in JobPrefab.List) { - GUITextBlock jobText = new GUITextBlock(new Rectangle(0, 0, 0, 20), i + ". " + job.Name, GUI.Style, Alignment.Left, Alignment.Right, jobList); + GUITextBlock jobText = new GUITextBlock(new Rectangle(0, 0, 0, 20), i + ". " + job.Name+" ", GUI.Style, Alignment.Left, Alignment.Right, jobList); jobText.UserData = job; - GUIButton upButton = new GUIButton(new Rectangle(0, 0, 15, 15), "u", GUI.Style, jobText); + GUIButton infoButton = new GUIButton(new Rectangle(0, 0, 15, 15), "?", GUI.Style, jobText); + infoButton.UserData = -1; + infoButton.OnClicked += ViewJobInfo; + + GUIButton upButton = new GUIButton(new Rectangle(30, 0, 15, 15), "^", GUI.Style, jobText); upButton.UserData = -1; upButton.OnClicked += ChangeJobPreference; - GUIButton downButton = new GUIButton(new Rectangle(25, 0, 15, 15), "d", GUI.Style, jobText); + GUIButton downButton = new GUIButton(new Rectangle(50, 0, 15, 15), "˅", GUI.Style, jobText); downButton.UserData = 1; downButton.OnClicked += ChangeJobPreference; } @@ -460,6 +466,8 @@ namespace Subsurface GameMain.GameScreen.Cam.MoveCamera((float)deltaTime); menu.Update((float)deltaTime); + + if (jobInfoFrame != null) jobInfoFrame.Update((float)deltaTime); //durationBar.BarScroll = Math.Max(durationBar.BarScroll, 1.0f / 60.0f); } @@ -474,6 +482,8 @@ namespace Subsurface menu.Draw(spriteBatch); + if (jobInfoFrame != null) jobInfoFrame.Draw(spriteBatch); + //if (previewPlayer!=null) previewPlayer.Draw(spriteBatch); GUI.Draw((float)deltaTime, spriteBatch, null); @@ -531,17 +541,14 @@ namespace Subsurface private bool SwitchGender(GUIButton button, object obj) { - try - { - Gender gender = (Gender)obj; - GameMain.Client.CharacterInfo.Gender = gender; - GameMain.Client.SendCharacterData(); + Gender gender = (Gender)obj; + GameMain.NetworkMember.CharacterInfo.Gender = gender; + GameMain.Client.SendCharacterData(); - //CreatePreviewCharacter(); - } - catch {} + //CreatePreviewCharacter(); + - UpdatePreviewPlayer(GameMain.Client.CharacterInfo); + UpdatePreviewPlayer(GameMain.NetworkMember.CharacterInfo); return true; } @@ -594,6 +601,25 @@ namespace Subsurface return true; } + private bool ViewJobInfo(GUIButton button, object obj) + { + GUIComponent jobText = button.Parent; + + JobPrefab jobPrefab = jobText.UserData as JobPrefab; + if (jobPrefab == null) return false; + + jobInfoFrame = jobPrefab.CreateInfoFrame(); + GUIButton closeButton = new GUIButton(new Rectangle(0,0,100,20), "Close", Alignment.BottomRight, GUI.Style, jobInfoFrame); + closeButton.OnClicked = CloseJobInfo; + return true; + } + + private bool CloseJobInfo(GUIButton button, object obj) + { + jobInfoFrame = null; + return true; + } + private bool ChangeJobPreference(GUIButton button, object obj) { GUIComponent jobText = button.Parent; diff --git a/Subsurface/Subsurface.csproj b/Subsurface/Subsurface.csproj index 65735f615..70e1a2df3 100644 --- a/Subsurface/Subsurface.csproj +++ b/Subsurface/Subsurface.csproj @@ -67,6 +67,7 @@ + @@ -74,6 +75,8 @@ + + @@ -217,7 +220,7 @@ - + @@ -530,6 +533,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest Designer diff --git a/Subsurface_Solution.v12.suo b/Subsurface_Solution.v12.suo index 569993ce4d1231c095f033564fd638828075117a..f58a4d26c1f5f2609853700e141eb0a086af8cf1 100644 GIT binary patch delta 16624 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 delta 15523 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