* GameRule, a lot of prototypes and FTL pending

* some protos

* - add: Magic additions, tweaks and bugfixes.

* - add: Wizard gamerule.

* - tweak: Do not call shuttle.

---------

Co-authored-by: melano <92106367+melanoTurbo@users.noreply.github.com>
This commit is contained in:
Aviu00
2024-06-07 16:02:23 +00:00
committed by GitHub
parent bd60968a9c
commit 4fde7aedee
25 changed files with 6051 additions and 41 deletions

View File

@@ -47,5 +47,8 @@ namespace Content.Server.Abilities.Mime
/// </summary>
[DataField("vowCooldown")]
public TimeSpan VowCooldown = TimeSpan.FromMinutes(5);
[DataField] // WD
public bool CanBreakVow = true;
}
}

View File

@@ -108,6 +108,9 @@ namespace Content.Server.Abilities.Mime
if (!Resolve(uid, ref mimePowers))
return;
if (!mimePowers.CanBreakVow) // WD
return;
if (mimePowers.VowBroken)
return;

View File

@@ -3,6 +3,7 @@ using Content.Server._White.Carrying;
using Content.Server._White.Cult.GameRule;
using Content.Server._White.Mood;
using Content.Server._White.Other.FastAndFuriousSystem;
using Content.Server._White.Wizard;
using Content.Server.Administration.Systems;
using Content.Server.Bible.Components;
using Content.Server.Body.Components;
@@ -404,7 +405,7 @@ public sealed partial class ChangelingSystem
if (!_ui.TryGetUi(user, TransformStingSelectorUiKey.Key, out var bui))
return;
if (HasComp<ChangelingComponent>(target) || HasComp<SpaceNinjaComponent>(target) ||
if (HasComp<ChangelingComponent>(target) || HasComp<SpaceNinjaComponent>(target) || HasComp<WizardComponent>(target) ||
_tag.HasTag(target, "Unimplantable")) // Terminator check
{
_popup.PopupEntity(Loc.GetString("changeling-popup-transform-not-effective"), user, user);

View File

@@ -46,6 +46,7 @@ using Robust.Shared.Random;
using Robust.Shared.Utility;
using System.Linq;
using Content.Shared.FixedPoint;
using Content.Shared.Mind;
namespace Content.Server.GameTicking.Rules;
@@ -215,12 +216,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
ev.AddLine(Loc.GetString("nukeops-list-start"));
var nukiesQuery = EntityQueryEnumerator<NukeopsRoleComponent, MindContainerComponent>();
while (nukiesQuery.MoveNext(out var nukeopsUid, out _, out var mindContainer))
var nukiesQuery = EntityQueryEnumerator<NukeopsRoleComponent, MindComponent>();
while (nukiesQuery.MoveNext(out var nukeopsUid, out _, out var mind))
{
if (!_mind.TryGetMind(nukeopsUid, out _, out var mind, mindContainer))
continue;
ev.AddLine(mind.Session != null
? Loc.GetString("nukeops-list-name-user", ("name", Name(nukeopsUid)), ("user", mind.Session.Name))
: Loc.GetString("nukeops-list-name", ("name", Name(nukeopsUid))));
@@ -895,4 +893,4 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
EnsureComp<NukeOperativeComponent>(transferTo);
RemComp<NukeOperativeComponent>(transferFrom);
}
}
}

View File

@@ -1,5 +1,7 @@
using System.Linq;
using Content.Shared.Eye;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Movement.Systems;
using Content.Shared.Physics;
using Content.Shared.Stealth;
@@ -17,6 +19,7 @@ public sealed class IncorporealSystem : EntitySystem
[Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
[Dependency] private readonly SharedStealthSystem _stealth = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly PullingSystem _pulling = default!;
public override void Initialize()
{
@@ -48,6 +51,8 @@ public sealed class IncorporealSystem : EntitySystem
Spawn("EffectEmpPulse", Transform(uid).Coordinates);
EnsureComp<StealthComponent>(uid);
_stealth.SetVisibility(uid, -1);
if (TryComp(uid, out PullableComponent? pullable))
_pulling.TryStopPull(uid, pullable);
_movement.RefreshMovementSpeedModifiers(uid);
}

View File

@@ -0,0 +1,14 @@
namespace Content.Server._White.Wizard.Magic.Other;
[RegisterComponent]
public sealed partial class VariableUseDelayComponent : Component
{
[DataField]
public TimeSpan UseDelay = TimeSpan.FromSeconds(6);
[DataField]
public TimeSpan AltUseDelay = TimeSpan.FromSeconds(12);
[DataField]
public TimeSpan ChargeUseDelay = TimeSpan.FromSeconds(40);
}

View File

@@ -61,6 +61,7 @@ public sealed class WizardSpellsSystem : EntitySystem
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly EmpSystem _empSystem = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
#endregion
@@ -148,7 +149,8 @@ public sealed class WizardSpellsSystem : EntitySystem
}
SetOutfitCommand.SetOutfit(msg.Target, "MimeGear", EntityManager);
EnsureComp<MimePowersComponent>(msg.Target);
var powers = EnsureComp<MimePowersComponent>(msg.Target);
powers.CanBreakVow = false;
Spawn("AdminInstantEffectSmoke3", Transform(msg.Target).Coordinates);
@@ -195,7 +197,8 @@ public sealed class WizardSpellsSystem : EntitySystem
return;
}
EnsureComp<CluwneComponent>(msg.Target);
var cluwne = EnsureComp<CluwneComponent>(msg.Target);
cluwne.KnockChance = 0.2f;
Spawn("AdminInstantEffectSmoke3", Transform(msg.Target).Coordinates);
@@ -317,6 +320,7 @@ public sealed class WizardSpellsSystem : EntitySystem
break;
}
SetCooldown(msg.Action, msg.ActionUseType);
msg.Handled = true;
Speak(msg);
}
@@ -338,7 +342,7 @@ public sealed class WizardSpellsSystem : EntitySystem
{
var xform = Transform(msg.Performer);
var positions = GetArenaPositions(xform, msg.ChargeLevel);
var positions = GetArenaPositions(xform.Coordinates, msg.ChargeLevel);
foreach (var position in positions)
{
@@ -351,9 +355,7 @@ public sealed class WizardSpellsSystem : EntitySystem
private void ForcewallSpellAlt(ForceWallSpellEvent msg)
{
var xform = Transform(msg.TargetUid);
var positions = GetArenaPositions(xform, 2);
var positions = GetArenaPositions(msg.Target, 2);
foreach (var direction in positions)
{
@@ -373,6 +375,8 @@ public sealed class WizardSpellsSystem : EntitySystem
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
var result = true;
switch (msg.ActionUseType)
{
case ActionUseType.Default:
@@ -382,10 +386,14 @@ public sealed class WizardSpellsSystem : EntitySystem
CardsSpellCharge(msg);
break;
case ActionUseType.AltUse:
CardsSpellAlt(msg);
result = CardsSpellAlt(msg);
break;
}
if (!result)
return;
SetCooldown(msg.Action, msg.ActionUseType);
msg.Handled = true;
Speak(msg);
}
@@ -417,7 +425,7 @@ public sealed class WizardSpellsSystem : EntitySystem
{
var xform = Transform(msg.Performer);
var count = 5 * msg.ChargeLevel;
var count = 10 * msg.ChargeLevel;
var angleStep = 360f / count;
for (var i = 0; i < count; i++)
@@ -441,14 +449,19 @@ public sealed class WizardSpellsSystem : EntitySystem
}
}
private void CardsSpellAlt(CardsSpellEvent msg)
private bool CardsSpellAlt(CardsSpellEvent msg)
{
if (!HasComp<ItemComponent>(msg.TargetUid))
return;
{
_popupSystem.PopupEntity("Работает только на предметах.", msg.Performer, msg.Performer);
return false;
}
Del(msg.TargetUid);
var item = Spawn(msg.Prototype);
_handsSystem.TryPickupAnyHand(msg.Performer, item);
return true;
}
#endregion
@@ -460,6 +473,8 @@ public sealed class WizardSpellsSystem : EntitySystem
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
var result = true;
switch (msg.ActionUseType)
{
case ActionUseType.Default:
@@ -469,10 +484,14 @@ public sealed class WizardSpellsSystem : EntitySystem
FireballSpellCharge(msg);
break;
case ActionUseType.AltUse:
FireballSpellAlt(msg);
result = FireballSpellAlt(msg);
break;
}
if (!result)
return;
SetCooldown(msg.Action, msg.ActionUseType);
msg.Handled = true;
Speak(msg);
}
@@ -507,21 +526,26 @@ public sealed class WizardSpellsSystem : EntitySystem
foreach (var target in targets.Where(target => target.Owner != msg.Performer))
{
target.Comp.FireStacks += 3;
target.Comp.FireStacks += 2 + msg.ChargeLevel * 2;
_flammableSystem.Ignite(target, msg.Performer);
}
}
private void FireballSpellAlt(FireballSpellEvent msg)
private bool FireballSpellAlt(FireballSpellEvent msg)
{
if (!TryComp<FlammableComponent>(msg.TargetUid, out var flammableComponent))
return;
{
_popupSystem.PopupEntity("Это нельзя поджечь!", msg.Performer, msg.Performer);
return false;
}
flammableComponent.FireStacks += 4;
_flammableSystem.Ignite(msg.TargetUid, msg.Performer);
EnsureComp<AmaterasuComponent>(msg.TargetUid);
return true;
}
#endregion
@@ -546,6 +570,7 @@ public sealed class WizardSpellsSystem : EntitySystem
break;
}
SetCooldown(msg.Action, msg.ActionUseType);
msg.Handled = true;
Speak(msg);
}
@@ -574,10 +599,12 @@ public sealed class WizardSpellsSystem : EntitySystem
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
return;
var result = true;
switch (msg.ActionUseType)
{
case ActionUseType.Default:
ArcSpellDefault(msg);
result = ArcSpellDefault(msg);
break;
case ActionUseType.Charge:
ArcSpellCharge(msg);
@@ -587,21 +614,28 @@ public sealed class WizardSpellsSystem : EntitySystem
break;
}
if (!result)
return;
SetCooldown(msg.Action, msg.ActionUseType);
msg.Handled = true;
Speak(msg);
}
private void ArcSpellDefault(ArcSpellEvent msg)
private bool ArcSpellDefault(ArcSpellEvent msg)
{
const int possibleEntitiesCount = 2;
var entitiesInRange = _lookup.GetEntitiesInRange(msg.Target, 1);
var entitiesToHit = entitiesInRange.Where(HasComp<MobStateComponent>).Take(possibleEntitiesCount);
foreach (var entity in entitiesToHit)
var entityUids = entitiesToHit.ToList();
foreach (var entity in entityUids)
{
_lightning.ShootLightning(msg.Performer, entity);
}
return entityUids.Count != 0;
}
private void ArcSpellCharge(ArcSpellEvent msg)
@@ -644,7 +678,23 @@ public sealed class WizardSpellsSystem : EntitySystem
InGameICChatType.Speak, false);
}
private List<EntityCoordinates> GetArenaPositions(TransformComponent casterXform, int arenaSize)
private void SetCooldown(EntityUid action, ActionUseType useType)
{
if (!TryComp(action, out VariableUseDelayComponent? variableUseDelayComponent))
return;
var cooldown = useType switch
{
ActionUseType.Default => variableUseDelayComponent.UseDelay,
ActionUseType.AltUse => variableUseDelayComponent.AltUseDelay,
ActionUseType.Charge => variableUseDelayComponent.ChargeUseDelay,
_ => TimeSpan.FromSeconds(60)
};
_actions.SetUseDelay(action, cooldown);
}
private List<EntityCoordinates> GetArenaPositions(EntityCoordinates coords, int arenaSize)
{
var positions = new List<EntityCoordinates>();
@@ -655,7 +705,7 @@ public sealed class WizardSpellsSystem : EntitySystem
for (var j = -arenaSize; j <= arenaSize; j++)
{
var position = new Vector2(i, j);
var coordinates = casterXform.Coordinates.Offset(position);
var coordinates = coords.Offset(position);
positions.Add(coordinates);
}
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server._White.Wizard;
[RegisterComponent]
public sealed partial class WizardComponent : Component
{
}

View File

@@ -0,0 +1,11 @@
using Content.Shared.Roles;
namespace Content.Server._White.Wizard;
/// <summary>
/// This is used for...
/// </summary>
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class WizardRoleComponent : AntagonistRoleComponent
{
}

View File

@@ -0,0 +1,68 @@
using Content.Server.RoundEnd;
using Content.Shared.NPC.Prototypes;
using Content.Shared.Random;
using Content.Shared.Roles;
using Robust.Shared.Prototypes;
namespace Content.Server._White.Wizard;
/// <summary>
/// This is used for...
/// </summary>
[RegisterComponent]
public sealed partial class WizardRuleComponent : Component
{
public readonly List<EntityUid> WizardMinds = new();
public EntityUid? TargetStation;
[DataField("minPlayers")]
public int MinPlayers = 20;
[DataField("announcementOnWizardDeath")]
public bool AnnouncementOnWizardDeath = true;
[DataField("points")]
public int Points = 10; //TODO: wizard shop prototype
[DataField("wizardRoleProto")]
public ProtoId<AntagPrototype> WizardRoleProto = "WizardRole";
[DataField("wizardSpawnPointProto")]
public EntProtoId SpawnPointProto = "SpawnPointWizard";
[DataField]
public EntProtoId GhostSpawnPointProto = "SpawnPointGhostWizard";
[DataField("startingGear")]
public ProtoId<StartingGearPrototype> StartingGear = "WizardGear";
[DataField("spawnShuttle")]
public bool SpawnShuttle = true;
[DataField]
public EntityUid? ShuttleMap;
[DataField("shuttlePath")]
public string ShuttlePath = "/Maps/White/Shuttles/wizard.yml";
[DataField]
public ProtoId<NpcFactionPrototype> Faction = "Wizard";
public RoundEndBehavior RoundEndBehavior = RoundEndBehavior.Nothing;
[DataField]
public string RoundEndTextSender = "comms-console-announcement-title-centcom";
[DataField]
public string RoundEndTextShuttleCall = "wizard-no-more-threat-announcement-shuttle-call";
[DataField]
public string RoundEndTextAnnouncement = "wizard-no-more-threat-announcement";
[DataField]
public TimeSpan EvacShuttleTime = TimeSpan.FromMinutes(5);
[DataField]
public ProtoId<WeightedRandomPrototype> ObjectiveGroup = "WizardObjectiveGroups";
}

View File

@@ -0,0 +1,397 @@
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Antag;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Ghost.Roles.Events;
using Content.Server.Humanoid;
using Content.Server.Mind;
using Content.Server.Preferences.Managers;
using Content.Server.RoundEnd;
using Content.Server.Spawners.Components;
using Content.Server.Station.Systems;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
using Content.Shared.NPC.Systems;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using System.Linq;
using Content.Server.Objectives;
using Content.Server.Station.Components;
using Content.Shared.Mind;
using Content.Shared.NPC.Components;
using Content.Shared.Objectives.Components;
using Robust.Server.GameObjects;
using Robust.Server.Maps;
using Robust.Shared.Random;
namespace Content.Server._White.Wizard;
/// <summary>
/// This handles...
/// </summary>
public sealed class WizardRuleSystem : GameRuleSystem<WizardRuleComponent>
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly MapLoaderSystem _map = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
[Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly ObjectivesSystem _objectives = default!;
private ISawmill _sawmill = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RoundStartAttemptEvent>(OnStartAttempt);
SubscribeLocalEvent<RulePlayerSpawningEvent>(OnPlayersSpawning);
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRunLevelChanged);
SubscribeLocalEvent<WizardComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<WizardComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<WizardComponent, GhostRoleSpawnerUsedEvent>(OnPlayersGhostSpawning);
SubscribeLocalEvent<WizardComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<WizardRuleComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
_sawmill = _logManager.GetSawmill("Wizard");
}
private void OnObjectivesTextGetInfo(Entity<WizardRuleComponent> ent, ref ObjectivesTextGetInfoEvent args)
{
args.Minds = ent.Comp.WizardMinds;
args.AgentName = Loc.GetString("wizard-round-end-agent-name");
}
private void OnStartAttempt(RoundStartAttemptEvent ev)
{
TryRoundStartAttempt(ev, Loc.GetString("wizard-title"));
}
private void OnPlayersSpawning(RulePlayerSpawningEvent ev)
{
var query = QueryActiveRules();
while (query.MoveNext(out var uid, out _, out var wizardRule, out _))
{
if (!SpawnMap((uid, wizardRule)))
{
_sawmill.Info("Failed to load shuttle for wizard");
continue;
}
//Handle there being nobody readied up
if (ev.PlayerPool.Count == 0)
continue;
var wizardEligible =
_antagSelection.GetEligibleSessions(ev.PlayerPool, wizardRule.WizardRoleProto);
//Select wizard
var selectedWizard = _antagSelection
.ChooseAntags(1, wizardEligible, ev.PlayerPool).FirstOrDefault();
SpawnWizard(selectedWizard, wizardRule, false);
if (selectedWizard != null)
GameTicker.PlayerJoinGame(selectedWizard);
}
}
protected override void Started(
EntityUid uid,
WizardRuleComponent component,
GameRuleComponent gameRule,
GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
if (GameTicker.RunLevel == GameRunLevel.InRound)
SpawnWizardGhostRole(uid, component);
}
private void OnComponentRemove(EntityUid uid, WizardComponent component, ComponentRemove args)
{
CheckAnnouncement();
}
private void OnMobStateChanged(EntityUid uid, WizardComponent component, MobStateChangedEvent ev)
{
if (ev.NewMobState == MobState.Dead)
CheckAnnouncement();
}
private void OnPlayersGhostSpawning(EntityUid uid, WizardComponent component, GhostRoleSpawnerUsedEvent args)
{
var spawner = args.Spawner;
if (!TryComp<WizardSpawnerComponent>(spawner, out var wizardSpawner))
return;
HumanoidCharacterProfile? profile = null;
if (TryComp(args.Spawned, out ActorComponent? actor))
profile = _prefs.GetPreferences(actor.PlayerSession.UserId).SelectedCharacter as HumanoidCharacterProfile;
if (!EntityQuery<WizardRuleComponent>().Any())
return;
if (!_prototypeManager.TryIndex(wizardSpawner.StartingGear, out var gear))
{
_sawmill.Error("Failed to load wizard gear prototype");
return;
}
SetupWizardEntity(uid, wizardSpawner.Points, gear, profile);
}
private void OnMindAdded(EntityUid uid, WizardComponent component, MindAddedMessage args)
{
if (!_mind.TryGetMind(uid, out var mindId, out var mind))
return;
var query = QueryActiveRules();
while (query.MoveNext(out _, out _, out var wizardRule, out _))
{
AddRole(mindId, mind, wizardRule);
if (mind.Session is not { } playerSession)
return;
if (GameTicker.RunLevel != GameRunLevel.InRound)
return;
NotifyWizard(playerSession, component, wizardRule);
}
}
private void AddRole(EntityUid mindId, MindComponent mind, WizardRuleComponent wizardRule)
{
if (_roles.MindHasRole<WizardRoleComponent>(mindId))
return;
wizardRule.WizardMinds.Add(mindId);
var role = wizardRule.WizardRoleProto;
_roles.MindAddRole(mindId, new WizardRoleComponent {PrototypeId = role});
GiveObjectives(mindId, mind, wizardRule);
}
private void GiveObjectives(EntityUid mindId, MindComponent mind, WizardRuleComponent wizardRule)
{
_mind.TryAddObjective(mindId, mind, "WizardSurviveObjective");
var difficulty = 0f;
for (var pick = 0; pick < 6 && 8 > difficulty; pick++)
{
var objective = _objectives.GetRandomObjective(mindId, mind, wizardRule.ObjectiveGroup);
if (objective == null)
continue;
_mind.AddObjective(mindId, mind, objective.Value);
var adding = Comp<ObjectiveComponent>(objective.Value).Difficulty;
difficulty += adding;
_sawmill.Debug($"Added objective {ToPrettyString(objective):objective} with {adding} difficulty");
}
}
private void OnRunLevelChanged(GameRunLevelChangedEvent ev)
{
var query = QueryActiveRules();
while (query.MoveNext(out var uid, out _, out var wiz, out _))
{
if (ev.New == GameRunLevel.InRound)
OnRoundStart(uid, wiz);
}
}
private void OnRoundStart(EntityUid uid, WizardRuleComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
var eligible = new List<Entity<StationEventEligibleComponent, NpcFactionMemberComponent>>();
var eligibleQuery = EntityQueryEnumerator<StationEventEligibleComponent, NpcFactionMemberComponent>();
while (eligibleQuery.MoveNext(out var eligibleUid, out var eligibleComp, out var member))
{
if (!_npcFaction.IsFactionHostile(component.Faction, (eligibleUid, member)))
continue;
eligible.Add((eligibleUid, eligibleComp, member));
}
if (eligible.Count == 0)
return;
component.TargetStation = _random.Pick(eligible);
var filter = Filter.Empty();
var query = EntityQueryEnumerator<WizardComponent, ActorComponent>();
while (query.MoveNext(out _, out var wizard, out var actor))
{
NotifyWizard(actor.PlayerSession, wizard, component);
filter.AddPlayer(actor.PlayerSession);
}
}
private void CheckAnnouncement()
{
var query = QueryActiveRules();
while (query.MoveNext(out _, out _, out var wizard, out _))
{
_roundEndSystem.DoRoundEndBehavior(
wizard.RoundEndBehavior, wizard.EvacShuttleTime, wizard.RoundEndTextSender,
wizard.RoundEndTextShuttleCall, wizard.RoundEndTextAnnouncement);
return;
}
}
private bool SpawnMap(Entity<WizardRuleComponent> ent)
{
if (!ent.Comp.SpawnShuttle
|| ent.Comp.ShuttleMap != null)
return true;
var shuttleMap = _mapManager.CreateMap();
var options = new MapLoadOptions
{
LoadMap = true,
};
if (!_map.TryLoad(shuttleMap, ent.Comp.ShuttlePath, out _, options))
return false;
ent.Comp.ShuttleMap = _mapManager.GetMapEntityId(shuttleMap);
return true;
}
private void SetupWizardEntity(
EntityUid mob,
int points,
StartingGearPrototype gear,
HumanoidCharacterProfile? profile)
{
EnsureComp<WizardComponent>(mob);
profile ??= HumanoidCharacterProfile.RandomWithSpecies();
_humanoid.LoadProfile(mob, profile);
_metaData.SetEntityName(mob, profile.Name);
_stationSpawning.EquipStartingGear(mob, gear, profile);
_npcFaction.RemoveFaction(mob, "NanoTrasen", false);
_npcFaction.AddFaction(mob, "Wizard");
}
private void SpawnWizard(ICommonSession? session, WizardRuleComponent component, bool spawnGhostRoles = true)
{
if (component.ShuttleMap is not {Valid: true} mapUid)
return;
var spawn = new EntityCoordinates();
foreach (var (_, meta, xform) in EntityQuery<SpawnPointComponent, MetaDataComponent, TransformComponent>(true))
{
if (meta.EntityPrototype?.ID != component.SpawnPointProto.Id)
continue;
if (xform.ParentUid != component.ShuttleMap)
continue;
spawn = xform.Coordinates;
break;
}
//Fallback, spawn at the centre of the map
if (spawn == new EntityCoordinates())
{
spawn = Transform(mapUid).Coordinates;
_sawmill.Warning("Fell back to default spawn for wizard!");
}
var wizardAntag = _prototypeManager.Index(component.WizardRoleProto);
//If a session is available, spawn mob and transfer mind into it
if (session != null)
{
var profile =
_prefs.GetPreferences(session.UserId).SelectedCharacter as HumanoidCharacterProfile;
profile ??= HumanoidCharacterProfile.RandomWithSpecies();
var name = profile.Name;
if (!_prototypeManager.TryIndex(profile.Species, out SpeciesPrototype? species))
{
species = _prototypeManager.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies);
}
var mob = Spawn(species.Prototype, spawn);
if (!_prototypeManager.TryIndex(component.StartingGear, out var gear))
{
_sawmill.Error("Failed to load wizard gear prototype");
return;
}
SetupWizardEntity(mob, component.Points, gear, profile);
var newMind = _mind.CreateMind(session.UserId, name);
_mind.SetUserId(newMind, session.UserId);
AddRole(newMind.Owner, newMind.Comp, component);
_mind.TransferTo(newMind, mob);
}
//Otherwise, spawn as a ghost role
else if (spawnGhostRoles)
{
var spawnPoint = Spawn(component.GhostSpawnPointProto, spawn);
var ghostRole = EnsureComp<GhostRoleComponent>(spawnPoint);
EnsureComp<GhostRoleMobSpawnerComponent>(spawnPoint);
ghostRole.RoleName = Loc.GetString(wizardAntag.Name);
ghostRole.RoleDescription = Loc.GetString(wizardAntag.Objective);
var wizardSpawner = EnsureComp<WizardSpawnerComponent>(spawnPoint);
//TODO: maybe other params
}
}
private void NotifyWizard(ICommonSession session, WizardComponent wizard, WizardRuleComponent wizardRule)
{
if (wizardRule.TargetStation is not { } station)
return;
_antagSelection.SendBriefing(session, Loc.GetString("wizard-welcome", ("station", station)), Color.Aqua, null);
}
/// <summary>
/// Spawn wizard ghost role if this gamerule was started mid round
/// </summary>
private void SpawnWizardGhostRole(EntityUid uid, WizardRuleComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (!SpawnMap((uid, component)))
{
_sawmill.Info("Failed to load map for wizard");
return;
}
ICommonSession? session = null;
SpawnWizard(session, component, true);
}
}

View File

@@ -0,0 +1,21 @@
using Content.Shared.Roles;
using Robust.Shared.Prototypes;
namespace Content.Server._White.Wizard;
[RegisterComponent]
public sealed partial class WizardSpawnerComponent : Component
{
[DataField("name")]
public string Name = "Ololo The Balls' Twister";
[DataField("points")]
public int Points = 10;
[DataField("startingGear")]
public ProtoId<StartingGearPrototype> StartingGear = "WizardGear";
[DataField]
public ProtoId<AntagPrototype> WizardRoleProto = "WizardRole";
}