From e33f30dad11fefe4e13738b99138ad0a74a2fa5b Mon Sep 17 00:00:00 2001 From: Regalis Date: Wed, 20 Apr 2016 17:19:38 +0300 Subject: [PATCH] Mid-round chat messages have a limited range, headset item which can be used to communicate with players further away, new inventory slot for items like masks and headsets --- .../Content/Items/Diving/divinggear.xml | 2 +- .../Content/Items/Jobgear/doctorgear.xml | 2 +- Subsurface/Content/Items/Jobgear/headset.png | Bin 0 -> 537 bytes Subsurface/Content/Items/Jobgear/misc.xml | 27 +++- Subsurface/Content/Jobs.xml | 83 +++++++---- Subsurface/Content/Sounds/UI/deadmsg.ogg | Bin 0 -> 6706 bytes Subsurface/Content/Sounds/UI/radiomsg.ogg | Bin 0 -> 10080 bytes Subsurface/Content/UI/inventoryIcons.png | Bin 12809 -> 14066 bytes Subsurface/Source/Characters/Character.cs | 46 ++---- Subsurface/Source/Characters/Jobs/Job.cs | 63 +++++++- .../Source/Characters/Jobs/JobPrefab.cs | 24 ++-- Subsurface/Source/GUI/GUI.cs | 10 +- Subsurface/Source/GUI/GUITextBox.cs | 6 + Subsurface/Source/Items/CharacterInventory.cs | 25 +++- .../Items/Components/Signal/WifiComponent.cs | 64 ++++++++- Subsurface/Source/Map/MapEntity.cs | 4 +- Subsurface/Source/Networking/ChatMessage.cs | 136 ++++++++++++++++++ Subsurface/Source/Networking/GameClient.cs | 30 +++- Subsurface/Source/Networking/GameServer.cs | 107 ++++++++++---- Subsurface/Source/Networking/NetworkMember.cs | 122 +++++++++++----- Subsurface/Source/Screens/NetLobbyScreen.cs | 23 +-- 21 files changed, 597 insertions(+), 177 deletions(-) create mode 100644 Subsurface/Content/Items/Jobgear/headset.png create mode 100644 Subsurface/Content/Sounds/UI/deadmsg.ogg create mode 100644 Subsurface/Content/Sounds/UI/radiomsg.ogg create mode 100644 Subsurface/Source/Networking/ChatMessage.cs diff --git a/Subsurface/Content/Items/Diving/divinggear.xml b/Subsurface/Content/Items/Diving/divinggear.xml index 2a27490e8..dbd6e5b2d 100644 --- a/Subsurface/Content/Items/Diving/divinggear.xml +++ b/Subsurface/Content/Items/Diving/divinggear.xml @@ -38,7 +38,7 @@ - + diff --git a/Subsurface/Content/Items/Jobgear/doctorgear.xml b/Subsurface/Content/Items/Jobgear/doctorgear.xml index d7c5a2cd4..98e5e7412 100644 --- a/Subsurface/Content/Items/Jobgear/doctorgear.xml +++ b/Subsurface/Content/Items/Jobgear/doctorgear.xml @@ -10,7 +10,7 @@ - + diff --git a/Subsurface/Content/Items/Jobgear/headset.png b/Subsurface/Content/Items/Jobgear/headset.png new file mode 100644 index 0000000000000000000000000000000000000000..c8ccc00c500eb992a164bc4be3fcea77c8f5efe3 GIT binary patch literal 537 zcmV+!0_OdRP)(_`g8%^e{{R4h=l}px2mk>USO5SzmjD14Z`WEM zkN^MypGibPRCwCNld(=4K@f)j-8=8?p6@Wo_@X-;+ma=u6r`hcL2HSEI#MdX15c2a zhBwF?(C`8@7JZB$j!+yBF$%?DpVxO_56#&YGB`*eG14zhGoNPunPEhP*96Ba<81+c z;5W*Za?@RMn||OoUIJhf4{BSr`qiJS?~Jqjh(^#_-x!AR_2T!%;Ua)JN!q9F?fbj? zyu0LL)mw#`G4n~1Y)jiZWSjv2l3@a=BnEvA03f2-2;FYC<~R;G9iCF^pmmFfHaOblQPe@YaP82*VIT5a8_OC%~}|0E{zmlVOyMr>&32^w`8)E_bH2 zMi_>Oq6ppVzld(5UqT63N<1olatobK=g2S&lu9M^dbb!3V(3A?Y8lq9E3@XaE)o&a zP1FJW13;-xENOi!e=NjGh#$66JH@pREF(-zOeLZjAcZslG*fL75u`0YJFb1;<+Ec; zSjV + + + + + + + + + + + + + + + + + + + + + - + diff --git a/Subsurface/Content/Jobs.xml b/Subsurface/Content/Jobs.xml index 0c2bc183a..1029f4b1a 100644 --- a/Subsurface/Content/Jobs.xml +++ b/Subsurface/Content/Jobs.xml @@ -1,17 +1,21 @@  - - - - - + + + + + + + + + @@ -21,10 +25,15 @@ - - - - + + + + + + + + + @@ -34,10 +43,15 @@ - - - - + + + + + + + + + @@ -47,12 +61,17 @@ - - - - - - + + + + + + + + + + + @@ -62,11 +81,16 @@ - - - - - + + + + + + + + + + @@ -76,8 +100,13 @@ - - - + + + + + + + + \ No newline at end of file diff --git a/Subsurface/Content/Sounds/UI/deadmsg.ogg b/Subsurface/Content/Sounds/UI/deadmsg.ogg new file mode 100644 index 0000000000000000000000000000000000000000..bd694f2cf0054aa84f7408725a24ce1f68eaf5d6 GIT binary patch literal 6706 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 literal 0 HcmV?d00001 diff --git a/Subsurface/Content/Sounds/UI/radiomsg.ogg b/Subsurface/Content/Sounds/UI/radiomsg.ogg new file mode 100644 index 0000000000000000000000000000000000000000..13fa4d47aa19e6fc5bb5c4e7c3eb0180bf1c980d GIT binary patch literal 10080 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 literal 0 HcmV?d00001 diff --git a/Subsurface/Content/UI/inventoryIcons.png b/Subsurface/Content/UI/inventoryIcons.png index c4c5339c483012f85e4c433fe76553d018a22bdd..ab2341432011a5710647dff39d55485cacc6f1f6 100644 GIT binary patch literal 14066 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 KB2^_}67oL-jV+G= diff --git a/Subsurface/Source/Characters/Character.cs b/Subsurface/Source/Characters/Character.cs index 3fd674551..c7ece6fc3 100644 --- a/Subsurface/Source/Characters/Character.cs +++ b/Subsurface/Source/Characters/Character.cs @@ -454,7 +454,7 @@ namespace Barotrauma { AnimController = new HumanoidAnimController(this, doc.Root.Element("ragdoll")); AnimController.TargetDir = Direction.Right; - inventory = new CharacterInventory(15, this); + inventory = new CharacterInventory(16, this); } else { @@ -594,39 +594,7 @@ namespace Barotrauma { if (info == null || info.Job == null) return; - for (int i = 0; i < info.Job.SpawnItemNames.Count; i++ ) - { - string itemName = info.Job.SpawnItemNames[i]; - - ItemPrefab itemPrefab = ItemPrefab.list.Find(ip => ip.Name == itemName) as ItemPrefab; - if (itemPrefab == null) - { - DebugConsole.ThrowError("Tried to spawn ''" + Name + "'' with the item ''" + itemName + "''. Matching item prefab not found."); - continue; - } - - Item item = new Item(itemPrefab, Position, null); - - if (info.Job.EquipSpawnItem[i]) - { - List allowedSlots = new List(item.AllowedSlots); - allowedSlots.Remove(LimbSlot.Any); - - inventory.TryPutItem(item, allowedSlots, false); - } - else - { - inventory.TryPutItem(item, item.AllowedSlots, false); - } - - if (item.Prefab.Name == "ID Card" && spawnPoint != null) - { - foreach (string s in spawnPoint.IdCardTags) - { - item.AddTag(s); - } - } - } + info.Job.GiveJobItems(this, spawnPoint); } public int GetSkillLevel(string skillName) @@ -988,6 +956,16 @@ namespace Barotrauma if (!Enabled) return; obstructVisionAmount = Math.Max(obstructVisionAmount - deltaTime, 0.0f); + + if (inventory!=null) + { + foreach (Item item in inventory.Items) + { + if (item == null || item.body == null || item.body.Enabled) continue; + item.Submarine = Submarine; + item.SetTransform(SimPosition, 0.0f); + } + } if (isDead) return; diff --git a/Subsurface/Source/Characters/Jobs/Job.cs b/Subsurface/Source/Characters/Jobs/Job.cs index c34b91089..d0e162094 100644 --- a/Subsurface/Source/Characters/Jobs/Job.cs +++ b/Subsurface/Source/Characters/Jobs/Job.cs @@ -26,15 +26,15 @@ namespace Barotrauma get { return prefab; } } - public List SpawnItemNames + public XElement SpawnItems { - get { return prefab.ItemNames; } + get { return prefab.Items; } } - public List EquipSpawnItem - { - get { return prefab.EquipItem; } - } + //public List EquipSpawnItem + //{ + // get { return prefab.EquipItem; } + //} public List Skills { @@ -84,6 +84,57 @@ namespace Barotrauma return (skill==null) ? 0 : skill.Level; } + public void GiveJobItems(Character character, WayPoint spawnPoint) + { + foreach (XElement itemElement in SpawnItems.Elements()) + { + InitializeJobItem(character, spawnPoint, itemElement); + + } + } + + private void InitializeJobItem(Character character, WayPoint spawnPoint, XElement itemElement, Item parentItem = null) + { + string itemName = ToolBox.GetAttributeString(itemElement, "name", ""); + + ItemPrefab itemPrefab = ItemPrefab.list.Find(ip => ip.Name == itemName) as ItemPrefab; + if (itemPrefab == null) + { + DebugConsole.ThrowError("Tried to spawn ''" + Name + "'' with the item ''" + itemName + "''. Matching item prefab not found."); + return; + } + + Item item = new Item(itemPrefab, character.Position, null); + + if (ToolBox.GetAttributeBool(itemElement, "equip", false)) + { + List allowedSlots = new List(item.AllowedSlots); + allowedSlots.Remove(LimbSlot.Any); + + character.Inventory.TryPutItem(item, allowedSlots, false); + } + else + { + character.Inventory.TryPutItem(item, item.AllowedSlots, false); + } + + if (item.Prefab.Name == "ID Card" && spawnPoint != null) + { + foreach (string s in spawnPoint.IdCardTags) + { + item.AddTag(s); + } + } + + + if (parentItem != null) parentItem.Combine(item); + + foreach (XElement childItemElement in itemElement.Elements()) + { + InitializeJobItem(character, spawnPoint, childItemElement, item); + } + } + public virtual XElement Save(XElement parentElement) { XElement jobElement = new XElement("job"); diff --git a/Subsurface/Source/Characters/Jobs/JobPrefab.cs b/Subsurface/Source/Characters/Jobs/JobPrefab.cs index 58b4f1452..426a1e3b4 100644 --- a/Subsurface/Source/Characters/Jobs/JobPrefab.cs +++ b/Subsurface/Source/Characters/Jobs/JobPrefab.cs @@ -10,9 +10,9 @@ namespace Barotrauma { public static List List; - //names of the items the Character spawns with - public List ItemNames; - public List EquipItem; + public readonly XElement Items; + public readonly List ItemNames; + //public List EquipItem; public List Skills; @@ -71,7 +71,7 @@ namespace Barotrauma AllowAlways = ToolBox.GetAttributeBool(element, "allowalways", false); ItemNames = new List(); - EquipItem = new List(); + //EquipItem = new List(); Skills = new List(); @@ -79,14 +79,20 @@ namespace Barotrauma { switch (subElement.Name.ToString().ToLower()) { - case "item": - string itemName = ToolBox.GetAttributeString(subElement, "name", ""); - bool equipItem = ToolBox.GetAttributeBool(subElement, "equip", false); - if (!string.IsNullOrEmpty(itemName)) + case "items": + Items = subElement; + foreach (XElement itemElement in subElement.Elements()) { + string itemName = ToolBox.GetAttributeString(subElement, "name", ""); ItemNames.Add(itemName); - EquipItem.Add(equipItem); } + //string itemName = ToolBox.GetAttributeString(subElement, "name", ""); + //bool equipItem = ToolBox.GetAttributeBool(subElement, "equip", false); + //if (!string.IsNullOrEmpty(itemName)) + //{ + // ItemNames.Add(itemName); + // EquipItem.Add(equipItem); + //} break; case "skills": foreach (XElement skillElement in subElement.Elements()) diff --git a/Subsurface/Source/GUI/GUI.cs b/Subsurface/Source/GUI/GUI.cs index 14d7e483b..19bb08050 100644 --- a/Subsurface/Source/GUI/GUI.cs +++ b/Subsurface/Source/GUI/GUI.cs @@ -18,8 +18,10 @@ namespace Barotrauma public enum GUISoundType { - Message = 0, - Click = 1 + Message, + RadioMessage, + DeadMessage, + Click } public class GUI @@ -75,8 +77,10 @@ namespace Barotrauma if (loadSounds) { - sounds = new Sound[2]; + sounds = new Sound[4]; sounds[(int)GUISoundType.Message] = Sound.Load("Content/Sounds/UI/UImsg.ogg", false); + sounds[(int)GUISoundType.RadioMessage] = Sound.Load("Content/Sounds/UI/radiomsg.ogg", false); + sounds[(int)GUISoundType.DeadMessage] = Sound.Load("Content/Sounds/UI/deadmsg.ogg", false); sounds[(int)GUISoundType.Click] = Sound.Load("Content/Sounds/UI/beep-shinymetal.ogg", false); } diff --git a/Subsurface/Source/GUI/GUITextBox.cs b/Subsurface/Source/GUI/GUITextBox.cs index 07a444ef2..eb5aaa945 100644 --- a/Subsurface/Source/GUI/GUITextBox.cs +++ b/Subsurface/Source/GUI/GUITextBox.cs @@ -72,6 +72,12 @@ namespace Barotrauma } } + public Color TextColor + { + get { return textBlock.TextColor; } + set { textBlock.TextColor = value; } + } + public override Color HoverColor { get diff --git a/Subsurface/Source/Items/CharacterInventory.cs b/Subsurface/Source/Items/CharacterInventory.cs index f4267c43f..ab9784bcf 100644 --- a/Subsurface/Source/Items/CharacterInventory.cs +++ b/Subsurface/Source/Items/CharacterInventory.cs @@ -11,7 +11,7 @@ namespace Barotrauma [Flags] public enum LimbSlot { - None = 0, Any = 1, RightHand = 2, LeftHand = 4, Head = 8, Torso = 16, Legs = 32 + None = 0, Any = 1, RightHand = 2, LeftHand = 4, Head = 8, Torso = 16, Legs = 32, Face=64 }; class CharacterInventory : Inventory @@ -21,7 +21,7 @@ namespace Barotrauma private Character character; public static LimbSlot[] limbSlots = new LimbSlot[] { - LimbSlot.Head, LimbSlot.Torso, LimbSlot.Legs, LimbSlot.LeftHand, LimbSlot.RightHand, + LimbSlot.Head, LimbSlot.Torso, LimbSlot.Legs, LimbSlot.LeftHand, LimbSlot.RightHand, LimbSlot.Face, LimbSlot.Any, LimbSlot.Any, LimbSlot.Any, LimbSlot.Any, LimbSlot.Any, LimbSlot.Any, LimbSlot.Any, LimbSlot.Any, LimbSlot.Any, LimbSlot.Any}; @@ -58,7 +58,7 @@ namespace Barotrauma case 3: case 4: SlotPositions[i] = new Vector2( - spacing * 2 + rectWidth + (spacing + rectWidth) * (i - 3), + spacing * 2 + rectWidth + (spacing + rectWidth) * (i - 2), GameMain.GraphicsHeight - (spacing + rectHeight)*3); useOnSelfButton[i - 3] = new GUIButton( @@ -70,11 +70,17 @@ namespace Barotrauma }; + break; + case 5: + SlotPositions[i] = new Vector2( + spacing * 2 + rectWidth + (spacing + rectWidth) * (i - 5), + GameMain.GraphicsHeight - (spacing + rectHeight) * 3); + break; default: SlotPositions[i] = new Vector2( - spacing * 2 + rectWidth + (spacing + rectWidth) * ((i - 3)%5), - GameMain.GraphicsHeight - (spacing + rectHeight) * ((i>9) ? 2 : 1)); + spacing * 2 + rectWidth + (spacing + rectWidth) * ((i - 6)%5), + GameMain.GraphicsHeight - (spacing + rectHeight) * ((i>10) ? 2 : 1)); break; } } @@ -298,6 +304,13 @@ namespace Barotrauma new Vector2(18.0f, 20.0f), Vector2.One, SpriteEffects.None, 0.1f); } + else if (i==5) + { + spriteBatch.Draw(icons, new Vector2(slotRect.Center.X, slotRect.Center.Y), + new Rectangle(57,0,31,32), Color.White * 0.7f, 0.0f, + new Vector2(15.0f, 16.0f), Vector2.One, + SpriteEffects.None, 0.1f); + } } for (int i = 0; i < capacity; i++) @@ -334,7 +347,7 @@ namespace Barotrauma highlightedSlot = slotRect; } - UpdateSlot(spriteBatch, slotRect, i, Items[i], false, i>4 ? 0.2f : 0.4f); + UpdateSlot(spriteBatch, slotRect, i, Items[i], false, i>5 ? 0.2f : 0.4f); if (draggingItem!=null && draggingItem == Items[i]) draggingItemSlot = slotRect; } diff --git a/Subsurface/Source/Items/Components/Signal/WifiComponent.cs b/Subsurface/Source/Items/Components/Signal/WifiComponent.cs index 5211d7d29..554839f23 100644 --- a/Subsurface/Source/Items/Components/Signal/WifiComponent.cs +++ b/Subsurface/Source/Items/Components/Signal/WifiComponent.cs @@ -1,4 +1,6 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; using System.Xml.Linq; @@ -6,11 +8,19 @@ namespace Barotrauma.Items.Components { class WifiComponent : ItemComponent { - private static List list = new List(); + private float range; + private int channel; + [HasDefaultValue(20000.0f, false)] + public float Range + { + get { return range; } + set { range = Math.Max(value, 0.0f); } + } + [InGameEditable, HasDefaultValue(1, true)] public int Channel { @@ -21,24 +31,66 @@ namespace Barotrauma.Items.Components } } + [Editable, HasDefaultValue(false, false)] + public bool LinkToChat + { + get; + set; + } + public WifiComponent(Item item, XElement element) : base (item, element) { list.Add(this); } + + public void Transmit(string signal) + { + if (!HasRequiredContainedItems(true)) return; + + var receivers = GetReceiversInRange(); + foreach (WifiComponent w in receivers) + { + var connections = w.item.Connections; + + w.ReceiveSignal(1, signal, connections == null ? null : connections.Find(c => c.Name == "signal_in"), item); + } + } + + private List GetReceiversInRange() + { + return list.FindAll(w => + w != this && w.channel == channel && + Vector2.Distance(item.WorldPosition, w.item.WorldPosition) <= Range); + } public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item sender, float power=0.0f) { - //prevent an ininite loop of wificomponents sending messages between each other - if (sender.GetComponent()!=null) return; + if (!HasRequiredContainedItems(false)) return; + + if (LinkToChat) + { + if (item.ParentInventory != null && + item.ParentInventory.Owner != null && + item.ParentInventory.Owner == Character.Controlled && + GameMain.NetworkMember != null) + { + signal = ChatMessage.ApplyDistanceEffect(item, sender, signal, range); + + GameMain.NetworkMember.AddChatMessage(signal, ChatMessageType.Radio); + } + } + + if (connection == null) return; switch (connection.Name) { case "signal_in": - foreach (WifiComponent wifiComp in list) + var receivers = GetReceiversInRange(); + + foreach (WifiComponent wifiComp in receivers) { - if (wifiComp == this || wifiComp.channel != channel) continue; wifiComp.item.SendSignal(stepsTaken, signal, "signal_out"); } break; diff --git a/Subsurface/Source/Map/MapEntity.cs b/Subsurface/Source/Map/MapEntity.cs index b195d2c94..8bc363a81 100644 --- a/Subsurface/Source/Map/MapEntity.cs +++ b/Subsurface/Source/Map/MapEntity.cs @@ -213,9 +213,9 @@ namespace Barotrauma if (linkedTo != null) { - foreach (MapEntity e in linkedTo) + for (int i = linkedTo.Count - 1; i >= 0; i-- ) { - e.RemoveLinked(this); + linkedTo[i].RemoveLinked(this); } linkedTo.Clear(); } diff --git a/Subsurface/Source/Networking/ChatMessage.cs b/Subsurface/Source/Networking/ChatMessage.cs new file mode 100644 index 000000000..28c8b33f9 --- /dev/null +++ b/Subsurface/Source/Networking/ChatMessage.cs @@ -0,0 +1,136 @@ +using Barotrauma.Items.Components; +using Barotrauma.Networking.ReliableMessages; +using Lidgren.Network; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Barotrauma.Networking +{ + + enum ChatMessageType + { + Default, Error, Dead, Server, Radio + } + + class ChatMessage + { + public const float SpeakRange = 2000.0f; + + public static Color[] MessageColor = { Color.White, Color.Red, new Color(63, 72, 204), Color.LightGreen, Color.Yellow }; + + public readonly string Text; + + public ChatMessageType Type; + + public readonly Character Sender; + + public readonly string SenderName; + + public Color Color + { + get { return MessageColor[(int)Type]; } + } + + public string TextWithSender + { + get; + private set; + } + + private ChatMessage(string senderName, string text, ChatMessageType type, Character sender) + { + Text = text; + Type = type; + + Sender = sender; + + SenderName = senderName; + + TextWithSender = string.IsNullOrWhiteSpace(senderName) ? text : senderName + ": " + text; + } + + public static ChatMessage Create(string senderName, string text, ChatMessageType type, Character sender) + { + return new ChatMessage(senderName, text, type, sender); + } + + public static string GetChatMessageCommand(string message, out string messageWithoutCommand) + { + messageWithoutCommand = message; + + int separatorIndex = message.IndexOf(";"); + if (separatorIndex == -1) return ""; + + //int colonIndex = message.IndexOf(":"); + + string command = ""; + try + { + command = message.Substring(0, separatorIndex); + command = command.Trim(); + } + + catch + { + return command; + } + + messageWithoutCommand = message.Substring(separatorIndex + 1, message.Length - separatorIndex - 1).TrimStart(); + + return command; + } + + public string ApplyDistanceEffect(Character listener) + { + if (Sender == null) return Text; + + return ApplyDistanceEffect(listener, Sender, Text, SpeakRange); + } + + public static string ApplyDistanceEffect(Entity listener, Entity Sender, string text, float range) + { + if (listener.WorldPosition == Sender.WorldPosition) return text; + + float dist = Vector2.Distance(listener.WorldPosition, Sender.WorldPosition); + if (dist > range) return ""; + + if (Submarine.CheckVisibility(listener.SimPosition, Sender.SimPosition) != null) dist *= 2.0f; + if (dist > range) return ""; + + float garbleAmount = dist / range; + if (garbleAmount < 0.5f) return text; + + int startIndex = Math.Max(text.IndexOf(':') + 1, 1); + + StringBuilder sb = new StringBuilder(text.Length); + for (int i = 0; i < text.Length; i++) + { + sb.Append((i>startIndex && Rand.Range(0.0f, 1.0f) < garbleAmount) ? '-' : text[i]); + } + + return sb.ToString(); + } + + public void WriteNetworkMessage(NetOutgoingMessage msg) + { + msg.WriteRangedInteger(0, Enum.GetValues(typeof(ChatMessageType)).Length, (byte)Type); + msg.Write(Sender == null ? (ushort)0 : Sender.ID); + msg.Write(SenderName); + + msg.Write(Text); + } + + public static ChatMessage ReadNetworkMessage(NetBuffer msg) + { + ChatMessageType type = (ChatMessageType)msg.ReadRangedInteger(0, Enum.GetValues(typeof(ChatMessageType)).Length); + ushort senderId = msg.ReadUInt16(); + string senderName = msg.ReadString(); + string text = msg.ReadString(); + + return new ChatMessage(senderName, text, type, Entity.FindEntityByID(senderId) as Character); + } + } +} diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 42a608e12..48636fcae 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using Barotrauma.Networking.ReliableMessages; using FarseerPhysics; using System.IO; +using System.Linq; +using Barotrauma.Items.Components; namespace Barotrauma.Networking { @@ -555,9 +557,9 @@ namespace Barotrauma.Networking break; case (byte)PacketTypes.Chatmessage: - ChatMessageType messageType = (ChatMessageType)inc.ReadByte(); + //ChatMessageType messageType = (ChatMessageType)inc.ReadByte(); - AddChatMessage(inc.ReadString(), messageType); + AddChatMessage(ChatMessage.ReadNetworkMessage(inc)); break; case (byte)PacketTypes.NetworkEvent: //read the data from the message and update client state accordingly @@ -958,19 +960,33 @@ namespace Barotrauma.Networking return character; } - public override void SendChatMessage(string message, ChatMessageType type = ChatMessageType.Default) + public override void SendChatMessage(string message) { //AddChatMessage(message); if (client.ServerConnection == null) return; - type = (Screen.Selected == GameMain.GameScreen && - (myCharacter == null || myCharacter.IsDead)) ? ChatMessageType.Dead : ChatMessageType.Default; + var type = ChatMessageType.Default; + + if (Screen.Selected == GameMain.GameScreen && (myCharacter == null || myCharacter.IsDead)) + { + type = ChatMessageType.Dead; + } + else + { + string command = ChatMessage.GetChatMessageCommand(message, out message).ToLower(); + + if (CanUseRadio(Character.Controlled)) type = ChatMessageType.Radio; + } + + var chatMessage = ChatMessage.Create( + gameStarted && myCharacter != null ? myCharacter.Name : name, + message, type, gameStarted ? myCharacter : null); + ReliableMessage msg = reliableChannel.CreateMessage(); msg.InnerMessage.Write((byte)PacketTypes.Chatmessage); - msg.InnerMessage.Write((byte)type); - msg.InnerMessage.Write(message); + chatMessage.WriteNetworkMessage(msg.InnerMessage); reliableChannel.SendMessage(msg, client.ServerConnection); } diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index cc142820e..7c260205e 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -7,6 +7,7 @@ using Lidgren.Network; using Microsoft.Xna.Framework; using RestSharp; using Barotrauma.Networking.ReliableMessages; +using Barotrauma.Items.Components; namespace Barotrauma.Networking { @@ -503,9 +504,10 @@ namespace Barotrauma.Networking break; case (byte)PacketTypes.Chatmessage: - ChatMessageType messageType = (ChatMessageType)inc.ReadByte(); + //SendChatMessage(ChatMessage.ReadNetworkMessage(inc)); + //!!!!!!!!!!! - SendChatMessage(inc.ReadString(), messageType); + ReadChatMessage(inc); break; case (byte)PacketTypes.PlayerLeft: @@ -953,7 +955,8 @@ namespace Barotrauma.Networking GameMain.GameScreen.Select(); - AddChatMessage("Press TAB to chat. Use ''d;'' to talk to dead players and spectators, and ''player name;'' to only send the message to a specific player.", ChatMessageType.Server); + AddChatMessage("Press TAB to chat. Use ''d;'' to talk to dead players and spectators, " + +"and ''player name;'' to only send the message to a specific player.", ChatMessageType.Server); yield return CoroutineStatus.Success; } @@ -1081,7 +1084,7 @@ namespace Barotrauma.Networking if (string.IsNullOrWhiteSpace(msg)) msg = client.name + " has left the server"; if (string.IsNullOrWhiteSpace(targetmsg)) targetmsg = "You have left the server"; - Log(msg, messageColor[(int)ChatMessageType.Server]); + Log(msg, ChatMessage.MessageColor[(int)ChatMessageType.Server]); NetOutgoingMessage outmsg = server.CreateMessage(); outmsg.Write((byte)PacketTypes.KickedOut); @@ -1370,33 +1373,79 @@ namespace Barotrauma.Networking } return true; - } - public override void SendChatMessage(string message, ChatMessageType type = ChatMessageType.Server) + private void ReadChatMessage(NetIncomingMessage inc) + { + ChatMessage message = ChatMessage.ReadNetworkMessage(inc); + + List recipients = new List(); + + foreach (Client c in ConnectedClients) + { + switch (message.Type) + { + case ChatMessageType.Dead: + if (c.Character != null && !c.Character.IsDead) continue; + break; + case ChatMessageType.Default: + if (message.Sender != null && c.Character != null && message.Sender != c.Character) + { + if (Vector2.Distance(message.Sender.WorldPosition, c.Character.WorldPosition) > ChatMessage.SpeakRange) continue; + } + break; + case ChatMessageType.Radio: + if (message.Sender == null) return; + var radio = message.Sender.Inventory.Items.First(i => i != null && i.GetComponent() != null); + if (radio == null) message.Type = ChatMessageType.Default; + break; + } + + recipients.Add(c); + } + + AddChatMessage(message); + + foreach (Client c in recipients) + { + ReliableMessage msg = c.ReliableChannel.CreateMessage(); + msg.InnerMessage.Write((byte)PacketTypes.Chatmessage); + //msg.InnerMessage.Write((byte)type); + //msg.InnerMessage.Write(message); + + message.WriteNetworkMessage(msg.InnerMessage); + + c.ReliableChannel.SendMessage(msg, c.Connection); + } + } + + public override void SendChatMessage(string message) { List recipients = new List(); Client targetClient = null; - if (type == ChatMessageType.Server) + ChatMessageType type = gameStarted && myCharacter != null ? ChatMessageType.Default : ChatMessageType.Server; + + string command = ChatMessage.GetChatMessageCommand(message, out message).ToLower(); + + if (command=="dead" || command=="d") { - string command = GetChatMessageCommand(message).ToLower(); + type = ChatMessageType.Dead; + } + else if (command=="radio" || command=="r") + { + if (CanUseRadio(Character.Controlled)) type = ChatMessageType.Radio; + } + else if (command != "") + { + targetClient = ConnectedClients.Find(c => + command == c.name.ToLower() || + c.Character != null && command == c.Character.Name.ToLower()); - if (command=="dead" || command=="d") + if (targetClient == null) { - type = ChatMessageType.Dead; - } - else if (command != "") - { - targetClient = ConnectedClients.Find(c => - command == c.name.ToLower() || - c.Character != null && command == c.Character.Name.ToLower()); - - if (targetClient == null) - { - AddChatMessage("Player ''" + command + "'' not found!", ChatMessageType.Admin); - return; - } + AddChatMessage("Player ''" + command + "'' not found!", ChatMessageType.Error); + return; } } @@ -1411,8 +1460,12 @@ namespace Barotrauma.Networking if (type != ChatMessageType.Dead || (c.Character == null || c.Character.IsDead)) recipients.Add(c); } } - - AddChatMessage(message, type); + + var chatMessage = ChatMessage.Create( + gameStarted && myCharacter != null ? myCharacter.Name : name, + message, type, gameStarted ? myCharacter : null); + + AddChatMessage(chatMessage); if (!server.Connections.Any()) return; @@ -1420,8 +1473,10 @@ namespace Barotrauma.Networking { ReliableMessage msg = c.ReliableChannel.CreateMessage(); msg.InnerMessage.Write((byte)PacketTypes.Chatmessage); - msg.InnerMessage.Write((byte)type); - msg.InnerMessage.Write(message); + //msg.InnerMessage.Write((byte)type); + //msg.InnerMessage.Write(message); + + chatMessage.WriteNetworkMessage(msg.InnerMessage); c.ReliableChannel.SendMessage(msg, c.Connection); } diff --git a/Subsurface/Source/Networking/NetworkMember.cs b/Subsurface/Source/Networking/NetworkMember.cs index 950385fb7..307f70a56 100644 --- a/Subsurface/Source/Networking/NetworkMember.cs +++ b/Subsurface/Source/Networking/NetworkMember.cs @@ -1,9 +1,11 @@ using System; +using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System.Collections.Generic; using Lidgren.Network; +using Barotrauma.Items.Components; namespace Barotrauma.Networking { @@ -40,11 +42,6 @@ namespace Barotrauma.Networking SpectateRequest } - enum ChatMessageType - { - Default, Admin, Dead, Server - } - enum VoteType { Unknown, @@ -55,9 +52,6 @@ namespace Barotrauma.Networking class NetworkMember { - - protected static Color[] messageColor = { Color.White, Color.Red, new Color(63,72,204), Color.LightGreen }; - protected NetPeer netPeer; protected string name; @@ -139,6 +133,7 @@ namespace Barotrauma.Networking chatMsgBox.Font = GUI.SmallFont; chatMsgBox.Padding = Vector4.Zero; chatMsgBox.OnEnterPressed = EnterChatMessage; + chatMsgBox.OnTextChanged = TypingChatMessage; Voting = new Voting(); } @@ -183,39 +178,98 @@ namespace Barotrauma.Networking return message; } + public bool TypingChatMessage(GUITextBox textBox, string text) + { + string tempStr; + string command = ChatMessage.GetChatMessageCommand(text, out tempStr); + switch (command) + { + case "r": + case "radio": + textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio]; + break; + case "d": + case "dead": + textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead]; + break; + default: + textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default]; + break; + } + + return true; + } + + public bool CanUseRadio(Character sender) + { + if (sender == null) return false; + + var radio = sender.Inventory.Items.First(i => i != null && i.GetComponent() != null); + if (radio == null) return false; + + var radioComponent = radio.GetComponent(); + return radioComponent.HasRequiredContainedItems(false); + } + public bool EnterChatMessage(GUITextBox textBox, string message) { + textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default]; + if (string.IsNullOrWhiteSpace(message)) return false; - string senderName = gameStarted && characterInfo != null ? characterInfo.Name : name; - - SendChatMessage(senderName + ": " + message); + SendChatMessage(message); textBox.Deselect(); return true; } - - public void AddChatMessage(string message, ChatMessageType messageType) - { - GameMain.NetLobbyScreen.NewChatMessage(message, messageColor[(int)messageType]); - GameServer.Log(message, messageColor[(int)messageType]); + public void AddChatMessage(string message, ChatMessageType type, string senderName="", Character senderCharacter = null) + { + AddChatMessage(ChatMessage.Create(senderName, message, type, senderCharacter)); + } + + public void AddChatMessage(ChatMessage message) + { + if (message.Type == ChatMessageType.Radio && message.Sender != null && message.Sender != myCharacter) + { + var radio = message.Sender.Inventory.Items.First(i => i != null && i.GetComponent() != null); + if (radio == null) return; + + var radioComponent = radio.GetComponent(); + radioComponent.Transmit(message.TextWithSender); + return; + } + + string displayedText = message.Text; + + if (message.Type == ChatMessageType.Default && myCharacter != null && message.Sender != null) + { + displayedText = message.ApplyDistanceEffect(myCharacter); + if (string.IsNullOrWhiteSpace(displayedText)) return; + } + + GameMain.NetLobbyScreen.NewChatMessage(message); + + GameServer.Log(message.Text, message.Color); while (chatBox.CountChildren > 20) { chatBox.RemoveChild(chatBox.children[1]); } - GUITextBlock msg = new GUITextBlock(new Rectangle(0, 0, 0, 20), message, - ((chatBox.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f, messageColor[(int)messageType], + if (!string.IsNullOrWhiteSpace(message.SenderName)) + { + displayedText = message.SenderName + ": " + displayedText; + } + + GUITextBlock msg = new GUITextBlock(new Rectangle(0, 0, 0, 20), displayedText, + ((chatBox.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f, message.Color, Alignment.Left, null, null, true); msg.Font = GUI.SmallFont; msg.Padding = new Vector4(20.0f, 0, 0, 0); - //float prevScroll = chatBox.BarScroll; - float prevSize = chatBox.BarSize; msg.Padding = new Vector4(20, 0, 0, 0); @@ -223,29 +277,21 @@ namespace Barotrauma.Networking if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) chatBox.BarScroll = 1.0f; - GUI.PlayUISound(GUISoundType.Message); - } - - public virtual void SendChatMessage(string message, ChatMessageType type = ChatMessageType.Server) { } - - protected string GetChatMessageCommand(string message) - { - int separatorIndex = message.IndexOf(";"); - if (separatorIndex == -1) return ""; - - int colonIndex = message.IndexOf(":"); - - string command = ""; - try + GUISoundType soundType = GUISoundType.Message; + if (message.Type == ChatMessageType.Radio) { - command = message.Substring(colonIndex + 2, separatorIndex - colonIndex - 2); + soundType = GUISoundType.RadioMessage; + } + else if (message.Type == ChatMessageType.Dead) + { + soundType = GUISoundType.DeadMessage; } - catch { } - - return command; + GUI.PlayUISound(soundType); } + public virtual void SendChatMessage(string message) { } + public virtual void Update(float deltaTime) { if (gameStarted && Screen.Selected == GameMain.GameScreen) diff --git a/Subsurface/Source/Screens/NetLobbyScreen.cs b/Subsurface/Source/Screens/NetLobbyScreen.cs index f8d0e42dc..187f6d2b2 100644 --- a/Subsurface/Source/Screens/NetLobbyScreen.cs +++ b/Subsurface/Source/Screens/NetLobbyScreen.cs @@ -157,7 +157,6 @@ namespace Barotrauma chatBox = new GUIListBox(new Rectangle(0,0,0,chatFrame.Rect.Height-80), Color.White, GUI.Style, chatFrame); textBox = new GUITextBox(new Rectangle(0, 25, 0, 25), Alignment.Bottom, GUI.Style, chatFrame); textBox.Font = GUI.SmallFont; - textBox.OnEnterPressed = EnterChatMessage; //player info panel ------------------------------------------------------------ @@ -292,6 +291,10 @@ namespace Barotrauma textBox.Select(); + + textBox.OnEnterPressed = GameMain.NetworkMember.EnterChatMessage; + textBox.OnTextChanged = GameMain.NetworkMember.TypingChatMessage; + Character.Controlled = null; //GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; @@ -753,7 +756,7 @@ namespace Barotrauma spriteBatch.End(); } - public void NewChatMessage(string message, Color color) + public void NewChatMessage(ChatMessage message) { float prevSize = chatBox.BarSize; @@ -763,8 +766,8 @@ namespace Barotrauma } GUITextBlock msg = new GUITextBlock(new Rectangle(0, 0, 0, 20), - message, - ((chatBox.CountChildren % 2) == 0) ? Color.Transparent : Color.Black*0.1f, color, + message.TextWithSender, + ((chatBox.CountChildren % 2) == 0) ? Color.Transparent : Color.Black*0.1f, message.Color, Alignment.Left, GUI.Style, null, true); msg.Font = GUI.SmallFont; msg.CanBeFocused = false; @@ -775,14 +778,14 @@ namespace Barotrauma if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) chatBox.BarScroll = 1.0f; } - public bool EnterChatMessage(GUITextBox textBox, string message) - { - if (String.IsNullOrEmpty(message)) return false; + //public bool EnterChatMessage(GUITextBox textBox, string message) + //{ + // if (String.IsNullOrEmpty(message)) return false; - GameMain.NetworkMember.SendChatMessage(GameMain.NetworkMember.Name + ": " + message); + // GameMain.NetworkMember.SendChatMessage(ChatMessage.Create(message, ChatMessageType.Default, null)); - return true; - } + // return true; + //} private void UpdatePreviewPlayer(CharacterInfo characterInfo) {