From fb0e8ea54ce90a169a20fb0de9b2d0791666d1f5 Mon Sep 17 00:00:00 2001 From: Regalis Date: Sun, 19 Jul 2015 23:29:47 +0300 Subject: [PATCH] Fixing items if sufficient skills+tools, choosing server port --- Subsurface/Characters/StatusEffect.cs | 2 +- Subsurface/Content/Items/Reactor/reactor.xml | 12 ++ Subsurface/Content/Items/Tools/tools.xml | 12 ++ Subsurface/Content/Items/Weapons/weapons.xml | 2 +- Subsurface/GUI/GUITextBlock.cs | 5 +- Subsurface/GUI/GUITickBox.cs | 33 +++- .../Items/Components/Holdable/RangedWeapon.cs | 2 +- .../Items/Components/Holdable/RepairTool.cs | 3 + Subsurface/Items/Components/ItemComponent.cs | 23 ++- .../Items/Components/Machines/Reactor.cs | 2 +- Subsurface/Items/FixRequirement.cs | 186 ++++++++++++++++++ Subsurface/Items/Item.cs | 71 ++++--- Subsurface/Items/ItemPrefab.cs | 50 +---- Subsurface/Map/Explosion.cs | 2 +- Subsurface/Networking/GameClient.cs | 65 +++--- Subsurface/Networking/NetworkMember.cs | 6 +- Subsurface/Screens/MainMenu.cs | 4 +- Subsurface/Subsurface.csproj | 1 + Subsurface_Solution.v12.suo | Bin 479744 -> 455680 bytes 19 files changed, 357 insertions(+), 124 deletions(-) create mode 100644 Subsurface/Items/FixRequirement.cs diff --git a/Subsurface/Characters/StatusEffect.cs b/Subsurface/Characters/StatusEffect.cs index e7c504e4c..c1744171f 100644 --- a/Subsurface/Characters/StatusEffect.cs +++ b/Subsurface/Characters/StatusEffect.cs @@ -150,7 +150,7 @@ namespace Subsurface public virtual void Apply(ActionType type, float deltaTime, Vector2 position, IPropertyObject target) { - if (!targetNames.Contains(target.Name)) return; + if (targetNames == null && !targetNames.Contains(target.Name)) return; List targets = new List(); targets.Add(target); diff --git a/Subsurface/Content/Items/Reactor/reactor.xml b/Subsurface/Content/Items/Reactor/reactor.xml index 15ad3cfef..bd432f5cb 100644 --- a/Subsurface/Content/Items/Reactor/reactor.xml +++ b/Subsurface/Content/Items/Reactor/reactor.xml @@ -3,8 +3,20 @@ name="Nuclear Reactor" type ="Reactor" linkable="true"> + + + + + + + + + + + + diff --git a/Subsurface/Content/Items/Tools/tools.xml b/Subsurface/Content/Items/Tools/tools.xml index 6bcfc97f2..a94b1d709 100644 --- a/Subsurface/Content/Items/Tools/tools.xml +++ b/Subsurface/Content/Items/Tools/tools.xml @@ -17,6 +17,12 @@ + + + + + + @@ -44,6 +50,12 @@ + + + + + + diff --git a/Subsurface/Content/Items/Weapons/weapons.xml b/Subsurface/Content/Items/Weapons/weapons.xml index 654824ded..0af72d864 100644 --- a/Subsurface/Content/Items/Weapons/weapons.xml +++ b/Subsurface/Content/Items/Weapons/weapons.xml @@ -30,7 +30,7 @@ - + diff --git a/Subsurface/GUI/GUITextBlock.cs b/Subsurface/GUI/GUITextBlock.cs index 96004c4d6..935f645eb 100644 --- a/Subsurface/GUI/GUITextBlock.cs +++ b/Subsurface/GUI/GUITextBlock.cs @@ -54,6 +54,7 @@ namespace Subsurface public Color TextColor { get { return textColor; } + set { textColor = value; } } public Vector2 CaretPos @@ -64,16 +65,12 @@ namespace Subsurface public GUITextBlock(Rectangle rect, string text, GUIStyle style, GUIComponent parent = null, bool wrap = false) : this(rect, text, style, Alignment.TopLeft, Alignment.TopLeft, parent, wrap) { - //hoverColor = style.hoverColor; - //selectedColor = style.selectedColor; } public GUITextBlock(Rectangle rect, string text, GUIStyle style, Alignment alignment = Alignment.TopLeft, Alignment textAlignment = Alignment.TopLeft, GUIComponent parent = null, bool wrap = false) : this (rect, text, null, null, alignment, textAlignment, style, parent, wrap) { - //hoverColor = style.hoverColor; - //selectedColor = style.selectedColor; } public GUITextBlock(Rectangle rect, string text, Color? color, Color? textColor, Alignment textAlignment = Alignment.Left, GUIStyle style = null, GUIComponent parent = null, bool wrap = false) diff --git a/Subsurface/GUI/GUITickBox.cs b/Subsurface/GUI/GUITickBox.cs index 6e0a9fe8c..ffa74fb80 100644 --- a/Subsurface/GUI/GUITickBox.cs +++ b/Subsurface/GUI/GUITickBox.cs @@ -12,7 +12,18 @@ namespace Subsurface public delegate bool OnSelectedHandler(object obj); public OnSelectedHandler OnSelected; - bool selected; + private bool selected; + + public bool Selected + { + get { return selected; } + set + { + if (value == selected) return; + selected = value; + state = (selected) ? ComponentState.Selected : ComponentState.None; + } + } public GUITickBox(Rectangle rect, string label, Alignment alignment, GUIComponent parent) : base(null) @@ -20,8 +31,11 @@ namespace Subsurface if (parent != null) parent.AddChild(this); - box = new GUIFrame(new Rectangle(rect.X, rect.Y, 30, 30), Color.LightGray, null, this); - text = new GUITextBlock(new Rectangle(rect.X + 40, rect.Y, 200, 30), label, Color.Transparent, Color.White, Alignment.Left | Alignment.CenterY, null, this); + box = new GUIFrame(rect, Color.DarkGray, null, this); + box.HoverColor = Color.Gray; + box.SelectedColor = Color.DarkGray; + + text = new GUITextBlock(new Rectangle(rect.X + 40, rect.Y, 200, 30), label, Color.Transparent, Color.White, Alignment.TopLeft, null, this); } public override void Update(float deltaTime) @@ -33,28 +47,31 @@ namespace Subsurface box.State = ComponentState.Hover; if (PlayerInput.GetMouseState.LeftButton == ButtonState.Pressed) - box.State = ComponentState.Selected; + { + box.State = ComponentState.Selected; + } + if (PlayerInput.LeftButtonClicked()) { - selected = !selected; + Selected = !Selected; if (OnSelected != null) OnSelected(this); } - } else { box.State = ComponentState.None; } + } public override void Draw(SpriteBatch spriteBatch) { DrawChildren(spriteBatch); - if (selected) + if (Selected) { - GUI.DrawRectangle(spriteBatch, new Rectangle(box.Rect.X + 5, box.Rect.Y + 5, box.Rect.Width - 10, box.Rect.Height - 10), Color.Green * (color.A / 255.0f), true); + GUI.DrawRectangle(spriteBatch, new Rectangle(box.Rect.X + 2, box.Rect.Y + 2, box.Rect.Width - 4, box.Rect.Height - 4), Color.Green * 0.8f, true); } } } diff --git a/Subsurface/Items/Components/Holdable/RangedWeapon.cs b/Subsurface/Items/Components/Holdable/RangedWeapon.cs index 98622a207..05cbb0559 100644 --- a/Subsurface/Items/Components/Holdable/RangedWeapon.cs +++ b/Subsurface/Items/Components/Holdable/RangedWeapon.cs @@ -57,7 +57,7 @@ namespace Subsurface.Items.Components isActive = true; reload = 1.0f; - bool failed = UseFailed(character); + bool failed = DoesUseFail(character); List limbBodies = new List(); foreach (Limb l in character.AnimController.limbs) diff --git a/Subsurface/Items/Components/Holdable/RepairTool.cs b/Subsurface/Items/Components/Holdable/RepairTool.cs index 7cd21e7e8..98bb5e813 100644 --- a/Subsurface/Items/Components/Holdable/RepairTool.cs +++ b/Subsurface/Items/Components/Holdable/RepairTool.cs @@ -93,6 +93,9 @@ namespace Subsurface.Items.Components public override bool Use(float deltaTime, Character character = null) { if (character == null) return false; + if (!character.SecondaryKeyDown.State) return false; + + if (DoesUseFail(character)) return false; isActive = true; diff --git a/Subsurface/Items/Components/ItemComponent.cs b/Subsurface/Items/Components/ItemComponent.cs index 048e61dba..cf2d7d63b 100644 --- a/Subsurface/Items/Components/ItemComponent.cs +++ b/Subsurface/Items/Components/ItemComponent.cs @@ -356,24 +356,39 @@ namespace Subsurface } public bool HasRequiredSkills(Character character) + { + Skill temp; + return HasRequiredSkills(character, out temp); + } + + public bool HasRequiredSkills(Character character, out Skill insufficientSkill) { foreach (Skill skill in requiredSkills) { int characterLevel = character.GetSkillLevel(skill.Name); - if (characterLevel < skill.Level) return false; + if (characterLevel < skill.Level) + { + insufficientSkill = skill; + return false; + } } - + insufficientSkill = null; return true; } - protected bool UseFailed(Character character) + protected bool DoesUseFail(Character character) { foreach (Skill skill in requiredSkills) { int characterLevel = character.GetSkillLevel(skill.Name); if (characterLevel > skill.Level) continue; - if (Rand.Int(characterLevel) - skill.Level < 0) return true; + if (Rand.Int(characterLevel) - skill.Level < 0) + { + item.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character); + //Item.ApplyStatusEffects(); + return true; + } } return false; diff --git a/Subsurface/Items/Components/Machines/Reactor.cs b/Subsurface/Items/Components/Machines/Reactor.cs index 5df26a393..bd2260e39 100644 --- a/Subsurface/Items/Components/Machines/Reactor.cs +++ b/Subsurface/Items/Components/Machines/Reactor.cs @@ -308,7 +308,7 @@ namespace Subsurface.Items.Components float xOffset = (graphTimer / (float)updateGraphInterval); - GUI.DrawRectangle(spriteBatch, new Rectangle(x, y, width, height), Color.Black, true); + //GUI.DrawRectangle(spriteBatch, new Rectangle(x, y, width, height), Color.Black, true); spriteBatch.DrawString(GUI.Font, "Temperature: " + (int)temperature + " C", new Vector2(x + 30, y + 30), Color.White); DrawGraph(tempGraph, spriteBatch, x + 30, y + 50, 10000.0f, xOffset); diff --git a/Subsurface/Items/FixRequirement.cs b/Subsurface/Items/FixRequirement.cs new file mode 100644 index 000000000..f117b1b73 --- /dev/null +++ b/Subsurface/Items/FixRequirement.cs @@ -0,0 +1,186 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace Subsurface +{ + class FixRequirement + { + string name; + + private static GUIFrame frame; + + List requiredSkills; + List requiredItems; + + public bool Fixed; + + public FixRequirement(XElement element) + { + name = ToolBox.GetAttributeString(element, "name", ""); + + requiredSkills = new List(); + requiredItems = new List(); + + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLower()) + { + case "skill": + string skillName = ToolBox.GetAttributeString(subElement, "name", ""); + int level = ToolBox.GetAttributeInt(subElement, "level", 1); + + requiredSkills.Add(new Skill(skillName, level)); + break; + case "item": + string itemName = ToolBox.GetAttributeString(subElement, "name", ""); + + requiredItems.Add(itemName); + break; + } + } + } + + public bool Fix(Character character, GUIComponent reqFrame) + { + bool success = true; + foreach (string itemName in requiredItems) + { + GUIComponent component = reqFrame.children.Find(c => c.UserData as string == itemName); + + GUITextBlock text = component as GUITextBlock; + bool itemFound = (character.Inventory.items.FirstOrDefault(i => i !=null && i.Name == itemName) != null); + + if (!itemFound) success = false; + + if (text != null) text.TextColor = itemFound ? Color.LightGreen : Color.Red; + } + + foreach (Skill skill in requiredSkills) + { + GUIComponent component = reqFrame.children.Find(c => c.UserData as Skill == skill); + GUITextBlock text = component as GUITextBlock; + + float characterSkill = character.GetSkillLevel(skill.Name); + bool sufficientSkill = characterSkill >= skill.Level; + + if (!sufficientSkill) success = false; + + if (text != null) text.TextColor = sufficientSkill ? Color.LightGreen : Color.Red; + } + + return success; + } + + private static void CreateGUIFrame(Item item) + { + int width = 400, height = 500; + int x = 0, y = 0; + + frame = new GUIFrame(new Rectangle(0, 0, width, height), Color.White * 0.8f, Alignment.Center, GUI.style); + frame.Padding = new Vector4(20.0f, 20.0f, 20.0f, 20.0f); + frame.UserData = item; + + new GUITextBlock(new Rectangle(0,0,200,20), "Attempting to fix " + item.Name, GUI.style, frame); + + y = y + 40; + foreach (FixRequirement requirement in item.FixRequirements) + { + GUIFrame reqFrame = new GUIFrame( + new Rectangle(0, y, 0, 20 + Math.Max(requirement.requiredItems.Count, requirement.requiredSkills.Count) * 15), + Color.Transparent, null, frame); + reqFrame.UserData = requirement; + + + var fixButton = new GUIButton(new Rectangle(0, 0, 50, 20), "Fix", GUI.style, reqFrame); + fixButton.OnClicked = FixButtonPressed; + fixButton.UserData = requirement; + + new GUITickBox(new Rectangle(70, 0, 20,20), requirement.name, Alignment.Left, reqFrame); + + int y2 = 20; + foreach (string itemName in requirement.requiredItems) + { + var itemBlock = new GUITextBlock(new Rectangle(30, y2, 200, 15), itemName, GUI.style, reqFrame); + itemBlock.Font = GUI.SmallFont; + itemBlock.UserData = itemName; + + y2 += 15; + } + + y2 = 20; + foreach (Skill skill in requirement.requiredSkills) + { + var skillBlock = new GUITextBlock(new Rectangle(150, y2, 200, 15), skill.Name + " - " + skill.Level, GUI.style, Alignment.Right, Alignment.TopLeft, reqFrame); + skillBlock.Font = GUI.SmallFont; + skillBlock.UserData = skill; + + + y2 += 15; + } + + y += reqFrame.Rect.Height; + } + } + + private static bool FixButtonPressed(GUIButton button, object obj) + { + FixRequirement requirement = obj as FixRequirement; + if (requirement == null) return false; + + requirement.Fixed = true; + return true; + } + + private static void UpdateGUIFrame(Item item, Character character) + { + bool unfixedFound = false; + foreach (GUIComponent child in frame.children) + { + FixRequirement requirement = child.UserData as FixRequirement; + if (requirement == null) continue; + + if (requirement.Fixed) + { + child.Color = Color.LightGreen * 0.3f; + child.GetChild().Selected = true; + } + else + { + bool canBeFixed = requirement.Fix(character, child); + unfixedFound = true; + //child.GetChild().Selected = canBeFixed; + GUITickBox tickBox = child.GetChild(); + if (tickBox.Selected) + { + tickBox.Selected = canBeFixed; + requirement.Fixed = canBeFixed; + + } + child.Color = Color.Red * 0.2f; + //tickBox.State = GUIComponent.ComponentState.None; + } + } + if (!unfixedFound) + { + item.Condition = 100.0f; + } + } + + public static void DrawHud(SpriteBatch spriteBatch, Item item, Character character) + { + if (frame == null || frame.UserData != item) + { + CreateGUIFrame(item); + + } +UpdateGUIFrame(item, character); + frame.Update((float)Physics.step); + frame.Draw(spriteBatch); + } + } +} diff --git a/Subsurface/Items/Item.cs b/Subsurface/Items/Item.cs index 6c7c91fff..d9972f511 100644 --- a/Subsurface/Items/Item.cs +++ b/Subsurface/Items/Item.cs @@ -17,7 +17,7 @@ namespace Subsurface public enum ActionType { - OnPicked, OnWearing, OnContaining, OnContained, OnActive, OnUse + OnPicked, OnWearing, OnContaining, OnContained, OnActive, OnUse, OnFailure } class Item : MapEntity, IDamageable, IPropertyObject @@ -27,6 +27,7 @@ namespace Subsurface private List tags; + public Hull CurrentHull; //components that determine the functionality of the item @@ -47,6 +48,8 @@ namespace Subsurface public Item container; + public List FixRequirements; + public override string Name { get { return prefab.Name; } @@ -57,7 +60,6 @@ namespace Subsurface get { return prefab.sprite; } } - public float Condition { get { return condition; } @@ -69,6 +71,7 @@ namespace Subsurface get { return condition; } } + [Editable, HasDefaultValue("", true)] public string Tags { @@ -181,17 +184,13 @@ namespace Subsurface { prefab = itemPrefab; - linkedTo = new ObservableCollection(); - components = new List(); - - tags = new List(); + linkedTo = new ObservableCollection(); + components = new List(); + FixRequirements = new List(); + tags = new List(); rect = newRect; - //rect.X -= rect.Width / 2; - //rect.Y += rect.Height / 2; - - //dir = 1.0f; - + FindHull(); condition = 100.0f; @@ -210,21 +209,8 @@ namespace Subsurface } } - properties = ObjectProperty.InitProperties(this, element); - //foreach (XAttribute attribute in element.Attributes()) - //{ - // ObjectProperty property = null; - // if (!properties.TryGetValue(attribute.Name.ToString().ToLower(), out property)) continue; - // if (property.Attributes.OfType().Count() == 0) continue; - // property.TrySetValue(attribute.Value); - //} - - - - //highlightText = new List(); - foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLower()) @@ -240,6 +226,9 @@ namespace Subsurface aiTarget.SightRange = ToolBox.GetAttributeFloat(subElement, "sightrange", 1000.0f); aiTarget.SoundRange = ToolBox.GetAttributeFloat(subElement, "soundrange", 0.0f); break; + case "fixrequirement": + FixRequirements.Add(new FixRequirement(subElement)); + break; default: ItemComponent ic = ItemComponent.Load(subElement, this, prefab.ConfigFile); if (ic == null) break; @@ -638,16 +627,23 @@ namespace Subsurface public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { - if (editingHUD==null || editingHUD.UserData as Item != this) + if (condition>0.0f) { - editingHUD = CreateEditingHUD(true); + if (editingHUD==null || editingHUD.UserData as Item != this) + { + editingHUD = CreateEditingHUD(true); + } + + editingHUD.Draw(spriteBatch); + + foreach (ItemComponent ic in components) + { + ic.DrawHUD(spriteBatch, character); + } } - - editingHUD.Draw(spriteBatch); - - foreach (ItemComponent ic in components) + else { - ic.DrawHUD(spriteBatch, character); + FixRequirement.DrawHud(spriteBatch, this, character); } } @@ -730,9 +726,16 @@ namespace Subsurface bool hasRequiredSkills = true; bool picked = false, selected = false; + + Skill requiredSkill = null; + foreach (ItemComponent ic in components) { - if (!ic.HasRequiredSkills(picker)) hasRequiredSkills = false; + Skill tempRequiredSkill; + if (!ic.HasRequiredSkills(picker, out tempRequiredSkill)) hasRequiredSkills = false; + + if (tempRequiredSkill != null) requiredSkill = tempRequiredSkill; + if (!ic.HasRequiredItems(picker, picker == Character.Controlled) && !forcePick) continue; if ((ic.CanBePicked && ic.Pick(picker)) || (ic.CanBeSelected && ic.Select(picker))) { @@ -751,6 +754,10 @@ namespace Subsurface if (!hasRequiredSkills && Character.Controlled==picker) { GUI.AddMessage("Your skills may be insufficient to use the item!", Color.Red, 5.0f); + if (requiredSkill != null) + { + GUI.AddMessage("("+requiredSkill.Name+" level "+requiredSkill.Level+" required)", Color.Red, 5.0f); + } } if (container!=null) container.RemoveContained(this); diff --git a/Subsurface/Items/ItemPrefab.cs b/Subsurface/Items/ItemPrefab.cs index 8ed6b2518..b1a768580 100644 --- a/Subsurface/Items/ItemPrefab.cs +++ b/Subsurface/Items/ItemPrefab.cs @@ -25,9 +25,6 @@ namespace Subsurface //how close the character has to be to the item to pick it up private float pickDistance; - - //public List sounds; - //an area next to the construction //the construction can be Activated() by a character inside the area public List Triggers; @@ -144,40 +141,19 @@ namespace Subsurface name = ToolBox.GetAttributeString(element, "name", ""); if (name == "") DebugConsole.ThrowError("Unnamed item in "+filePath+"!"); - //if (element.Attribute("sprite") != null) - //{ - // sprite = new Sprite(Path.GetDirectoryName(filePath) + "/" + element.Attribute("sprite").Value, new Vector2(0.5f, 0.5f)); - // sprite.Depth = 0.5f; - //} - - //var initableProperties = GetProperties(); - //foreach (ObjectProperty initableProperty in initableProperties) - //{ - // object value = ToolBox.GetAttributeObject(element, initableProperty.Name.ToLower()); - // if (value == null) - // { - // foreach (var ini in initableProperty.Attributes.OfType()) - // { - // value = ini.defaultValue; - // break; - // } - // } - - // initableProperty.TrySetValue(value); - //} - pickDistance = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "pickdistance", 0.0f)); - isLinkable = ToolBox.GetAttributeBool(element, "linkable", false); + isLinkable = ToolBox.GetAttributeBool(element, "linkable", false); - resizeHorizontal = ToolBox.GetAttributeBool(element, "resizehorizontal", false); - resizeVertical = ToolBox.GetAttributeBool(element, "resizevertical", false); + resizeHorizontal = ToolBox.GetAttributeBool(element, "resizehorizontal", false); + resizeVertical = ToolBox.GetAttributeBool(element, "resizevertical", false); - focusOnSelected = ToolBox.GetAttributeBool(element, "focusonselected", false); + focusOnSelected = ToolBox.GetAttributeBool(element, "focusonselected", false); - offsetOnSelected = ToolBox.GetAttributeFloat(element, "offsetonselected", 0.0f); + offsetOnSelected = ToolBox.GetAttributeFloat(element, "offsetonselected", 0.0f); - Triggers = new List(); + Triggers = new List(); + foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLower()) @@ -201,18 +177,6 @@ namespace Subsurface } } - //sounds = new List(); - //var soundElements = element.Descendants(); - //foreach (XElement soundElement in soundElements) - //{ - // if (soundElement.Name.ToString().ToLower() != "sound") continue; - // string soundPath = ToolBox.GetAttributeString(soundElement, "path", ""); - // if (soundPath == "") continue; - - // Sound sound = Sound.Load(soundPath); - // if (sound != null) sounds.Add(sound); - //} - list.Add(this); } } diff --git a/Subsurface/Map/Explosion.cs b/Subsurface/Map/Explosion.cs index 64b20cf53..d0344c4f0 100644 --- a/Subsurface/Map/Explosion.cs +++ b/Subsurface/Map/Explosion.cs @@ -94,7 +94,7 @@ namespace Subsurface { distFactor = 1.0f - Vector2.Distance(limb.SimPosition, position)/range; - c.AddDamage(limb.SimPosition, DamageType.None, damage * distFactor, 0.0f, stun * distFactor); + c.AddDamage(limb.SimPosition, DamageType.None, damage / c.AnimController.limbs.Length * distFactor, 0.0f, stun * distFactor); if (force>0.0f) { diff --git a/Subsurface/Networking/GameClient.cs b/Subsurface/Networking/GameClient.cs index 3528a3bb2..d14e1c8ff 100644 --- a/Subsurface/Networking/GameClient.cs +++ b/Subsurface/Networking/GameClient.cs @@ -6,22 +6,20 @@ using System.Collections.Generic; namespace Subsurface.Networking { - - class GameClient : NetworkMember { - NetClient Client; + private NetClient client; private Character myCharacter; private CharacterInfo characterInfo; - GUIMessageBox reconnectBox; + private GUIMessageBox reconnectBox; private bool connected; private int myID; - List otherClients; + private List otherClients; private string serverIP; @@ -48,11 +46,28 @@ namespace Subsurface.Networking characterInfo = new CharacterInfo("Content/Characters/Human/human.xml", name); otherClients = new List(); + } public void ConnectToServer(string hostIP) { - serverIP = hostIP; + + string[] address = hostIP.Split(':'); + if (address.Length==1) + { + serverIP = hostIP; + Port = DefaultPort; + } + else + { + serverIP = address[0]; + + if (!int.TryParse(address[1], out Port)) + { + DebugConsole.ThrowError("Invalid port: address[1]!"); + Port = DefaultPort; + } + } myCharacter = Character.Controlled; @@ -63,10 +78,10 @@ namespace Subsurface.Networking //Config.SimulatedMinimumLatency = 0.25f; // Create new client, with previously created configs - Client = new NetClient(Config); + client = new NetClient(Config); - NetOutgoingMessage outmsg = Client.CreateMessage(); - Client.Start(); + NetOutgoingMessage outmsg = client.CreateMessage(); + client.Start(); outmsg.Write((byte)PacketTypes.Login); outmsg.Write(Game1.Version.ToString()); @@ -75,7 +90,7 @@ namespace Subsurface.Networking // Connect client, to ip previously requested from user try { - Client.Connect(hostIP, 14242, outmsg); + client.Connect(serverIP, Port, outmsg); } catch (ArgumentNullException e) { @@ -133,7 +148,7 @@ namespace Subsurface.Networking NetIncomingMessage inc; // If new messages arrived - if ((inc = Client.ReadMessage()) == null) continue; + if ((inc = client.ReadMessage()) == null) continue; // Switch based on the message types switch (inc.MessageType) @@ -192,7 +207,7 @@ namespace Subsurface.Networking reconnectBox = null; } - if (Client.ConnectionStatus != NetConnectionStatus.Connected) + if (client.ConnectionStatus != NetConnectionStatus.Connected) { var reconnect = new GUIMessageBox("CONNECTION FAILED", "Failed to connect to server.", new string[] { "Retry", "Cancel" }); reconnect.Buttons[0].OnClicked += RetryConnection; @@ -226,7 +241,7 @@ namespace Subsurface.Networking if (!connected || updateTimer > DateTime.Now) return; - if (Client.ConnectionStatus == NetConnectionStatus.Disconnected && reconnectBox==null) + if (client.ConnectionStatus == NetConnectionStatus.Disconnected && reconnectBox==null) { reconnectBox = new GUIMessageBox("CONNECTION LOST", "You have been disconnected from the server. Reconnecting...", new string[0]); connected = false; @@ -255,13 +270,13 @@ namespace Subsurface.Networking foreach (NetworkEvent networkEvent in NetworkEvent.events) { - NetOutgoingMessage message = Client.CreateMessage(); + NetOutgoingMessage message = client.CreateMessage(); message.Write((byte)PacketTypes.NetworkEvent); if (networkEvent.FillData(message)) { - Client.SendMessage(message, + client.SendMessage(message, (networkEvent.IsImportant) ? NetDeliveryMethod.ReliableUnordered : NetDeliveryMethod.Unreliable); } } @@ -284,7 +299,7 @@ namespace Subsurface.Networking // Create new incoming message holder NetIncomingMessage inc; - while ((inc = Client.ReadMessage()) != null) + while ((inc = client.ReadMessage()) != null) { if (inc.MessageType != NetIncomingMessageType.Data) continue; @@ -406,18 +421,18 @@ namespace Subsurface.Networking public override void Disconnect() { - NetOutgoingMessage msg = Client.CreateMessage(); + NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)PacketTypes.PlayerLeft); - Client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); - Client.Shutdown(""); + client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); + client.Shutdown(""); } public void SendCharacterData() { if (characterInfo == null) return; - NetOutgoingMessage msg = Client.CreateMessage(); + NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)PacketTypes.CharacterInfo); msg.Write(characterInfo.Name); msg.Write(characterInfo.Gender == Gender.Male); @@ -431,7 +446,7 @@ namespace Subsurface.Networking msg.Write(jobPreferences[i].Name); } - Client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); + client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } private Character ReadCharacterData(NetIncomingMessage inc) @@ -486,12 +501,12 @@ namespace Subsurface.Networking type = (gameStarted && myCharacter != null && myCharacter.IsDead) ? ChatMessageType.Dead : ChatMessageType.Default; - NetOutgoingMessage msg = Client.CreateMessage(); + NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)PacketTypes.Chatmessage); msg.Write((byte)type); msg.Write(message); - Client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); + client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } /// @@ -500,7 +515,7 @@ namespace Subsurface.Networking /// public void SendRandomData() { - NetOutgoingMessage msg = Client.CreateMessage(); + NetOutgoingMessage msg = client.CreateMessage(); switch (Rand.Int(5)) { case 0: @@ -531,7 +546,7 @@ namespace Subsurface.Networking } - Client.SendMessage(msg, (Rand.Int(2)==0) ? NetDeliveryMethod.ReliableOrdered : NetDeliveryMethod.Unreliable); + client.SendMessage(msg, (Rand.Int(2)==0) ? NetDeliveryMethod.ReliableOrdered : NetDeliveryMethod.Unreliable); } } diff --git a/Subsurface/Networking/NetworkMember.cs b/Subsurface/Networking/NetworkMember.cs index 6d3641cf6..1d5cea09a 100644 --- a/Subsurface/Networking/NetworkMember.cs +++ b/Subsurface/Networking/NetworkMember.cs @@ -29,8 +29,10 @@ namespace Subsurface.Networking class NetworkMember { - protected static Color[] messageColor = { Color.White, Color.Red, Color.LightBlue, Color.LightGreen }; + public const int DefaultPort = 14242; + protected static Color[] messageColor = { Color.White, Color.Red, Color.LightBlue, Color.LightGreen }; + protected string name; protected TimeSpan updateInterval; @@ -39,6 +41,8 @@ namespace Subsurface.Networking protected GUIFrame inGameHUD; protected GUIListBox chatBox; + public int Port; + protected bool gameStarted; public string Name diff --git a/Subsurface/Screens/MainMenu.cs b/Subsurface/Screens/MainMenu.cs index 70714c967..d78b9bf53 100644 --- a/Subsurface/Screens/MainMenu.cs +++ b/Subsurface/Screens/MainMenu.cs @@ -141,7 +141,7 @@ namespace Subsurface new GUITextBlock(new Rectangle(0, 100, 0, 30), "Server IP:", GUI.style, Alignment.CenterX, Alignment.CenterX, menuTabs[(int)Tabs.JoinServer]); ipBox = new GUITextBox(new Rectangle(0, 130, 200, 30), Color.White, Color.Black, Alignment.CenterX, Alignment.CenterX, null, menuTabs[(int)Tabs.JoinServer]); - + GUIButton joinButton = new GUIButton(new Rectangle(0, 0, 200, 30), "Join", Alignment.BottomCenter, GUI.style, menuTabs[(int)Tabs.JoinServer]); joinButton.OnClicked = JoinServer; @@ -162,7 +162,7 @@ namespace Subsurface selectedTab = (int)obj; return true; } - + private bool HostServerClicked(GUIButton button, object obj) { Game1.NetLobbyScreen.IsServer = true; diff --git a/Subsurface/Subsurface.csproj b/Subsurface/Subsurface.csproj index a45191f9c..004ff703e 100644 --- a/Subsurface/Subsurface.csproj +++ b/Subsurface/Subsurface.csproj @@ -66,6 +66,7 @@ + diff --git a/Subsurface_Solution.v12.suo b/Subsurface_Solution.v12.suo index 92cc730011c0800906512aef3decc0c503ef911a..64afddea1d12cacdefe3d07d51c59d56563b2ff2 100644 GIT binary patch delta 13722 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 delta 12845 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