Unstable v0.10.600.0

This commit is contained in:
Juan Pablo Arce
2020-10-01 12:19:24 -03:00
parent 20a69375ca
commit ebe1ce1427
217 changed files with 4284 additions and 1547 deletions
@@ -528,19 +528,19 @@ namespace Barotrauma
{
msg.Write(true);
msg.Write((byte)Order.PrefabList.IndexOf(info.CurrentOrder.Prefab));
msg.Write(info.CurrentOrder.TargetEntity == null ? (UInt16)0 :
info.CurrentOrder.TargetEntity.ID);
if (info.CurrentOrder.OrderGiver != null)
msg.Write(info.CurrentOrder.TargetEntity == null ? (UInt16)0 : info.CurrentOrder.TargetEntity.ID);
var hasOrderGiver = info.CurrentOrder.OrderGiver != null;
msg.Write(hasOrderGiver);
if (hasOrderGiver) { msg.Write(info.CurrentOrder.OrderGiver.ID); }
msg.Write((byte)(string.IsNullOrWhiteSpace(info.CurrentOrderOption) ? 0 : Array.IndexOf(info.CurrentOrder.Prefab.Options, info.CurrentOrderOption)));
var hasTargetPosition = info.CurrentOrder.TargetPosition != null;
msg.Write(hasTargetPosition);
if (hasTargetPosition)
{
msg.Write(true);
msg.Write(info.CurrentOrder.OrderGiver.ID);
msg.Write(info.CurrentOrder.TargetPosition.Position.X);
msg.Write(info.CurrentOrder.TargetPosition.Position.Y);
msg.Write(info.CurrentOrder.TargetPosition.Hull == null ? (UInt16)0 : info.CurrentOrder.TargetPosition.Hull.ID);
}
else
{
msg.Write(false);
}
msg.Write((byte)(string.IsNullOrWhiteSpace(info.CurrentOrderOption) ? 0 :
Array.IndexOf(info.CurrentOrder.Prefab.Options, info.CurrentOrderOption)));
}
else
{
@@ -247,7 +247,7 @@ namespace Barotrauma
catch (Exception e)
{
string errorMsg = "Failed to write input to command line (window width: " + Console.WindowWidth + ", window height: " + Console.WindowHeight + ")\n"
+ e.Message + "\n" + e.StackTrace;
+ e.Message + "\n" + e.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce("DebugConsole.RewriteInputToCommandLine", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
}
@@ -1352,7 +1352,7 @@ namespace Barotrauma
ServerEntityEvent ev = GameMain.Server.EntityEventManager.Events[Convert.ToUInt16(args[0])];
if (ev != null)
{
NewMessage(ev.StackTrace, Color.Lime);
NewMessage(ev.StackTrace.CleanupStackTrace(), Color.Lime);
}
}));
@@ -694,6 +694,10 @@ namespace Barotrauma
}
pendingHireInfos.Add(match);
if (pendingHireInfos.Count + CrewManager.CharacterInfos.Count() >= CrewManager.MaxCrewSize)
{
break;
}
}
location.HireManager.PendingHires = pendingHireInfos;
}
@@ -67,8 +67,26 @@ namespace Barotrauma.Items.Components
if (!CheckCharacterSuccess(c.Character))
{
item.CreateServerEvent(this);
c.Character.SelectedItems[0]?.GetComponent<Wire>()?.CreateNetworkEvent();
c.Character.SelectedItems[1]?.GetComponent<Wire>()?.CreateNetworkEvent();
c.Character.Inventory?.CreateNetworkEvent();
for (int i = 0; i < 2; i++)
{
var selectedWire = c.Character.SelectedItems[i]?.GetComponent<Wire>();
if (selectedWire == null) { continue; }
selectedWire.CreateNetworkEvent();
var panel1 = selectedWire.Connections[0]?.ConnectionPanel;
if (panel1 != null && panel1 != this) { panel1.item.CreateServerEvent(panel1); }
var panel2 = selectedWire.Connections[1]?.ConnectionPanel;
if (panel2 != null && panel2 != this) { panel2.item.CreateServerEvent(panel2); }
CoroutineManager.InvokeAfter(() =>
{
item.CreateServerEvent(this);
if (panel1 != null && panel1 != this) { panel1.item.CreateServerEvent(panel1); }
if (panel2 != null && panel2 != this) { panel2.item.CreateServerEvent(panel2); }
if (!selectedWire.Item.Removed) { selectedWire.CreateNetworkEvent(); }
}, 1.0f);
}
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, c.Character.ID });
return;
}
@@ -371,7 +371,7 @@ namespace Barotrauma
if (!ItemList.Contains(this))
{
string errorMsg = "Attempted to create a network event for an item (" + Name + ") that hasn't been fully initialized yet.\n" + Environment.StackTrace;
string errorMsg = "Attempted to create a network event for an item (" + Name + ") that hasn't been fully initialized yet.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Item.CreateServerEvent:EventForUninitializedItem" + Name + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
@@ -12,7 +12,7 @@ namespace Barotrauma
private float lastSentVolume, lastSentOxygen, lastSentFireCount;
private float sendUpdateTimer;
private bool decalsChanged;
private bool decalUpdatePending;
public override bool IsMouseOn(Vector2 position)
{
@@ -32,27 +32,21 @@ namespace Barotrauma
return;
}
if (decalsChanged)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { false });
lastSentVolume = waterVolume;
lastSentOxygen = OxygenPercentage;
lastSentFireCount = FireSources.Count;
sendUpdateTimer = NetConfig.HullUpdateInterval;
decalsChanged = false;
return;
}
sendUpdateTimer -= deltaTime;
//update client hulls if the amount of water has changed by >10%
//or if oxygen percentage has changed by 5%
if (Math.Abs(lastSentVolume - waterVolume) > Volume * 0.1f || Math.Abs(lastSentOxygen - OxygenPercentage) > 5f ||
lastSentFireCount != FireSources.Count || FireSources.Count > 0 ||
pendingSectionUpdates.Count > 0 ||
sendUpdateTimer < -NetConfig.SparseHullUpdateInterval)
sendUpdateTimer < -NetConfig.SparseHullUpdateInterval ||
decalUpdatePending)
{
if (sendUpdateTimer < 0.0f)
{
if (decalUpdatePending)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { false });
}
if (pendingSectionUpdates.Count > 0)
{
foreach (int pendingSectionUpdate in pendingSectionUpdates)
@@ -120,8 +114,9 @@ namespace Barotrauma
foreach (Decal decal in decals)
{
message.Write(decal.Prefab.UIntIdentifier);
float normalizedXPos = MathHelper.Clamp(MathUtils.InverseLerp(rect.X, rect.Right, decal.Position.X), 0.0f, 1.0f);
float normalizedYPos = MathHelper.Clamp(MathUtils.InverseLerp(rect.Y - rect.Height, rect.Y, decal.Position.Y), 0.0f, 1.0f);
message.Write((byte)decal.SpriteIndex);
float normalizedXPos = MathHelper.Clamp(MathUtils.InverseLerp(0.0f, rect.Width, decal.CenterPosition.X), 0.0f, 1.0f);
float normalizedYPos = MathHelper.Clamp(MathUtils.InverseLerp(-rect.Height, 0.0f, decal.CenterPosition.Y), 0.0f, 1.0f);
message.WriteRangedSingle(normalizedXPos, 0.0f, 1.0f, 8);
message.WriteRangedSingle(normalizedYPos, 0.0f, 1.0f, 8);
message.WriteRangedSingle(decal.Scale, 0f, 2f, 12);
@@ -332,7 +332,7 @@ namespace Barotrauma.Networking
catch (Exception e)
{
string errorMsg = "Error while writing banlist. {" + e + "}\n" + e.StackTrace;
string errorMsg = "Error while writing banlist. {" + e + "}\n" + e.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce("Banlist.ServerAdminWrite", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
throw;
}
@@ -1,7 +1,5 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Text;
namespace Barotrauma.Networking
@@ -14,19 +12,25 @@ namespace Barotrauma.Networking
UInt16 ID = msg.ReadUInt16();
ChatMessageType type = (ChatMessageType)msg.ReadByte();
string txt = "";
string txt;
int orderIndex = -1;
Character orderTargetCharacter = null;
Entity orderTargetEntity = null;
int orderOptionIndex = -1;
OrderChatMessage orderMsg = null;
OrderTarget orderTargetPosition = null;
if (type == ChatMessageType.Order)
{
orderIndex = msg.ReadByte();
int orderIndex = msg.ReadByte();
orderTargetCharacter = Entity.FindEntityByID(msg.ReadUInt16()) as Character;
orderTargetEntity = Entity.FindEntityByID(msg.ReadUInt16()) as Entity;
orderOptionIndex = msg.ReadByte();
int orderOptionIndex = msg.ReadByte();
if (msg.ReadBoolean())
{
var x = msg.ReadSingle();
var y = msg.ReadSingle();
var hull = Entity.FindEntityByID(msg.ReadUInt16()) as Hull;
orderTargetPosition = new OrderTarget(new Vector2(x, y), hull, true);
}
if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count)
{
@@ -37,7 +41,7 @@ namespace Barotrauma.Networking
Order order = Order.PrefabList[orderIndex];
string orderOption = orderOptionIndex < 0 || orderOptionIndex >= order.Options.Length ? "" : order.Options[orderOptionIndex];
orderMsg = new OrderChatMessage(order, orderOption, orderTargetEntity, orderTargetCharacter, c.Character);
orderMsg = new OrderChatMessage(order, orderOption, orderTargetPosition ?? orderTargetEntity as ISpatialEntity, orderTargetCharacter, c.Character);
txt = orderMsg.Text;
}
else
@@ -114,20 +118,14 @@ namespace Barotrauma.Networking
if (type == ChatMessageType.Order)
{
if (c.Character == null || c.Character.SpeechImpediment >= 100.0f || c.Character.IsDead) return;
ChatMessageType messageType = CanUseRadio(orderMsg.Sender) ? ChatMessageType.Radio : ChatMessageType.Default;
if (orderMsg.Order.TargetAllCharacters)
if (c.Character == null || c.Character.SpeechImpediment >= 100.0f || c.Character.IsDead) { return; }
if (!orderMsg.Order.TargetAllCharacters && orderTargetCharacter != null)
{
//do nothing
var order = orderTargetPosition == null ?
new Order(orderMsg.Order.Prefab, orderTargetEntity, orderMsg.Order.Prefab?.GetTargetItemComponent(orderTargetEntity as Item), orderMsg.Sender) :
new Order(orderMsg.Order.Prefab, orderTargetPosition, orderMsg.Sender);
orderTargetCharacter.SetOrder(order, orderMsg.OrderOption, orderMsg.Sender);
}
else if (orderTargetCharacter != null)
{
orderTargetCharacter.SetOrder(
new Order(orderMsg.Order.Prefab, orderTargetEntity, (orderTargetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == orderMsg.Order.ItemComponentType)),
orderMsg.OrderOption, orderMsg.Sender);
}
GameMain.Server.SendOrderChatMessage(orderMsg);
}
else
@@ -272,7 +272,7 @@ namespace Barotrauma.Networking
GameAnalyticsManager.AddErrorEventOnce(
"FileSender.Update:Exception",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace);
"FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace());
transfer.Status = FileTransferStatus.Error;
return;
}
@@ -615,14 +615,14 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("Failed to write a network message for the client \"" + c.Name + "\"!", e);
string errorMsg = "Failed to write a network message for the client \"" + c.Name + "\"! (MidRoundSyncing: " + c.NeedsMidRoundSync + ")\n"
+ e.Message + "\n" + e.StackTrace;
+ e.Message + "\n" + e.StackTrace.CleanupStackTrace();
if (e.InnerException != null)
{
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace;
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace();
}
GameAnalyticsManager.AddErrorEventOnce(
"GameServer.Update:ClientWriteFailed" + e.StackTrace,
"GameServer.Update:ClientWriteFailed" + e.StackTrace.CleanupStackTrace(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMsg);
}
@@ -1796,7 +1796,7 @@ namespace Barotrauma.Networking
outmsg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name);
outmsg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.ToString());
string campaignSubmarineIndexes = string.Empty;
List<int> campaignSubIndices = new List<int>();
if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
{
List<SubmarineInfo> subList = GameMain.NetLobbyScreen.GetSubList();
@@ -1804,17 +1804,15 @@ namespace Barotrauma.Networking
{
if (GameMain.NetLobbyScreen.CampaignSubmarines.Contains(subList[i]))
{
campaignSubmarineIndexes += i.ToString();
campaignSubmarineIndexes += ";";
campaignSubIndices.Add(i);
}
}
if (campaignSubmarineIndexes.Length > 0)
{
campaignSubmarineIndexes.Trim(';');
}
}
outmsg.Write(campaignSubmarineIndexes);
outmsg.Write((UInt16)campaignSubIndices.Count);
foreach (int campaignSubIndex in campaignSubIndices)
{
outmsg.Write((UInt16)campaignSubIndex);
}
outmsg.Write(serverSettings.Voting.AllowSubVoting);
outmsg.Write(serverSettings.Voting.AllowModeVoting);
@@ -2437,7 +2435,7 @@ namespace Barotrauma.Networking
if (GameSettings.VerboseLogging)
{
Log("Ending the round...\n" + Environment.StackTrace, ServerLog.MessageType.ServerMessage);
Log("Ending the round...\n" + Environment.StackTrace.CleanupStackTrace(), ServerLog.MessageType.ServerMessage);
}
else
@@ -2772,7 +2770,7 @@ namespace Barotrauma.Networking
{
if (recipient == null)
{
string errorMsg = "Attempted to send a chat message to a null client.\n" + Environment.StackTrace;
string errorMsg = "Attempted to send a chat message to a null client.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.SendDirectChatMessage:ClientNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
@@ -3069,12 +3067,13 @@ namespace Barotrauma.Networking
Client.UpdateKickVotes(connectedClients);
int minimumKickVotes = Math.Max(1, (int)(connectedClients.Count * serverSettings.KickVoteRequiredRatio));
var clientsToKick = connectedClients.FindAll(c =>
c.Connection != OwnerConnection &&
!c.HasPermission(ClientPermissions.Kick) &&
!c.HasPermission(ClientPermissions.Ban) &&
!c.HasPermission(ClientPermissions.Unban) &&
c.KickVoteCount >= connectedClients.Count * serverSettings.KickVoteRequiredRatio);
c.KickVoteCount >= minimumKickVotes);
foreach (Client c in clientsToKick)
{
var previousPlayer = previousPlayers.Find(p => p.MatchesClient(c));
@@ -32,7 +32,7 @@ namespace Barotrauma.Networking
createTime = Timing.TotalTime;
#if DEBUG
StackTrace = Environment.StackTrace.ToString();
StackTrace = Environment.StackTrace.CleanupStackTrace();
#endif
}
@@ -121,12 +121,12 @@ namespace Barotrauma.Networking
if (((Entity)entity).Removed && !(entity is Level))
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the entity has been removed.\n"+Environment.StackTrace);
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the entity has been removed.\n"+Environment.StackTrace.CleanupStackTrace());
return;
}
if (((Entity)entity).IdFreed)
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the ID of the entity has been freed.\n"+Environment.StackTrace);
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the ID of the entity has been freed.\n"+Environment.StackTrace.CleanupStackTrace());
return;
}
@@ -197,12 +197,12 @@ namespace Barotrauma.Networking
if (GameSettings.VerboseLogging)
{
string errorMsg = "Failed to read server event for entity \"" + entityName + "\"!";
GameServer.Log(errorMsg + "\n" + e.StackTrace, ServerLog.MessageType.Error);
GameServer.Log(errorMsg + "\n" + e.StackTrace.CleanupStackTrace(), ServerLog.MessageType.Error);
DebugConsole.ThrowError(errorMsg, e);
}
GameAnalyticsManager.AddErrorEventOnce("ServerEntityEventManager.Read:ReadFailed" + entityName,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Failed to read server event for entity \"" + entityName + "\"!\n" + e.StackTrace);
"Failed to read server event for entity \"" + entityName + "\"!\n" + e.StackTrace.CleanupStackTrace());
}
bufferedEvent.IsProcessed = true;
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Barotrauma.Networking
{
@@ -21,8 +19,19 @@ namespace Barotrauma.Networking
msg.Write((byte)Order.PrefabList.IndexOf(Order.Prefab));
msg.Write(TargetCharacter == null ? (UInt16)0 : TargetCharacter.ID);
msg.Write(TargetEntity == null ? (UInt16)0 : TargetEntity.ID);
msg.Write(TargetEntity is Entity ? (TargetEntity as Entity).ID : (UInt16)0);
msg.Write((byte)Array.IndexOf(Order.Prefab.Options, OrderOption));
if (TargetEntity is OrderTarget orderTarget)
{
msg.Write(true);
msg.Write(orderTarget.Position.X);
msg.Write(orderTarget.Position.Y);
msg.Write(orderTarget.Hull == null ? (UInt16)0 : orderTarget.Hull.ID);
}
else
{
msg.Write(false);
}
}
}
}
@@ -124,7 +124,7 @@ namespace Barotrauma.Networking
catch (Exception e)
{
string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace;
string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce("LidgrenServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
@@ -87,7 +87,7 @@ namespace Barotrauma.Networking
if (!Client.IsValidName(name, serverSettings))
{
RemovePendingClient(pendingClient, DisconnectReason.InvalidName, "The name \"" + name + "\" is invalid");
RemovePendingClient(pendingClient, DisconnectReason.InvalidName, "");
return;
}
@@ -101,7 +101,7 @@ namespace Barotrauma.Networking
catch (Exception e)
{
string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace;
string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce("SteamP2PServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
@@ -293,9 +293,9 @@ namespace Barotrauma.Networking
if (!started) { return; }
if (!(conn is SteamP2PConnection steamp2pConn)) { return; }
if (sendDisconnectMessage) { SendDisconnectMessage(steamp2pConn.SteamID, msg); }
if (connectedClients.Contains(steamp2pConn))
{
if (sendDisconnectMessage) SendDisconnectMessage(steamp2pConn.SteamID, msg);
steamp2pConn.Status = NetworkConnectionStatus.Disconnected;
connectedClients.Remove(steamp2pConn);
OnDisconnect?.Invoke(conn, msg);
@@ -41,7 +41,7 @@ namespace Barotrauma
setLinuxEnv();
#endif
Console.WriteLine("Barotrauma Dedicated Server " + GameMain.Version +
" (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")");
" (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
string executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
Directory.SetCurrentDirectory(executableDir);
@@ -99,7 +99,7 @@ namespace Barotrauma
sb.AppendLine("\n");
sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! ");
sb.AppendLine("\n");
sb.AppendLine("Game version " + GameMain.Version + " (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")");
sb.AppendLine("Game version " + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
if (GameMain.Config != null)
{
sb.AppendLine("Language: " + (GameMain.Config.Language ?? "none"));
@@ -124,7 +124,7 @@ namespace Barotrauma
sb.AppendLine("Exception: " + exception.Message + " (" + exception.GetType().ToString() + ")");
sb.AppendLine("Target site: " +exception.TargetSite.ToString());
sb.AppendLine("Stack trace: ");
sb.AppendLine(exception.StackTrace);
sb.AppendLine(exception.StackTrace.CleanupStackTrace());
sb.AppendLine("\n");
if (exception.InnerException != null)
@@ -135,7 +135,7 @@ namespace Barotrauma
sb.AppendLine("Target site: " + exception.InnerException.TargetSite.ToString());
}
sb.AppendLine("Stack trace: ");
sb.AppendLine(exception.InnerException.StackTrace);
sb.AppendLine(exception.InnerException.StackTrace.CleanupStackTrace());
}
sb.AppendLine("Last debug messages:");
@@ -261,7 +261,7 @@ namespace Barotrauma
if (GameMain.Server.ServerSettings.SubSelectionMode == SelectionMode.Random)
{
var nonShuttles = SubmarineInfo.SavedSubmarines.Where(c => !c.HasTag(SubmarineTag.Shuttle) && !c.HasTag(SubmarineTag.HideInMenus)).ToList();
var nonShuttles = SubmarineInfo.SavedSubmarines.Where(c => !c.HasTag(SubmarineTag.Shuttle) && !c.HasTag(SubmarineTag.HideInMenus) && c.IsPlayer).ToList();
SelectedSub = nonShuttles[Rand.Range(0, nonShuttles.Count)];
}
if (GameMain.Server.ServerSettings.ModeSelectionMode == SelectionMode.Random)