Lobby refactor + species loadouts support (#27576)

* Vox stuff

* Species loadouts and lobby refactor

The control flow for lobby is all over the shop so I pulled it all up from the individual controls so now they handle the bare minimum required and LobbyUIController handles the rest.

* a

* Bulk changes

* a

* weh

* Character import / export

* finalise

* woops this stuff too

* Also datafield exporting

* comments

* Review
This commit is contained in:
metalgearsloth
2024-05-12 09:18:21 +10:00
committed by GitHub
parent 999d044139
commit 332f54a3ae
49 changed files with 1867 additions and 1563 deletions

View File

@@ -5,7 +5,6 @@ using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Roles;
using Content.Shared.Traits;
using Robust.Shared.Collections;
@@ -32,16 +31,101 @@ namespace Content.Shared.Preferences
public const int MaxNameLength = 32;
public const int MaxDescLength = 512;
private readonly Dictionary<string, JobPriority> _jobPriorities;
private readonly List<string> _antagPreferences;
private readonly List<string> _traitPreferences;
/// <summary>
/// Job preferences for initial spawn.
/// </summary>
[DataField]
private Dictionary<string, JobPriority> _jobPriorities = new()
{
{
SharedGameTicker.FallbackOverflowJob, JobPriority.High
}
};
/// <summary>
/// Antags we have opted in to.
/// </summary>
[DataField]
private HashSet<string> _antagPreferences = new();
/// <summary>
/// Enabled traits.
/// </summary>
[DataField]
private HashSet<string> _traitPreferences = new();
/// <summary>
/// <see cref="_loadouts"/>
/// </summary>
public IReadOnlyDictionary<string, RoleLoadout> Loadouts => _loadouts;
private Dictionary<string, RoleLoadout> _loadouts;
[DataField]
private Dictionary<string, RoleLoadout> _loadouts = new();
// What in the lord is happening here.
private HumanoidCharacterProfile(
[DataField]
public string Name { get; set; } = "John Doe";
/// <summary>
/// Detailed text that can appear for the character if <see cref="CCVars.FlavorText"/> is enabled.
/// </summary>
[DataField]
public string FlavorText { get; set; } = string.Empty;
/// <summary>
/// Associated <see cref="SpeciesPrototype"/> for this profile.
/// </summary>
[DataField]
public string Species { get; set; } = SharedHumanoidAppearanceSystem.DefaultSpecies;
[DataField]
public int Age { get; set; } = 18;
[DataField]
public Sex Sex { get; private set; } = Sex.Male;
[DataField]
public Gender Gender { get; private set; } = Gender.Male;
/// <summary>
/// <see cref="Appearance"/>
/// </summary>
public ICharacterAppearance CharacterAppearance => Appearance;
/// <summary>
/// Stores markings, eye colors, etc for the profile.
/// </summary>
[DataField]
public HumanoidCharacterAppearance Appearance { get; set; } = new();
/// <summary>
/// When spawning into a round what's the preferred spot to spawn.
/// </summary>
[DataField]
public SpawnPriorityPreference SpawnPriority { get; private set; } = SpawnPriorityPreference.None;
/// <summary>
/// <see cref="_jobPriorities"/>
/// </summary>
public IReadOnlyDictionary<string, JobPriority> JobPriorities => _jobPriorities;
/// <summary>
/// <see cref="_antagPreferences"/>
/// </summary>
public IReadOnlySet<string> AntagPreferences => _antagPreferences;
/// <summary>
/// <see cref="_traitPreferences"/>
/// </summary>
public IReadOnlySet<string> TraitPreferences => _traitPreferences;
/// <summary>
/// If we're unable to get one of our preferred jobs do we spawn as a fallback job or do we stay in lobby.
/// </summary>
[DataField]
public PreferenceUnavailableMode PreferenceUnavailable { get; private set; } =
PreferenceUnavailableMode.SpawnAsOverflow;
public HumanoidCharacterProfile(
string name,
string flavortext,
string species,
@@ -52,8 +136,8 @@ namespace Content.Shared.Preferences
SpawnPriorityPreference spawnPriority,
Dictionary<string, JobPriority> jobPriorities,
PreferenceUnavailableMode preferenceUnavailable,
List<string> antagPreferences,
List<string> traitPreferences,
HashSet<string> antagPreferences,
HashSet<string> traitPreferences,
Dictionary<string, RoleLoadout> loadouts)
{
Name = name;
@@ -71,40 +155,21 @@ namespace Content.Shared.Preferences
_loadouts = loadouts;
}
/// <summary>Copy constructor but with overridable references (to prevent useless copies)</summary>
private HumanoidCharacterProfile(
HumanoidCharacterProfile other,
Dictionary<string, JobPriority> jobPriorities,
List<string> antagPreferences,
List<string> traitPreferences,
Dictionary<string, RoleLoadout> loadouts)
: this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, other.SpawnPriority,
jobPriorities, other.PreferenceUnavailable, antagPreferences, traitPreferences, loadouts)
{
}
/// <summary>Copy constructor</summary>
private HumanoidCharacterProfile(HumanoidCharacterProfile other)
: this(other, new Dictionary<string, JobPriority>(other.JobPriorities), new List<string>(other.AntagPreferences), new List<string>(other.TraitPreferences), new Dictionary<string, RoleLoadout>(other.Loadouts))
{
}
public HumanoidCharacterProfile(
string name,
string flavortext,
string species,
int age,
Sex sex,
Gender gender,
HumanoidCharacterAppearance appearance,
SpawnPriorityPreference spawnPriority,
IReadOnlyDictionary<string, JobPriority> jobPriorities,
PreferenceUnavailableMode preferenceUnavailable,
IReadOnlyList<string> antagPreferences,
IReadOnlyList<string> traitPreferences,
Dictionary<string, RoleLoadout> loadouts)
: this(name, flavortext, species, age, sex, gender, appearance, spawnPriority, new Dictionary<string, JobPriority>(jobPriorities),
preferenceUnavailable, new List<string>(antagPreferences), new List<string>(traitPreferences), new Dictionary<string, RoleLoadout>(loadouts))
public HumanoidCharacterProfile(HumanoidCharacterProfile other)
: this(other.Name,
other.FlavorText,
other.Species,
other.Age,
other.Sex,
other.Gender,
other.Appearance.Clone(),
other.SpawnPriority,
new Dictionary<string, JobPriority>(other.JobPriorities),
other.PreferenceUnavailable,
new HashSet<string>(other.AntagPreferences),
new HashSet<string>(other.TraitPreferences),
new Dictionary<string, RoleLoadout>(other.Loadouts))
{
}
@@ -113,23 +178,7 @@ namespace Content.Shared.Preferences
/// Defaults to <see cref="SharedHumanoidAppearanceSystem.DefaultSpecies"/> for the species.
/// </summary>
/// <returns></returns>
public HumanoidCharacterProfile() : this(
"John Doe",
"",
SharedHumanoidAppearanceSystem.DefaultSpecies,
18,
Sex.Male,
Gender.Male,
new HumanoidCharacterAppearance(),
SpawnPriorityPreference.None,
new Dictionary<string, JobPriority>
{
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
},
PreferenceUnavailableMode.SpawnAsOverflow,
new List<string>(),
new List<string>(),
new Dictionary<string, RoleLoadout>())
public HumanoidCharacterProfile()
{
}
@@ -140,23 +189,10 @@ namespace Content.Shared.Preferences
/// <returns>Humanoid character profile with default settings.</returns>
public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies)
{
return new(
"John Doe",
"",
species,
18,
Sex.Male,
Gender.Male,
HumanoidCharacterAppearance.DefaultWithSpecies(species),
SpawnPriorityPreference.None,
new Dictionary<string, JobPriority>
{
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
},
PreferenceUnavailableMode.SpawnAsOverflow,
new List<string>(),
new List<string>(),
new Dictionary<string, RoleLoadout>());
return new()
{
Species = species,
};
}
// TODO: This should eventually not be a visual change only.
@@ -201,36 +237,17 @@ namespace Content.Shared.Preferences
var name = GetName(species, gender);
return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(species, sex), SpawnPriorityPreference.None,
new Dictionary<string, JobPriority>
{
{SharedGameTicker.FallbackOverflowJob, JobPriority.High},
}, PreferenceUnavailableMode.StayInLobby, new List<string>(), new List<string>(), new Dictionary<string, RoleLoadout>());
return new HumanoidCharacterProfile()
{
Name = name,
Sex = sex,
Age = age,
Gender = gender,
Species = species,
Appearance = HumanoidCharacterAppearance.Random(species, sex),
};
}
public string Name { get; private set; }
public string FlavorText { get; private set; }
public string Species { get; private set; }
[DataField("age")]
public int Age { get; private set; }
[DataField("sex")]
public Sex Sex { get; private set; }
[DataField("gender")]
public Gender Gender { get; private set; }
public ICharacterAppearance CharacterAppearance => Appearance;
[DataField("appearance")]
public HumanoidCharacterAppearance Appearance { get; private set; }
public SpawnPriorityPreference SpawnPriority { get; private set; }
public IReadOnlyDictionary<string, JobPriority> JobPriorities => _jobPriorities;
public IReadOnlyList<string> AntagPreferences => _antagPreferences;
public IReadOnlyList<string> TraitPreferences => _traitPreferences;
public PreferenceUnavailableMode PreferenceUnavailable { get; private set; }
public HumanoidCharacterProfile WithName(string name)
{
return new(this) { Name = name };
@@ -274,7 +291,10 @@ namespace Content.Shared.Preferences
public HumanoidCharacterProfile WithJobPriorities(IEnumerable<KeyValuePair<string, JobPriority>> jobPriorities)
{
return new(this, new Dictionary<string, JobPriority>(jobPriorities), _antagPreferences, _traitPreferences, _loadouts);
return new(this)
{
_jobPriorities = new Dictionary<string, JobPriority>(jobPriorities),
};
}
public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority)
@@ -288,7 +308,11 @@ namespace Content.Shared.Preferences
{
dictionary[jobId] = priority;
}
return new(this, dictionary, _antagPreferences, _traitPreferences, _loadouts);
return new(this)
{
_jobPriorities = dictionary,
};
}
public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode)
@@ -298,50 +322,47 @@ namespace Content.Shared.Preferences
public HumanoidCharacterProfile WithAntagPreferences(IEnumerable<string> antagPreferences)
{
return new(this, _jobPriorities, new List<string>(antagPreferences), _traitPreferences, _loadouts);
return new(this)
{
_antagPreferences = new HashSet<string>(antagPreferences),
};
}
public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref)
{
var list = new List<string>(_antagPreferences);
var list = new HashSet<string>(_antagPreferences);
if (pref)
{
if (!list.Contains(antagId))
{
list.Add(antagId);
}
list.Add(antagId);
}
else
{
if (list.Contains(antagId))
{
list.Remove(antagId);
}
list.Remove(antagId);
}
return new(this, _jobPriorities, list, _traitPreferences, _loadouts);
return new(this)
{
_antagPreferences = list,
};
}
public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref)
{
var list = new List<string>(_traitPreferences);
var list = new HashSet<string>(_traitPreferences);
// TODO: Maybe just refactor this to HashSet? Same with _antagPreferences
if (pref)
{
if (!list.Contains(traitId))
{
list.Add(traitId);
}
list.Add(traitId);
}
else
{
if (list.Contains(traitId))
{
list.Remove(traitId);
}
list.Remove(traitId);
}
return new(this, _jobPriorities, _antagPreferences, list, _loadouts);
return new(this)
{
_traitPreferences = list,
};
}
public string Summary =>
@@ -498,10 +519,10 @@ namespace Content.Shared.Preferences
PreferenceUnavailable = prefsUnavailableMode;
_antagPreferences.Clear();
_antagPreferences.AddRange(antags);
_antagPreferences.UnionWith(antags);
_traitPreferences.Clear();
_traitPreferences.AddRange(traits);
_traitPreferences.UnionWith(traits);
// Checks prototypes exist for all loadouts and dump / set to default if not.
var toRemove = new ValueList<string>();
@@ -514,7 +535,7 @@ namespace Content.Shared.Preferences
continue;
}
loadouts.EnsureValid(session, collection);
loadouts.EnsureValid(this, session, collection);
}
foreach (var value in toRemove)
@@ -540,27 +561,26 @@ namespace Content.Shared.Preferences
public override bool Equals(object? obj)
{
return obj is HumanoidCharacterProfile other && MemberwiseEquals(other);
return ReferenceEquals(this, obj) || obj is HumanoidCharacterProfile other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(
HashCode.Combine(
Name,
Species,
Age,
Sex,
Gender,
Appearance
),
SpawnPriority,
PreferenceUnavailable,
_jobPriorities,
_antagPreferences,
_traitPreferences,
_loadouts
);
var hashCode = new HashCode();
hashCode.Add(_jobPriorities);
hashCode.Add(_antagPreferences);
hashCode.Add(_traitPreferences);
hashCode.Add(_loadouts);
hashCode.Add(Name);
hashCode.Add(FlavorText);
hashCode.Add(Species);
hashCode.Add(Age);
hashCode.Add((int)Sex);
hashCode.Add((int)Gender);
hashCode.Add(Appearance);
hashCode.Add((int)SpawnPriority);
hashCode.Add((int)PreferenceUnavailable);
return hashCode.ToHashCode();
}
public void SetLoadout(RoleLoadout loadout)
@@ -582,10 +602,12 @@ namespace Content.Shared.Preferences
}
copied[loadout.Role] = loadout.Clone();
return new(this, _jobPriorities, _antagPreferences, _traitPreferences, copied);
var profile = Clone();
profile._loadouts = copied;
return profile;
}
public RoleLoadout GetLoadoutOrDefault(string id, IEntityManager entManager, IPrototypeManager protoManager)
public RoleLoadout GetLoadoutOrDefault(string id, ProtoId<SpeciesPrototype>? species, IEntityManager entManager, IPrototypeManager protoManager)
{
if (!_loadouts.TryGetValue(id, out var loadout))
{
@@ -596,5 +618,10 @@ namespace Content.Shared.Preferences
loadout.SetDefault(protoManager);
return loadout;
}
public HumanoidCharacterProfile Clone()
{
return new HumanoidCharacterProfile(this);
}
}
}