Skip to content

Commit

Permalink
Multi gamemod support (new-frontiers-14#2541)
Browse files Browse the repository at this point in the history
  • Loading branch information
dvir001 authored Dec 22, 2024
1 parent 5553b3c commit 70a205f
Show file tree
Hide file tree
Showing 30 changed files with 396 additions and 296 deletions.
4 changes: 2 additions & 2 deletions Content.IntegrationTests/PoolSettings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#nullable enable
#nullable enable

using Robust.Shared.Random;

Expand Down Expand Up @@ -93,7 +93,7 @@ public sealed class PoolSettings
/// <summary>
/// Frontier: the preset to run the game in.
/// Set to secret for upstream tests to mimic upstream behaviour.
/// If you need to check adventure game rule things, set this to Adventure.
/// If you need to check adventure game rule things, set this to nfadventure or nfpirate.
/// </summary>
public string GameLobbyDefaultPreset { get; set; } = "secret";

Expand Down
1 change: 0 additions & 1 deletion Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using Content.Server.Radio.EntitySystems;
using Content.Server._NF.Bank;
using Content.Server.Shipyard.Components;
using Content.Shared._NF.GameRule;
using Content.Shared.Bank.Components;
using Content.Shared.Shipyard.Events;
using Content.Shared.Shipyard.BUI;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
using Content.Shared.Procedural;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;

namespace Content.Server._NF.GameRule.Components;

[RegisterComponent, Access(typeof(NfAdventureRuleSystem))]
public sealed partial class AdventureRuleComponent : Component
[RegisterComponent, Access(typeof(NFAdventureRuleSystem))]
public sealed partial class NFAdventureRuleComponent : Component
{
public List<EntityUid> NFPlayerMinds = new();
public List<EntityUid> CargoDepots = new();
Expand Down
261 changes: 10 additions & 251 deletions Content.Server/_NF/GameRule/NfAdventureRuleSystem.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
using System.Linq;
using System.Net.Http;
using System.Numerics;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Content.Shared._NF.GameRule;
using Content.Server._NF.GameTicking.Events;
using Robust.Server.GameObjects;
using Robust.Server.Maps;
using Content.Shared.GameTicking.Components;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Content.Server.Shuttles.Systems;
using Content.Server.Cargo.Components;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.Maps;
using Content.Server.Station.Systems;
using Content.Shared._NF.CCVar; // Frontier
using Robust.Shared.Configuration;
using Content.Shared._NF.Bank;
Expand All @@ -29,26 +20,19 @@
using Content.Shared.GameTicking;
using Robust.Shared.Enums;
using Robust.Server.Player;
using Content.Server.Warps;

namespace Content.Server._NF.GameRule;

/// <summary>
/// This handles the dungeon and trading post spawning, as well as round end capitalism summary
/// </summary>
public sealed class NfAdventureRuleSystem : GameRuleSystem<AdventureRuleComponent>
public sealed class NFAdventureRuleSystem : GameRuleSystem<NFAdventureRuleComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly MapLoaderSystem _map = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
[Dependency] private readonly BankSystem _bank = default!;
[Dependency] private readonly StationRenameWarpsSystems _renameWarps = default!;
[Dependency] private readonly PointOfInterestSystem _poi = default!;

private readonly HttpClient _httpClient = new();

Expand Down Expand Up @@ -77,11 +61,6 @@ public PlayerRoundBankInformation(int startBalance, string name, NetUserId userI
[ViewVariables]
private Dictionary<EntityUid, PlayerRoundBankInformation> _players = new();

private float _distanceOffset = 1f;
private List<Vector2> _stationCoords = new();

private MapId _mapId;

/// <inheritdoc/>
public override void Initialize()
{
Expand All @@ -92,7 +71,7 @@ public override void Initialize()
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
}

protected override void AppendRoundEndText(EntityUid uid, AdventureRuleComponent component, GameRuleComponent gameRule, ref RoundEndTextAppendEvent ev)
protected override void AppendRoundEndText(EntityUid uid, NFAdventureRuleComponent component, GameRuleComponent gameRule, ref RoundEndTextAppendEvent ev)
{
ev.AddLine(Loc.GetString("adventure-list-start"));
var allScore = new List<Tuple<string, int>>();
Expand Down Expand Up @@ -206,12 +185,9 @@ private void OnRoundRestart(RoundRestartCleanupEvent ev)
_players.Clear();
}

protected override void Started(EntityUid uid, AdventureRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
protected override void Started(EntityUid uid, NFAdventureRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
_mapId = GameTicker.DefaultMap;

_distanceOffset = _configurationManager.GetCVar(NFCCVars.POIDistanceModifier);
_stationCoords = new List<Vector2>();
var mapUid = GameTicker.DefaultMap;

//First, we need to grab the list and sort it into its respective spawning logics
List<PointOfInterestPrototype> depotProtos = new();
Expand All @@ -237,235 +213,18 @@ protected override void Started(EntityUid uid, AdventureRuleComponent component,
remainingUniqueProtosBySpawnGroup[location.SpawnGroup].Add(location);
}
}
GenerateDepots(depotProtos, out component.CargoDepots);
GenerateMarkets(marketProtos, out component.MarketStations);
GenerateRequireds(requiredProtos, out component.RequiredPois);
GenerateOptionals(optionalProtos, out component.OptionalPois);
GenerateUniques(remainingUniqueProtosBySpawnGroup, out component.UniquePois);
_poi.GenerateDepots(mapUid, depotProtos, out component.CargoDepots);
_poi.GenerateMarkets(mapUid, marketProtos, out component.MarketStations);
_poi.GenerateRequireds(mapUid, requiredProtos, out component.RequiredPois);
_poi.GenerateOptionals(mapUid, optionalProtos, out component.OptionalPois);
_poi.GenerateUniques(mapUid, remainingUniqueProtosBySpawnGroup, out component.UniquePois);

base.Started(uid, component, gameRule, args);

// Using invalid entity, we don't have a relevant entity to reference here.
RaiseLocalEvent(EntityUid.Invalid, new StationsGeneratedEvent(), broadcast: true); // TODO: attach this to a meaningful entity.
}

private void GenerateDepots(List<PointOfInterestPrototype> depotPrototypes, out List<EntityUid> depotStations)
{
//For depots, we want them to fill a circular type dystance formula to try to keep them as far apart as possible
//Therefore, we will be taking our range properties and treating them as magnitudes of a direction vector divided
//by the number of depots set in our corresponding cvar

depotStations = new List<EntityUid>();
var depotCount = _configurationManager.GetCVar(NFCCVars.CargoDepots);
var rotation = 2 * Math.PI / depotCount;
var rotationOffset = _random.NextAngle() / depotCount;

for (int i = 0; i < depotCount && depotPrototypes.Count > 0; i++)
{
var proto = _random.Pick(depotPrototypes);
Vector2i offset = new Vector2i((int) (_random.Next(proto.MinimumDistance, proto.MaximumDistance) * _distanceOffset), 0);
offset = offset.Rotate(rotationOffset);
rotationOffset += rotation;
// Append letter to depot name.

string overrideName = proto.Name;
if (i < 26)
overrideName += $" {(char) ('A' + i)}"; // " A" ... " Z"
else
overrideName += $" {i + 1}"; // " 27", " 28"...
if (TrySpawnPoiGrid(proto, offset, out var depotUid, overrideName: overrideName) && depotUid is { Valid: true } depot)
{
depotStations.Add(depot);
AddStationCoordsToSet(offset); // adjust list of actual station coords
}
}
}

private void GenerateMarkets(List<PointOfInterestPrototype> marketPrototypes, out List<EntityUid> marketStations)
{
//For market stations, we are going to allow for a bit of randomness and a different offset configuration. We dont
//want copies of this one, since these can be more themed and duplicate names, for instance, can make for a less
//ideal world

marketStations = new List<EntityUid>();
var marketCount = _configurationManager.GetCVar(NFCCVars.MarketStations);
_random.Shuffle(marketPrototypes);
int marketsAdded = 0;
foreach (var proto in marketPrototypes)
{
if (marketsAdded >= marketCount)
break;

var offset = GetRandomPOICoord(proto.MinimumDistance, proto.MaximumDistance, true);

if (TrySpawnPoiGrid(proto, offset, out var marketUid) && marketUid is { Valid: true } market)
{
marketStations.Add(market);
marketsAdded++;
AddStationCoordsToSet(offset);
}
}
}

private void GenerateOptionals(List<PointOfInterestPrototype> optionalPrototypes, out List<EntityUid> optionalStations)
{
//Stations that do not have a defined grouping in their prototype get a default of "Optional" and get put into the
//generic random rotation of POIs. This should include traditional places like Tinnia's rest, the Science Lab, The Pit,
//and most RP places. This will essentially put them all into a pool to pull from, and still does not use the RNG function.

optionalStations = new List<EntityUid>();
var optionalCount = _configurationManager.GetCVar(NFCCVars.OptionalStations);
_random.Shuffle(optionalPrototypes);
int optionalsAdded = 0;
foreach (var proto in optionalPrototypes)
{
if (optionalsAdded >= optionalCount)
break;

var offset = GetRandomPOICoord(proto.MinimumDistance, proto.MaximumDistance, true);

if (TrySpawnPoiGrid(proto, offset, out var optionalUid) && optionalUid is { Valid: true } uid)
{
optionalStations.Add(uid);
AddStationCoordsToSet(offset);
}
}
}

private void GenerateRequireds(List<PointOfInterestPrototype> requiredPrototypes, out List<EntityUid> requiredStations)
{
//Stations are required are ones that are vital to function but otherwise still follow a generic random spawn logic
//Traditionally these would be stations like Expedition Lodge, NFSD station, Prison/Courthouse POI, etc.
//There are no limit to these, and any prototype marked alwaysSpawn = true will get pulled out of any list that isnt Markets/Depots
//And will always appear every time, and also will not be included in other optional/dynamic lists

requiredStations = new List<EntityUid>();
foreach (var proto in requiredPrototypes)
{
var offset = GetRandomPOICoord(proto.MinimumDistance, proto.MaximumDistance, true);

if (TrySpawnPoiGrid(proto, offset, out var requiredUid) && requiredUid is { Valid: true } uid)
{
requiredStations.Add(uid);
AddStationCoordsToSet(offset);
}
}
}

private void GenerateUniques(Dictionary<string, List<PointOfInterestPrototype>> uniquePrototypes, out List<EntityUid> uniqueStations)
{
//Unique locations are semi-dynamic groupings of POIs that rely each independantly on the SpawnChance per POI prototype
//Since these are the remainder, and logically must have custom-designated groupings, we can then know to subdivide
//our random pool into these found groups.
//To do this with an equal distribution on a per-POI, per-round percentage basis, we are going to ensure a random
//pick order of which we analyze our weighted chances to spawn, and if successful, remove every entry of that group
//entirely.

uniqueStations = new List<EntityUid>();
foreach (var prototypeList in uniquePrototypes.Values)
{
// Try to spawn
_random.Shuffle(prototypeList);
foreach (var proto in prototypeList)
{
var chance = _random.NextFloat(0, 1);
if (chance <= proto.SpawnChance)
{
var offset = GetRandomPOICoord(proto.MinimumDistance, proto.MaximumDistance, true);

if (TrySpawnPoiGrid(proto, offset, out var optionalUid) && optionalUid is { Valid: true } uid)
{
uniqueStations.Add(uid);
AddStationCoordsToSet(offset);
break;
}
}
}
}
}

private bool TrySpawnPoiGrid(PointOfInterestPrototype proto, Vector2 offset, out EntityUid? gridUid, string? overrideName = null)
{
gridUid = null;
if (_map.TryLoad(_mapId, proto.GridPath.ToString(), out var mapUids,
new MapLoadOptions
{
Offset = offset,
Rotation = _random.NextAngle()
}))
{

string stationName = string.IsNullOrEmpty(overrideName) ? proto.Name : overrideName;

EntityUid? stationUid = null;
if (_prototypeManager.TryIndex<GameMapPrototype>(proto.ID, out var stationProto))
{
stationUid = _station.InitializeNewStation(stationProto.Stations[proto.ID], mapUids, stationName);
}

foreach (var grid in mapUids)
{
var meta = EnsureComp<MetaDataComponent>(grid);
_meta.SetEntityName(grid, stationName, meta);

EntityManager.AddComponents(grid, proto.AddComponents);
}

// Rename warp points after set up if needed
if (proto.NameWarp)
{
bool? hideWarp = proto.HideWarp ? true : null;
if (stationUid != null)
_renameWarps.SyncWarpPointsToStation(stationUid.Value, forceAdminOnly: hideWarp);
else
_renameWarps.SyncWarpPointsToGrids(mapUids, forceAdminOnly: hideWarp);
}

gridUid = mapUids[0];
return true;
}

return false;
}

private Vector2 GetRandomPOICoord(float unscaledMinRange, float unscaledMaxRange, bool scaleRange)
{
int numRetries = int.Max(_configurationManager.GetCVar(NFCCVars.POIPlacementRetries), 0);
float minDistance = float.Max(_configurationManager.GetCVar(NFCCVars.MinPOIDistance), 0); // Constant at the end to avoid NaN weirdness

Vector2 coords = _random.NextVector2(unscaledMinRange, unscaledMaxRange);
if (scaleRange)
coords *= _distanceOffset;
for (int i = 0; i < numRetries; i++)
{
bool positionIsValid = true;
foreach (var station in _stationCoords)
{
if (Vector2.Distance(station, coords) < minDistance)
{
positionIsValid = false;
break;
}
}

// We have a valid position
if (positionIsValid)
break;

// No vector yet, get next value.
coords = _random.NextVector2(unscaledMinRange, unscaledMaxRange);
if (scaleRange)
coords *= _distanceOffset;
}

return coords;
}

private void AddStationCoordsToSet(Vector2 coords)
{
_stationCoords.Add(coords);
}

private async Task ReportRound(string message, int color = 0x77DDE7)
{
Logger.InfoS("discord", message);
Expand Down
Loading

0 comments on commit 70a205f

Please sign in to comment.