Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
046c5b4
feat(Species): Port subspecies and protogens
Fasuh Mar 16, 2026
65ba884
Merge pull request #1 from Fasuh/prootening-but-HL
KillerTheFlareon Mar 16, 2026
f606fb9
The protogens arrive
KillerTheFlareon Mar 17, 2026
a579155
auto implanting stuff into nukies and protogens
princess-gurchi Feb 3, 2026
935d56c
Merge pull request #3 from Fasuh/prootening-but-HL
KillerTheFlareon Mar 17, 2026
bcb9fee
Remove autoimplant from nukies
KillerTheFlareon Mar 17, 2026
4331fb4
Changes I missed
KillerTheFlareon Mar 17, 2026
6374f1d
a
KillerTheFlareon Mar 17, 2026
d1bbb04
feat(Species): Port subspecies and protogens
Fasuh Mar 18, 2026
869c756
feat(Species): Port subspecies and protogens
Fasuh Mar 18, 2026
d13298e
Merge pull request #4 from Fasuh/prootening-but-HL
KillerTheFlareon Mar 18, 2026
f663f7c
a
KillerTheFlareon Mar 19, 2026
8efdaed
feat(Species): Port subspecies and protogens
Fasuh Mar 19, 2026
0793f51
Merge pull request #5 from Fasuh/prootening-but-HL
KillerTheFlareon Mar 19, 2026
f8e740a
a
KillerTheFlareon Mar 19, 2026
0378b9e
feat(Species): Port subspecies and protogens
Fasuh Mar 20, 2026
e01f798
Merge pull request #6 from Fasuh/prootening-but-HL
KillerTheFlareon Mar 20, 2026
444c88b
Merge branch 'master' into prootening-but-HL
KillerTheFlareon Mar 20, 2026
5fec74c
Test
KillerTheFlareon Mar 21, 2026
6f2c3ad
aaaa
KillerTheFlareon Mar 21, 2026
7462519
Alllmooosttt..
KillerTheFlareon Mar 21, 2026
eae461d
feat(Species): Port subspecies and protogens
Fasuh Mar 21, 2026
e030bef
Merge pull request #7 from Fasuh/prootening-but-HL
KillerTheFlareon Mar 21, 2026
384ebe6
Stop failing please
KillerTheFlareon Mar 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 8 additions & 0 deletions Content.Client/Lobby/LobbyUIController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,14 @@ public EntityUid LoadProfileEntity(HumanoidCharacterProfile? humanoid, JobProtot

_humanoid.LoadProfile(dummyEnt, humanoid);

// Far Horizons start
if (humanoid != null)
{
var loadout = humanoid.GetSpeciesLoadoutOrDefault(_playerManager.LocalSession, _prototypeManager);
GiveDummyLoadout(dummyEnt, loadout);
}
// Far Horizons end

if (humanoid != null && jobClothes)
{
DebugTools.Assert(job != null);
Expand Down
14 changes: 14 additions & 0 deletions Content.Client/Lobby/UI/HumanoidProfileEditor.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@
ToolTip="{Loc 'humanoid-profile-editor-guidebook-button-tooltip'}"/>
<OptionButton Name="SpeciesButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Far Horizons - subspecies -->
<BoxContainer Name="CSubspecies" HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-subspecies-label'}" />
<Control HorizontalExpand="True"/>
<OptionButton Name="SubspeciesButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Far Horizons end -->
<!-- Custom species display string (examined text override) -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-custom-species-label'}" />
Expand Down Expand Up @@ -97,6 +104,13 @@
<Control HorizontalExpand="True"/>
<OptionButton Name="SpawnPriorityButton" HorizontalAlignment="Right" />
</BoxContainer>
<!--Far Horizons species loadouts-->
<BoxContainer HorizontalExpand="True" Name="CSpeciesLoadout">
<Label Text="{Loc 'humanoid-profile-editor-species-loadout'}" />
<Control HorizontalExpand="True"/>
<Button Name="SpeciesLoadout" Text="{Loc 'loadout-window'}" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Far Horizons end -->
<!-- Height -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-height-label'}" />
Expand Down
38 changes: 33 additions & 5 deletions Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,16 @@ public HumanoidProfileEditor(
OnSkinColorOnValueChanged();
};

// Far Horizons start
UpdateSubspecies();
SubspeciesButton.OnItemSelected += args =>
{
SubspeciesButton.SelectId(args.Id);
SetSpecies(_subspecies[args.Id].ID);
UpdateHairPickers();
};
// Far Horizons end

#region Skin

Skin.OnValueChanged += _ =>
Expand Down Expand Up @@ -1052,23 +1062,33 @@ public void RefreshSpecies()
_species.Clear();

_species.AddRange(_prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart));
_species.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.CurrentCultureIgnoreCase)); // Far Horizons
var speciesIds = _species.Select(o => o.ID).ToList();

for (var i = 0; i < _species.Count; i++)
{
// Far Horizons Start - subspecies
if (_species[i].SubspeciesOf != null)
continue;

var name = Loc.GetString(_species[i].Name);
SpeciesButton.AddItem(name, i);

if (Profile?.Species.Equals(_species[i].ID) == true)
if (Profile?.Species.Equals(_species[i].ID) == true ||
_species.Find(p => p.ID == Profile?.Species)?.SubspeciesOf == _species[i].ID)
{
SpeciesButton.SelectId(i);
}
// Far Horizons End
}

// If our species isn't available then reset it to default.
if (Profile != null)
{
if (!speciesIds.Contains(Profile.Species))
// Far Horizons-Start - resolve parent species for subspecies
var parentSpecies = _species.Find(p => p.ID == Profile?.Species)?.SubspeciesOf ?? Profile.Species;
if (!speciesIds.Contains(parentSpecies))
// Far Horizons-End
{
SetSpecies(SharedHumanoidAppearanceSystem.DefaultSpecies);
}
Expand Down Expand Up @@ -1222,6 +1242,8 @@ public void SetProfile(HumanoidCharacterProfile? profile, int? slot)
JobOverride = null;

UpdateNameEdit();
UpdateSubspecies(); // Far Horizons
UpdateSpeciesLoadout(); // Far Horizons
UpdateCustomSpeciesEdit();
UpdateFlavorTextEdit();
UpdateSexControls();
Expand Down Expand Up @@ -1275,10 +1297,14 @@ private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
// I.e., do what jobs/antags do.

var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
// Far Horizons Start - Subspecies
var speciesId = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
var speciesProto = _species.Find(p => p.ID == speciesId) ?? _species.First();
var species = speciesProto.SubspeciesOf ?? speciesProto.ID;
var page = DefaultSpeciesGuidebook;
if (_prototypeManager.HasIndex<GuideEntryPrototype>(species))
page = species;
page = new ProtoId<GuideEntryPrototype>(species.Id);
// Far Horizons End

if (_prototypeManager.TryIndex<GuideEntryPrototype>(DefaultSpeciesGuidebook, out var guideRoot))
{
Expand Down Expand Up @@ -1710,6 +1736,8 @@ private void SetGender(Gender newGender)
private void SetSpecies(string newSpecies)
{
Profile = Profile?.WithSpecies(newSpecies);
UpdateSubspecies(); // Far Horizons
UpdateSpeciesLoadout(); // Far Horizons
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
Markings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
EnforceSpeciesTraitRestrictions();
Expand Down Expand Up @@ -1980,7 +2008,7 @@ public void UpdateSpeciesGuidebookIcon()
return;

// Don't display the info button if no guide entry is found
if (!_prototypeManager.HasIndex<GuideEntryPrototype>(species))
if (!_prototypeManager.HasIndex<GuideEntryPrototype>(speciesProto.SubspeciesOf ?? species)) // Far Horizons - Subspecies
return;

const string style = "SpeciesInfoDefault";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System.Linq;
using Content.Client.Lobby.UI.Loadouts;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences.Loadouts;
using Robust.Shared.Prototypes;

namespace Content.Client.Lobby.UI;

public sealed partial class HumanoidProfileEditor
{
private List<SpeciesPrototype> _subspecies = [];

private void UpdateSubspecies()
{
CSubspecies.Visible = false;
_subspecies = [];
SubspeciesButton.Clear();

var species = _species.Find(x => x.ID == Profile?.Species) ?? _species.First();

if(species.HasSubspecies == false && species.SubspeciesOf == null)
return;

List<SpeciesPrototype> subspecies = [];
var selected = 0;

if (species.HasSubspecies)
{
List<SpeciesPrototype> allSubspecies = [.. _prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(p => p.SubspeciesOf == species.ID)];
allSubspecies.Sort((a, b) => string.Compare(a.SubspeciesName ?? a.Name, b.SubspeciesName ?? b.Name, StringComparison.OrdinalIgnoreCase));

subspecies.Add(species);
subspecies.AddRange(allSubspecies);
}
else if (species.SubspeciesOf != null)
{
List<SpeciesPrototype> allSubspecies = [.. _prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(p => p.SubspeciesOf == species.SubspeciesOf)];
allSubspecies.Sort((a, b) => string.Compare(a.SubspeciesName ?? a.Name, b.SubspeciesName ?? b.Name, StringComparison.OrdinalIgnoreCase));
var parent = _prototypeManager.Index(species.SubspeciesOf);

subspecies.Add(parent);
subspecies.AddRange(allSubspecies);
selected = subspecies.IndexOf(species);
}

if (subspecies.Count == 0)
return;

for (var i = 0; i < subspecies.Count; i++)
{
_subspecies.Add(subspecies[i]);

var name = Loc.GetString(subspecies[i].SubspeciesName == null ? subspecies[i].Name : subspecies[i].SubspeciesName!.Value);
SubspeciesButton.AddItem(name, i);
}


SubspeciesButton.SelectId(selected);
CSubspecies.Visible = true;
}

private void UpdateSpeciesLoadout()
{
CSpeciesLoadout.Visible = false;

if (Profile == null ||
!_prototypeManager.TryIndex(Profile.Species, out var species) ||
species.Loadout == null ||
!_prototypeManager.TryIndex(species.Loadout, out var loadoutProto))
return;


CSpeciesLoadout.Visible = true;
SpeciesLoadout.OnPressed += args =>
{
RoleLoadout? loadout = null;

if (Profile.SpeciesLoadout == null)
{
loadout = Profile.GetSpeciesLoadoutOrDefault(_playerManager.LocalSession, _prototypeManager);
loadout!.SetDefault(Profile, _playerManager.LocalSession, _prototypeManager);
} else {
loadout = Profile.SpeciesLoadout!.Clone();
loadout!.SetDefault(Profile, _playerManager.LocalSession, _prototypeManager);
}

OpenSpeciesLoadout(species, loadout, loadoutProto);
};
}

private void OpenSpeciesLoadout(SpeciesPrototype species, RoleLoadout speciesLoadout, RoleLoadoutPrototype speciesLoadoutProto)
{
_loadoutWindow?.Dispose();
_loadoutWindow = null;
var collection = IoCManager.Instance;

if (collection == null || _playerManager.LocalSession == null || Profile == null || species.Loadout == null)
return;

var session = _playerManager.LocalSession;

_loadoutWindow = new LoadoutWindow(Profile, speciesLoadout, speciesLoadoutProto, _playerManager.LocalSession, collection)
{
Title = Loc.GetString("loadout-window-title-loadout", ("job", $"{Loc.GetString(species.Name)}")),
};

// Refresh the buttons etc.
_loadoutWindow.RefreshLoadouts(speciesLoadout, session, collection);
_loadoutWindow.OpenCenteredLeft();

_loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) =>
{
speciesLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
_loadoutWindow.RefreshLoadouts(speciesLoadout, session, collection);
Profile = Profile?.WithSpeciesLoadout(speciesLoadout);
ReloadPreview();
};

_loadoutWindow.OnLoadoutUnpressed += (loadoutGroup, loadoutProto) =>
{
speciesLoadout.RemoveLoadout(loadoutGroup, loadoutProto, _prototypeManager);
_loadoutWindow.RefreshLoadouts(speciesLoadout, session, collection);
Profile = Profile?.WithSpeciesLoadout(speciesLoadout);
ReloadPreview();
};

ReloadPreview();

_loadoutWindow.OnClose += () =>
{
JobOverride = null;
ReloadPreview();
};

if (Profile is null)
return;

UpdateJobPriorities();
}
}
21 changes: 18 additions & 3 deletions Content.Server/Database/ServerDbBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,14 @@ private static HumanoidCharacterProfile ConvertProfiles(Profile profile)
loadouts[role.RoleName] = loadout;
}

// Far Horizons Start - Subspecies
RoleLoadout? speciesLoadout = null;
if (loadouts.Remove(HumanoidCharacterProfile.SpeciesLoadoutDatabaseKey, out var speciesLoadoutValue))
{
speciesLoadout = speciesLoadoutValue;
}
// Far Horizons End

// Get the company with fallback to default "None"
var company = profile.Company ?? "None";

Expand Down Expand Up @@ -287,7 +295,8 @@ private static HumanoidCharacterProfile ConvertProfiles(Profile profile)
antags.ToHashSet(),
traits.ToHashSet(),
loadouts,
company);
company,
speciesLoadout); // Far Horizons
}

private static Profile ConvertProfiles(HumanoidCharacterProfile humanoid, int slot, Profile? profile = null)
Expand Down Expand Up @@ -347,7 +356,13 @@ private static Profile ConvertProfiles(HumanoidCharacterProfile humanoid, int sl

profile.Loadouts.Clear();

foreach (var (role, loadouts) in humanoid.Loadouts)
// Far Horizons-Start - Include species loadout in serialized loadouts
Dictionary<string, RoleLoadout> allLoadouts = new(humanoid.Loadouts);
if (humanoid.SpeciesLoadout != null)
allLoadouts[HumanoidCharacterProfile.SpeciesLoadoutDatabaseKey] = humanoid.SpeciesLoadout;
// Far Horizons-End

foreach (var (role, loadouts) in allLoadouts) // Far Horizons
{
var dz = new ProfileRoleLoadout()
{
Expand Down Expand Up @@ -963,7 +978,7 @@ public async Task AddAdminLogs(List<AdminLog> logs)
try
{
await using var db = await GetDb();

// Get all unique player IDs referenced in these logs
var playerIds = logs
.SelectMany(log => log.Players)
Expand Down
5 changes: 5 additions & 0 deletions Content.Server/Medical/Components/HealingComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,10 @@ public sealed partial class HealingComponent : Component
/// </summary>
[DataField("healingEndSound")]
public SoundSpecifier? HealingEndSound = null;

#region FarHorizons
[DataField]
public int AdjustEyeDamage = 0;
#endregion
}
}
Loading
Loading