b48aa89004
commit e6715d605db9bb1d608e4a4990ac41f6214f61d1 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Tue Mar 5 17:22:31 2019 +0200 Fixed the "control" console command not being usable by clients, changed the way arguments are given to the "setclientcharacter" command (no semicolon to separate the names, quotation marks have to be used for multi-word names just like with any other command). Closes #1224 commit acb7c1e0dc05bf619e7ec4875196cc45647d3fb4 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Tue Mar 5 16:30:38 2019 +0200 Steam Workshop fixes: - Install content packages for items that only contain a sub. Otherwise the system can't determine if the item has been updated (and might also be useful for toggling off incompatible subs when switching from mod to another). - Log an error instead of crashing the game if CheckWorkshopItemEnabled or CheckWorkshopItemUpToDate fails due to a missing filelist.xml. - Show a messagebox when a workshop item is updated succesfully. commit b2e8ed565bc03f466930799268c13b2fca4bb9c9 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Tue Mar 5 14:43:42 2019 +0200 Fixed nullref exception when disabling a workshop item that doesn't have an update button (or when enabling the item fails) commit 26f1f285cd80ca6f023b12e6dd80dc71e87ee9c3 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Tue Mar 5 14:15:14 2019 +0200 Fixed console command aliases not being taken into account in GameClient.HasConsoleCommandPermission (meaning that the client needed a permission for each name variant of a command, making it impossible to for example use "fixwalls" instead of "fixhulls"). Closes #1225 commit dee02de681a212efd0e0a82c14619f3fe4839cc4 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Tue Mar 5 13:35:48 2019 +0200 Fixed traitor rounds failing to start if there's no owner client, fixed occasional "traitorCount somehow ended up less than 1" errors due to Rand.Int using 0 as the minimum value. Closes #1217
555 lines
20 KiB
C#
555 lines
20 KiB
C#
using Barotrauma.Networking;
|
|
using Barotrauma.Sounds;
|
|
using Lidgren.Network;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Barotrauma.Items.Components
|
|
{
|
|
enum SoundSelectionMode
|
|
{
|
|
Random,
|
|
CharacterSpecific,
|
|
ItemSpecific,
|
|
All
|
|
}
|
|
|
|
class ItemSound
|
|
{
|
|
public readonly RoundSound RoundSound;
|
|
public readonly ActionType Type;
|
|
|
|
public string VolumeProperty;
|
|
public float VolumeMultiplier
|
|
{
|
|
get { return RoundSound.Volume; }
|
|
}
|
|
|
|
public float Range
|
|
{
|
|
get { return RoundSound.Range; }
|
|
}
|
|
|
|
public float VolumeMultiplier
|
|
{
|
|
get { return RoundSound.Volume; }
|
|
}
|
|
|
|
public float Range
|
|
{
|
|
get { return RoundSound.Range; }
|
|
}
|
|
|
|
public readonly bool Loop;
|
|
|
|
public ItemSound(RoundSound sound, ActionType type, bool loop = false)
|
|
{
|
|
this.RoundSound = sound;
|
|
this.Type = type;
|
|
this.Loop = loop;
|
|
}
|
|
}
|
|
|
|
partial class ItemComponent : ISerializableEntity
|
|
{
|
|
private Dictionary<ActionType, List<ItemSound>> sounds;
|
|
private Dictionary<ActionType, SoundSelectionMode> soundSelectionModes;
|
|
|
|
public GUILayoutSettings DefaultLayout { get; protected set; }
|
|
public GUILayoutSettings AlternativeLayout { get; protected set; }
|
|
|
|
public class GUILayoutSettings
|
|
{
|
|
public Vector2? RelativeSize { get; private set; }
|
|
public Point? AbsoluteSize { get; private set; }
|
|
public Vector2? RelativeOffset { get; private set; }
|
|
public Point? AbsoluteOffset { get; private set; }
|
|
public Anchor? Anchor { get; private set; }
|
|
public Pivot? Pivot { get; private set; }
|
|
|
|
public static GUILayoutSettings Load(XElement element)
|
|
{
|
|
var layout = new GUILayoutSettings();
|
|
var relativeSize = XMLExtensions.GetAttributeVector2(element, "relativesize", Vector2.Zero);
|
|
var absoluteSize = XMLExtensions.GetAttributePoint(element, "absolutesize", new Point(-1000, -1000));
|
|
var relativeOffset = XMLExtensions.GetAttributeVector2(element, "relativeoffset", Vector2.Zero);
|
|
var absoluteOffset = XMLExtensions.GetAttributePoint(element, "absoluteoffset", new Point(-1000, -1000));
|
|
if (relativeSize.Length() > 0)
|
|
{
|
|
layout.RelativeSize = relativeSize;
|
|
}
|
|
if (absoluteSize.X > 0 && absoluteSize.Y > 0)
|
|
{
|
|
layout.AbsoluteSize = absoluteSize;
|
|
}
|
|
if (relativeOffset.Length() > 0)
|
|
{
|
|
layout.RelativeOffset = relativeOffset;
|
|
}
|
|
if (absoluteOffset.X > -1000 && absoluteOffset.Y > -1000)
|
|
{
|
|
layout.AbsoluteOffset = absoluteOffset;
|
|
}
|
|
if (Enum.TryParse(XMLExtensions.GetAttributeString(element, "anchor", ""), out Anchor a))
|
|
{
|
|
layout.Anchor = a;
|
|
}
|
|
if (Enum.TryParse(XMLExtensions.GetAttributeString(element, "pivot", ""), out Pivot p))
|
|
{
|
|
layout.Pivot = p;
|
|
}
|
|
return layout;
|
|
}
|
|
|
|
public void ApplyTo(RectTransform target)
|
|
{
|
|
if (RelativeOffset.HasValue)
|
|
{
|
|
target.RelativeOffset = RelativeOffset.Value;
|
|
}
|
|
else if (AbsoluteOffset.HasValue)
|
|
{
|
|
target.AbsoluteOffset = AbsoluteOffset.Value;
|
|
}
|
|
if (RelativeSize.HasValue)
|
|
{
|
|
target.RelativeSize = RelativeSize.Value;
|
|
}
|
|
else if (AbsoluteSize.HasValue)
|
|
{
|
|
target.NonScaledSize = AbsoluteSize.Value;
|
|
}
|
|
if (Anchor.HasValue)
|
|
{
|
|
target.Anchor = Anchor.Value;
|
|
}
|
|
if (Pivot.HasValue)
|
|
{
|
|
target.Pivot = Pivot.Value;
|
|
}
|
|
else
|
|
{
|
|
target.Pivot = RectTransform.MatchPivotToAnchor(target.Anchor);
|
|
}
|
|
target.RecalculateChildren(true, true);
|
|
}
|
|
}
|
|
|
|
public GUIFrame GuiFrame { get; protected set; }
|
|
|
|
[Serialize(false, false)]
|
|
public bool AllowUIOverlap
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
private ItemComponent linkToUIComponent;
|
|
[Serialize("", false)]
|
|
public string LinkUIToComponent
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(0, false)]
|
|
public int HudPriority
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
private bool useAlternativeLayout;
|
|
public bool UseAlternativeLayout
|
|
{
|
|
get { return useAlternativeLayout; }
|
|
set
|
|
{
|
|
if (AlternativeLayout != null)
|
|
{
|
|
if (value == useAlternativeLayout) { return; }
|
|
useAlternativeLayout = value;
|
|
if (useAlternativeLayout)
|
|
{
|
|
AlternativeLayout?.ApplyTo(GuiFrame.RectTransform);
|
|
}
|
|
else
|
|
{
|
|
DefaultLayout?.ApplyTo(GuiFrame.RectTransform);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ApplyTo(RectTransform target)
|
|
{
|
|
if (RelativeOffset.HasValue)
|
|
{
|
|
target.RelativeOffset = RelativeOffset.Value;
|
|
}
|
|
else if (AbsoluteOffset.HasValue)
|
|
{
|
|
target.AbsoluteOffset = AbsoluteOffset.Value;
|
|
}
|
|
if (RelativeSize.HasValue)
|
|
{
|
|
target.RelativeSize = RelativeSize.Value;
|
|
}
|
|
else if (AbsoluteSize.HasValue)
|
|
{
|
|
target.NonScaledSize = AbsoluteSize.Value;
|
|
}
|
|
if (Anchor.HasValue)
|
|
{
|
|
target.Anchor = Anchor.Value;
|
|
}
|
|
if (Pivot.HasValue)
|
|
{
|
|
target.Pivot = Pivot.Value;
|
|
}
|
|
else
|
|
{
|
|
target.Pivot = RectTransform.MatchPivotToAnchor(target.Anchor);
|
|
}
|
|
target.RecalculateChildren(true, true);
|
|
}
|
|
}
|
|
|
|
public GUIFrame GuiFrame { get; protected set; }
|
|
|
|
[Serialize(false, false)]
|
|
public bool AllowUIOverlap
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
private ItemComponent linkToUIComponent;
|
|
[Serialize("", false)]
|
|
public string LinkUIToComponent
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(0, false)]
|
|
public int HudPriority
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
private bool shouldMuffleLooping;
|
|
private float lastMuffleCheckTime;
|
|
private ItemSound loopingSound;
|
|
private SoundChannel loopingSoundChannel;
|
|
public void PlaySound(ActionType type, Vector2 position, Character user = null)
|
|
{
|
|
if (loopingSound != null)
|
|
{
|
|
if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(position.X, position.Y, 0.0f)) > loopingSound.Range * loopingSound.Range)
|
|
{
|
|
if (loopingSoundChannel != null)
|
|
{
|
|
loopingSoundChannel.Dispose(); loopingSoundChannel = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound)
|
|
{
|
|
loopingSoundChannel.Dispose(); loopingSoundChannel = null;
|
|
}
|
|
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
|
|
{
|
|
loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
|
|
new Vector3(position.X, position.Y, 0.0f),
|
|
GetSoundVolume(loopingSound),
|
|
SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull));
|
|
loopingSoundChannel.Looping = true;
|
|
//TODO: tweak
|
|
loopingSoundChannel.Near = loopingSound.Range * 0.4f;
|
|
loopingSoundChannel.Far = loopingSound.Range;
|
|
}
|
|
if (loopingSoundChannel != null)
|
|
{
|
|
if (Timing.TotalTime > lastMuffleCheckTime + 0.2f)
|
|
{
|
|
shouldMuffleLooping = SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull);
|
|
lastMuffleCheckTime = (float)Timing.TotalTime;
|
|
}
|
|
loopingSoundChannel.Muffled = shouldMuffleLooping;
|
|
loopingSoundChannel.Gain = GetSoundVolume(loopingSound);
|
|
loopingSoundChannel.Position = new Vector3(position.X, position.Y, 0.0f);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!sounds.TryGetValue(type, out List<ItemSound> matchingSounds)) return;
|
|
|
|
ItemSound itemSound = null;
|
|
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
|
|
{
|
|
SoundSelectionMode soundSelectionMode = soundSelectionModes[type];
|
|
int index;
|
|
if (soundSelectionMode == SoundSelectionMode.CharacterSpecific && user != null)
|
|
{
|
|
index = user.ID % matchingSounds.Count;
|
|
}
|
|
else if (soundSelectionMode == SoundSelectionMode.ItemSpecific)
|
|
{
|
|
index = item.ID % matchingSounds.Count;
|
|
}
|
|
else if (soundSelectionMode == SoundSelectionMode.All)
|
|
{
|
|
foreach (ItemSound sound in matchingSounds)
|
|
{
|
|
PlaySound(sound, position, user);
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
index = Rand.Int(matchingSounds.Count);
|
|
}
|
|
|
|
itemSound = matchingSounds[index];
|
|
PlaySound(matchingSounds[index], position, user);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
private void PlaySound(ItemSound itemSound, Vector2 position, Character user = null)
|
|
{
|
|
if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(position.X, position.Y, 0.0f)) > itemSound.Range * itemSound.Range)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (itemSound.Loop)
|
|
{
|
|
loopingSound = itemSound;
|
|
if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound)
|
|
{
|
|
loopingSoundChannel.Dispose(); loopingSoundChannel = null;
|
|
}
|
|
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
|
|
{
|
|
loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
|
|
new Vector3(position.X, position.Y, 0.0f),
|
|
GetSoundVolume(loopingSound),
|
|
muffle: SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull));
|
|
loopingSoundChannel.Looping = true;
|
|
//TODO: tweak
|
|
loopingSoundChannel.Near = loopingSound.Range * 0.4f;
|
|
loopingSoundChannel.Far = loopingSound.Range;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float volume = GetSoundVolume(itemSound);
|
|
if (volume <= 0.0f) return;
|
|
SoundPlayer.PlaySound(itemSound.RoundSound.Sound, volume, itemSound.Range, position, item.CurrentHull);
|
|
}
|
|
}
|
|
|
|
public void StopSounds(ActionType type)
|
|
{
|
|
if (loopingSound == null) return;
|
|
|
|
if (loopingSound.Type != type) return;
|
|
|
|
if (loopingSoundChannel != null)
|
|
{
|
|
loopingSoundChannel.Dispose();
|
|
loopingSoundChannel = null;
|
|
loopingSound = null;
|
|
}
|
|
}
|
|
|
|
private float GetSoundVolume(ItemSound sound)
|
|
{
|
|
if (sound == null) { return 0.0f; }
|
|
if (sound.VolumeProperty == "") { return sound.VolumeMultiplier; }
|
|
|
|
if (properties.TryGetValue(sound.VolumeProperty, out SerializableProperty property))
|
|
{
|
|
float newVolume = 0.0f;
|
|
try
|
|
{
|
|
newVolume = (float)property.GetValue(this);
|
|
}
|
|
catch
|
|
{
|
|
return 0.0f;
|
|
}
|
|
newVolume *= sound.VolumeMultiplier;
|
|
|
|
if (!MathUtils.IsValid(newVolume))
|
|
{
|
|
DebugConsole.Log("Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume);
|
|
GameAnalyticsManager.AddErrorEventOnce(
|
|
"ItemComponent.PlaySound:" + item.Name + GetType().ToString(),
|
|
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
|
"Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume);
|
|
return 0.0f;
|
|
}
|
|
|
|
return MathHelper.Clamp(newVolume, 0.0f, 1.0f);
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
public virtual bool ShouldDrawHUD(Character character)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
public ItemComponent GetLinkUIToComponent()
|
|
{
|
|
if (string.IsNullOrEmpty(LinkUIToComponent))
|
|
{
|
|
return null;
|
|
}
|
|
foreach (ItemComponent component in item.Components)
|
|
{
|
|
if (component.name.ToLower() == LinkUIToComponent.ToLower())
|
|
{
|
|
linkToUIComponent = component;
|
|
}
|
|
}
|
|
if (linkToUIComponent == null)
|
|
{
|
|
DebugConsole.ThrowError("Failed to link the component \"" + Name + "\" to \"" + LinkUIToComponent + "\" in the item \"" + item.Name + "\" - component with a matching name not found.");
|
|
}
|
|
return linkToUIComponent;
|
|
}
|
|
|
|
public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { }
|
|
|
|
public virtual void AddToGUIUpdateList()
|
|
{
|
|
GuiFrame?.AddToGUIUpdateList();
|
|
}
|
|
|
|
public virtual void UpdateHUD(Character character, float deltaTime, Camera cam) { }
|
|
|
|
private bool LoadElemProjSpecific(XElement subElement)
|
|
{
|
|
switch (subElement.Name.ToString().ToLowerInvariant())
|
|
{
|
|
case "guiframe":
|
|
if (subElement.Attribute("rect") != null)
|
|
{
|
|
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - GUIFrame defined as rect, use RectTransform instead.");
|
|
break;
|
|
}
|
|
|
|
Color? color = null;
|
|
if (subElement.Attribute("color") != null) color = subElement.GetAttributeColor("color", Color.White);
|
|
string style = subElement.Attribute("style") == null ?
|
|
null : subElement.GetAttributeString("style", "");
|
|
|
|
GuiFrame = new GUIFrame(RectTransform.Load(subElement, GUI.Canvas), style, color);
|
|
DefaultLayout = GUILayoutSettings.Load(subElement);
|
|
break;
|
|
case "alternativelayout":
|
|
AlternativeLayout = GUILayoutSettings.Load(subElement);
|
|
break;
|
|
case "itemsound":
|
|
case "sound":
|
|
string filePath = subElement.GetAttributeString("file", "");
|
|
|
|
if (filePath == "") filePath = subElement.GetAttributeString("sound", "");
|
|
|
|
if (filePath == "")
|
|
{
|
|
DebugConsole.ThrowError("Error when instantiating item \"" + item.Name + "\" - sound with no file path set");
|
|
break;
|
|
}
|
|
|
|
if (!filePath.Contains("/") && !filePath.Contains("\\") && !filePath.Contains(Path.DirectorySeparatorChar))
|
|
{
|
|
filePath = Path.Combine(Path.GetDirectoryName(item.Prefab.ConfigFile), filePath);
|
|
}
|
|
|
|
ActionType type;
|
|
try
|
|
{
|
|
type = (ActionType)Enum.Parse(typeof(ActionType), subElement.GetAttributeString("type", ""), true);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
DebugConsole.ThrowError("Invalid sound type in " + subElement + "!", e);
|
|
break;
|
|
}
|
|
|
|
RoundSound sound = Submarine.LoadRoundSound(subElement);
|
|
if (sound == null) { break; }
|
|
ItemSound itemSound = new ItemSound(sound, type, subElement.GetAttributeBool("loop", false))
|
|
{
|
|
VolumeProperty = subElement.GetAttributeString("volumeproperty", "").ToLowerInvariant()
|
|
};
|
|
|
|
if (soundSelectionModes == null) soundSelectionModes = new Dictionary<ActionType, SoundSelectionMode>();
|
|
if (!soundSelectionModes.ContainsKey(type) || soundSelectionModes[type] == SoundSelectionMode.Random)
|
|
{
|
|
SoundSelectionMode selectionMode = SoundSelectionMode.Random;
|
|
Enum.TryParse(subElement.GetAttributeString("selectionmode", "Random"), out selectionMode);
|
|
soundSelectionModes[type] = selectionMode;
|
|
}
|
|
|
|
List<ItemSound> soundList = null;
|
|
if (!sounds.TryGetValue(itemSound.Type, out soundList))
|
|
{
|
|
soundList = new List<ItemSound>();
|
|
sounds.Add(itemSound.Type, soundList);
|
|
}
|
|
|
|
soundList.Add(itemSound);
|
|
break;
|
|
default:
|
|
return false; //unknown element
|
|
}
|
|
return true; //element processed
|
|
}
|
|
|
|
//Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
|
|
protected void StartDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime, bool waitForMidRoundSync = false)
|
|
{
|
|
if (delayedCorrectionCoroutine != null) CoroutineManager.StopCoroutines(delayedCorrectionCoroutine);
|
|
|
|
delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime, waitForMidRoundSync));
|
|
}
|
|
|
|
private IEnumerable<object> DoDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime, bool waitForMidRoundSync)
|
|
{
|
|
while (GameMain.Client != null &&
|
|
(correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing)))
|
|
{
|
|
correctionTimer -= CoroutineManager.DeltaTime;
|
|
yield return CoroutineStatus.Running;
|
|
}
|
|
|
|
if (item.Removed || GameMain.Client == null)
|
|
{
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
((IServerSerializable)this).ClientRead(type, buffer, sendingTime);
|
|
|
|
correctionTimer = 0.0f;
|
|
delayedCorrectionCoroutine = null;
|
|
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
}
|
|
}
|