| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- using System.Linq;
- using Content.Server.GameTicking.Rules.Components;
- using Content.Server.Chat.Managers;
- using Content.Server.Popups;
- using Content.Shared.GameTicking.Components;
- using Content.Shared.Mobs;
- using Content.Shared.Mobs.Components;
- using Content.Shared.NPC.Components;
- using Content.Shared.Interaction;
- using Content.Shared.Hands.EntitySystems;
- using Robust.Shared.Timing;
- using Content.Server.KillTracking;
- using Content.Shared.NPC.Systems;
- using Content.Server.RoundEnd;
- namespace Content.Server.GameTicking.Rules;
- /// <summary>
- /// Handles the Valley gamemode points system for Blugoslavia vs Insurgents
- /// </summary>
- public sealed class ValleyPointsRuleSystem : GameRuleSystem<ValleyPointsComponent>
- {
- [Dependency] private readonly ILogManager _logManager = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IChatManager _chatManager = default!;
- [Dependency] private readonly PopupSystem _popup = default!;
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly SharedHandsSystem _hands = default!;
- [Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
- [Dependency] private readonly NpcFactionSystem _factionSystem = default!; // Added dependency
- private ISawmill _sawmill = default!;
- private TimeSpan _lastSupplyBoxCheck = TimeSpan.Zero;
- private const float SupplyBoxCheckInterval = 30f; // Check every 30 seconds
- public override void Initialize()
- {
- base.Initialize();
- _sawmill = _logManager.GetSawmill("valley-points");
- SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
- SubscribeLocalEvent<CaptureAreaComponent, ComponentStartup>(OnCaptureAreaStartup);
- SubscribeLocalEvent<KillReportedEvent>(OnKillReported);
- }
- protected override void Started(EntityUid uid, ValleyPointsComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
- {
- component.GameStartTime = _timing.CurTime;
- component.LastCheckpointBonusTime = _timing.CurTime;
- component.LastScoreAnnouncementTime = _timing.CurTime;
- _lastSupplyBoxCheck = _timing.CurTime;
- // Initialize checkpoints immediately
- InitializeCheckpoints(component);
- // Delay civilian count initialization to allow spawning
- Timer.Spawn(TimeSpan.FromSeconds(5), () => InitializeCivilianCount(component));
- _sawmill.Info("Valley gamemode started - 50 minute timer begins now");
- AnnounceToAll("Valley conflict has begun! Blugoslavia and Insurgents fight for control.");
- }
- private void OnCaptureAreaStartup(EntityUid uid, CaptureAreaComponent component, ComponentStartup args)
- {
- if (HasComp<ValleyCheckpointComponent>(uid))
- {
- component.CapturableFactions.Clear();
- component.CapturableFactions.Add("Blugoslavia");
- component.CapturableFactions.Add("Insurgents");
- }
- }
- private void OnMobStateChanged(MobStateChangedEvent args)
- {
- if (args.NewMobState == MobState.Dead && args.OldMobState == MobState.Alive)
- {
- if (TryComp<NpcFactionMemberComponent>(args.Target, out var factionMember) &&
- factionMember.Factions.Any(f => f == "Blugoslavia"))
- {
- var ruleQuery = EntityQueryEnumerator<ValleyPointsComponent>();
- if (ruleQuery.MoveNext(out var ruleEntity, out _))
- {
- AwardInsurgentKill(ruleEntity);
- }
- }
- }
- }
- private void InitializeCivilianCount(ValleyPointsComponent component)
- {
- // Count civilian NPCs on the map
- var civilianCount = 0;
- var query = EntityQueryEnumerator<NpcFactionMemberComponent>();
- while (query.MoveNext(out _, out var faction))
- {
- if (faction.Factions.Any(f => f == "Civilian"))
- civilianCount++;
- }
- component.InitialCivilianCount = civilianCount;
- component.TotalCivilianNPCs = civilianCount;
- _sawmill.Info($"Initialized with {civilianCount} civilian NPCs");
- }
- private void InitializeCheckpoints(ValleyPointsComponent valley)
- {
- var checkpointQuery = EntityQueryEnumerator<ValleyCheckpointComponent, CaptureAreaComponent>();
- while (checkpointQuery.MoveNext(out var uid, out var checkpoint, out var area))
- {
- area.CapturableFactions.Clear();
- area.CapturableFactions.Add("Blugoslavia");
- area.CapturableFactions.Add("Insurgents");
- _sawmill.Info($"Initialized Valley checkpoint: {area.Name}");
- }
- }
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
- var query = EntityQueryEnumerator<ValleyPointsComponent, GameRuleComponent>();
- while (query.MoveNext(out var uid, out var valley, out var gameRule))
- {
- if (!GameTicker.IsGameRuleAdded(uid, gameRule))
- continue;
- if (valley.GameEnded)
- continue;
- UpdateCheckpointHolding(valley);
- UpdateSupplyBoxSecuring(valley);
- CheckCaptureAreaControl(valley);
- CheckSupplyBoxDeliveries(valley);
- CheckScoreAnnouncement(valley);
- CheckWinConditions(uid, valley);
- CheckTimeLimit(uid, valley);
- }
- }
- private void CheckSupplyBoxDeliveries(ValleyPointsComponent valley)
- {
- var currentTime = _timing.CurTime;
- // Only check every 30 seconds
- if ((currentTime - _lastSupplyBoxCheck).TotalSeconds < SupplyBoxCheckInterval)
- return;
- _lastSupplyBoxCheck = currentTime;
- // Check all capture areas for nearby supply boxes
- var areaQuery = EntityQueryEnumerator<CaptureAreaComponent>();
- while (areaQuery.MoveNext(out var areaUid, out var area))
- {
- var areaPos = _transform.GetMapCoordinates(areaUid);
- var nearbyEntities = _lookup.GetEntitiesInRange(areaPos, 3f);
- foreach (var entity in nearbyEntities)
- {
- if (TryComp<ValleySupplyBoxComponent>(entity, out var supplyBox) && !supplyBox.Delivered)
- {
- // Check if this is a checkpoint controlled by Blugoslavia
- if (HasComp<ValleyCheckpointComponent>(areaUid) && area.Controller == "Blugoslavia")
- {
- ProcessBlugoslavianSupplyDelivery(valley, entity, areaUid, area);
- }
- // Check if this is the Insurgent base
- else if (IsInsurgentBase(areaUid, area))
- {
- ProcessInsurgentSupplyTheft(valley, entity, areaUid, area);
- }
- }
- }
- }
- }
- private bool IsInsurgentBase(EntityUid areaUid, CaptureAreaComponent area)
- {
- // Check if this area is marked as insurgent base
- return area.Name.ToLower().Contains("insurgent");
- }
- private void ProcessBlugoslavianSupplyDelivery(ValleyPointsComponent valley, EntityUid supplyBox, EntityUid checkpoint, CaptureAreaComponent area)
- {
- if (!TryComp<ValleySupplyBoxComponent>(supplyBox, out var boxComp))
- return;
- boxComp.Delivered = true;
- boxComp.SecuringAtCheckpoint = checkpoint;
- // Start securing timer
- valley.SecuringSupplyBoxes[supplyBox] = _timing.CurTime;
- if (TryComp<ValleyCheckpointComponent>(checkpoint, out var checkpointComp))
- {
- checkpointComp.SecuringBoxes.Add(supplyBox);
- }
- _sawmill.Info($"Supply box delivery started at {area.Name}, securing for {valley.SupplyBoxSecureTime} seconds");
- }
- private void ProcessInsurgentSupplyTheft(ValleyPointsComponent valley, EntityUid supplyBox, EntityUid baseArea, CaptureAreaComponent area)
- {
- if (!TryComp<ValleySupplyBoxComponent>(supplyBox, out var boxComp))
- return;
- boxComp.Delivered = true;
- // Award points immediately for insurgent theft
- valley.InsurgentPoints += valley.StolenSupplyBoxPoints;
- _sawmill.Info($"Insurgents awarded {valley.StolenSupplyBoxPoints} points for stolen supply box at {area.Name}. Total: {valley.InsurgentPoints}");
- AnnounceToAll($"Insurgents: +{valley.StolenSupplyBoxPoints} points (Supply Theft at {area.Name}) - Total: {valley.InsurgentPoints}");
- // Remove the supply box
- QueueDel(supplyBox);
- }
- private void CheckCaptureAreaControl(ValleyPointsComponent valley)
- {
- var checkpointQuery = EntityQueryEnumerator<ValleyCheckpointComponent, CaptureAreaComponent>();
- while (checkpointQuery.MoveNext(out var uid, out var checkpoint, out var area))
- {
- var blugoslaviaControlled = area.Controller == "Blugoslavia";
- if (checkpoint.BlugoslaviaControlled != blugoslaviaControlled)
- {
- checkpoint.BlugoslaviaControlled = blugoslaviaControlled;
- SetCheckpointControl(valley, uid, blugoslaviaControlled);
- }
- }
- }
- public void SetCheckpointControl(ValleyPointsComponent valley, EntityUid checkpoint, bool blugoslaviaControlled)
- {
- if (blugoslaviaControlled)
- {
- if (!valley.BlugoslaviaHeldCheckpoints.Contains(checkpoint))
- {
- valley.BlugoslaviaHeldCheckpoints.Add(checkpoint);
- valley.CheckpointHoldStartTimes[checkpoint] = _timing.CurTime;
- _sawmill.Info($"Blugoslavia gained control of checkpoint {checkpoint}");
- }
- }
- else
- {
- if (valley.BlugoslaviaHeldCheckpoints.Contains(checkpoint))
- {
- valley.BlugoslaviaHeldCheckpoints.Remove(checkpoint);
- valley.CheckpointHoldStartTimes.Remove(checkpoint);
- _sawmill.Info($"Blugoslavia lost control of checkpoint {checkpoint}");
- }
- }
- }
- private void UpdateCheckpointHolding(ValleyPointsComponent valley)
- {
- var currentTime = _timing.CurTime;
- var checkpointsToAward = new List<EntityUid>();
- // Check individual checkpoint holding - award points every minute (60 seconds)
- foreach (var kvp in valley.CheckpointHoldStartTimes.ToList())
- {
- var checkpoint = kvp.Key;
- var startTime = kvp.Value;
- if ((currentTime - startTime).TotalSeconds >= 60f) // 1 minute instead of 5
- {
- checkpointsToAward.Add(checkpoint);
- valley.CheckpointHoldStartTimes[checkpoint] = currentTime;
- }
- }
- if (checkpointsToAward.Count > 0)
- {
- var pointsAwarded = checkpointsToAward.Count * 5; // 5 points per minute instead of 25 per 5 minutes
- valley.BlugoslaviaPoints += pointsAwarded;
- _sawmill.Info($"Blugoslavia awarded {pointsAwarded} points for holding {checkpointsToAward.Count} checkpoints");
- AnnounceToAll($"Blugoslavia: +{pointsAwarded} points (Checkpoint Control) - Total: {valley.BlugoslaviaPoints}");
- }
- // Check for all checkpoints bonus - still every 5 minutes but reduced frequency
- if (valley.BlugoslaviaHeldCheckpoints.Count >= 4 && // Assuming 4 checkpoints total
- (currentTime - valley.LastCheckpointBonusTime).TotalSeconds >= valley.CheckpointBonusInterval)
- {
- valley.BlugoslaviaPoints += valley.AllCheckpointsBonusPoints;
- valley.LastCheckpointBonusTime = currentTime;
- _sawmill.Info($"Blugoslavia awarded {valley.AllCheckpointsBonusPoints} bonus points for controlling all checkpoints");
- AnnounceToAll($"Blugoslavia: +{valley.AllCheckpointsBonusPoints} points (All Checkpoints Bonus) - Total: {valley.BlugoslaviaPoints}");
- }
- }
- private void UpdateSupplyBoxSecuring(ValleyPointsComponent valley)
- {
- var currentTime = _timing.CurTime;
- var securedBoxes = new List<EntityUid>();
- // Check all checkpoints for securing boxes
- var checkpointQuery = EntityQueryEnumerator<ValleyCheckpointComponent>();
- while (checkpointQuery.MoveNext(out var checkpointUid, out var checkpoint))
- {
- var boxesToRemove = new List<EntityUid>();
- foreach (var boxUid in checkpoint.SecuringBoxes)
- {
- if (!TryComp<ValleySupplyBoxComponent>(boxUid, out var boxComp))
- {
- boxesToRemove.Add(boxUid);
- continue;
- }
- // Check if box is still near the checkpoint
- var boxPos = _transform.GetMapCoordinates(boxUid);
- var checkpointPos = _transform.GetMapCoordinates(checkpointUid);
- if ((boxPos.Position - checkpointPos.Position).Length() > 3f)
- {
- // Box moved away, cancel securing
- boxesToRemove.Add(boxUid);
- boxComp.Delivered = false;
- boxComp.SecuringAtCheckpoint = null;
- _sawmill.Info("Supply box moved away from checkpoint, delivery cancelled");
- continue;
- }
- // Check if securing time has elapsed
- if (valley.SecuringSupplyBoxes.TryGetValue(boxUid, out var startTime) &&
- (currentTime - startTime).TotalSeconds >= valley.SupplyBoxSecureTime)
- {
- securedBoxes.Add(boxUid);
- boxesToRemove.Add(boxUid);
- }
- }
- // Remove boxes that are no longer securing
- foreach (var box in boxesToRemove)
- {
- checkpoint.SecuringBoxes.Remove(box);
- }
- }
- // Award points for secured boxes
- foreach (var box in securedBoxes)
- {
- valley.SecuringSupplyBoxes.Remove(box);
- valley.BlugoslaviaPoints += valley.SupplyBoxDeliveryPoints;
- _sawmill.Info($"Blugoslavia awarded {valley.SupplyBoxDeliveryPoints} points for secured supply box delivery");
- AnnounceToAll($"Blugoslavia: +{valley.SupplyBoxDeliveryPoints} points (Supply Delivery) - Total: {valley.BlugoslaviaPoints}");
- // Delete the secured box
- QueueDel(box);
- }
- }
- /// <summary>
- /// Award points to insurgents for killing a Blugoslavian soldier.
- /// </summary>
- public void AwardInsurgentKill(EntityUid ruleEntity)
- {
- if (!TryComp<ValleyPointsComponent>(ruleEntity, out var valley))
- return;
- valley.InsurgentPoints += valley.KillPoints;
- _sawmill.Info($"Insurgents awarded {valley.KillPoints} points for kill. Total: {valley.InsurgentPoints}");
- AnnounceToAll($"Insurgents: +{valley.KillPoints} points (Kill) - Total: {valley.InsurgentPoints}");
- }
- /// <summary>
- /// Award points to Blugoslavia for successfully escorting a convoy.
- /// </summary>
- public void AwardConvoyEscort(EntityUid ruleEntity)
- {
- if (!TryComp<ValleyPointsComponent>(ruleEntity, out var valley))
- return;
- valley.BlugoslaviaPoints += valley.ConvoyEscortPoints;
- _sawmill.Info($"Blugoslavia awarded {valley.ConvoyEscortPoints} points for convoy escort. Total: {valley.BlugoslaviaPoints}");
- AnnounceToAll($"Blugoslavia: +{valley.ConvoyEscortPoints} points (Convoy Escort) - Total: {valley.BlugoslaviaPoints}");
- }
- private void CheckWinConditions(EntityUid uid, ValleyPointsComponent valley)
- {
- if (valley.BlugoslaviaPoints >= valley.PointsToWin)
- {
- EndGame(uid, valley, "Blugoslavia");
- }
- else if (valley.InsurgentPoints >= valley.PointsToWin)
- {
- EndGame(uid, valley, "Insurgents");
- }
- }
- private void CheckTimeLimit(EntityUid uid, ValleyPointsComponent valley)
- {
- var elapsed = (_timing.CurTime - valley.GameStartTime).TotalMinutes;
- if (elapsed >= valley.MatchDurationMinutes)
- {
- // Determine winner by points
- if (valley.BlugoslaviaPoints > valley.InsurgentPoints)
- {
- EndGame(uid, valley, "Blugoslavia");
- }
- else if (valley.InsurgentPoints > valley.BlugoslaviaPoints)
- {
- EndGame(uid, valley, "Insurgents");
- }
- else
- {
- EndGame(uid, valley, "Draw");
- }
- }
- }
- private void EndGame(EntityUid uid, ValleyPointsComponent valley, string winner)
- {
- valley.GameEnded = true;
- var finalMessage = winner switch
- {
- "Blugoslavia" => $"VICTORY: Blugoslavia wins with {valley.BlugoslaviaPoints} points!",
- "Insurgents" => $"VICTORY: Insurgents win with {valley.InsurgentPoints} points!",
- "Draw" => $"DRAW: Match ended in a tie! Blugoslavia: {valley.BlugoslaviaPoints}, Insurgents: {valley.InsurgentPoints}",
- _ => "Match ended."
- };
- _sawmill.Info($"Valley gamemode ended: {finalMessage}");
- AnnounceToAll(finalMessage);
- _roundEndSystem.EndRound();
- }
- private string CheckUNObjectives(ValleyPointsComponent valley)
- {
- var civilianSurvivalRate = valley.TotalCivilianNPCs > 0
- ? (float)valley.AliveCivilianNPCs / valley.TotalCivilianNPCs
- : 1.0f;
- var query = EntityQueryEnumerator<CaptureAreaComponent>();
- while (query.MoveNext(out var uid, out var area))
- {
- if (area.Name == "UN Hospital")
- {
- if (area.Occupied == true)
- {
- valley.UNHospitalZoneControlled = false;
- }
- else
- {
- valley.UNHospitalZoneControlled = true;
- }
- }
- }
- var unSuccess = civilianSurvivalRate >= valley.RequiredCivilianSurvivalRate &&
- valley.UNHospitalZoneControlled &&
- valley.UNNeutralityMaintained;
- var unMessage = unSuccess
- ? $"UN OBJECTIVES COMPLETED: {civilianSurvivalRate:P0} civilian survival rate maintained, hospital zone secured, neutrality preserved."
- : $"UN OBJECTIVES FAILED: {civilianSurvivalRate:P0} civilian survival rate, hospital zone: {(valley.UNHospitalZoneControlled ? "Secured" : "Lost")}, neutrality: {(valley.UNNeutralityMaintained ? "Maintained" : "Violated")}";
- _sawmill.Info(unMessage);
- return unMessage;
- }
- private void AnnounceToAll(string message)
- {
- _chatManager.DispatchServerAnnouncement(message);
- }
- protected override void AppendRoundEndText(EntityUid uid, ValleyPointsComponent component, GameRuleComponent gameRule, ref RoundEndTextAppendEvent args)
- {
- if (component.BlugoslaviaPoints > component.InsurgentPoints)
- {
- args.AddLine($"[color=lime]Blugoslavia[/color] has won!");
- }
- else if (component.BlugoslaviaPoints < component.InsurgentPoints)
- {
- args.AddLine($"[color=lime]Insurgents[/color] have won!");
- }
- else
- {
- args.AddLine("The round ended in a [color=yellow]draw[/color]!");
- }
- args.AddLine("");
- args.AddLine($"Blugoslavia: {component.BlugoslaviaPoints} points");
- args.AddLine($"Insurgents: {component.InsurgentPoints} points");
- args.AddLine("");
- args.AddLine($"UN Objectives:");
- args.AddLine(CheckUNObjectives(component));
- }
- private void OnKillReported(ref KillReportedEvent ev)
- {
- var query = EntityQueryEnumerator<ValleyPointsComponent, GameRuleComponent>();
- while (query.MoveNext(out var uid, out var valley, out var rule))
- {
- if (!GameTicker.IsGameRuleActive(uid, rule))
- continue;
- // Check if UN member was involved (either as killer or victim) TODO: Check killer
- bool unInvolved = false;
- // Check if victim is UN
- if (TryComp<NpcFactionMemberComponent>(ev.Entity, out var victimFaction))
- {
- if (_factionSystem.IsMember(ev.Entity, "UnitedNations"))
- {
- unInvolved = true;
- }
- // If UN was involved, set neutrality to false
- if (unInvolved && valley.UNNeutralityMaintained)
- {
- valley.UNNeutralityMaintained = false;
- _sawmill.Info("UN neutrality violated - UN member involved in combat");
- AnnounceToAll("UN neutrality has been violated!");
- }
- // Award points to Insurgents for killing Blugoslavian soldiers
- if (TryComp<NpcFactionMemberComponent>(ev.Entity, out var deadFaction) &&
- deadFaction.Factions.Any(f => f == "Blugoslavia"))
- {
- valley.InsurgentPoints += valley.KillPoints;
- _sawmill.Info($"Insurgents awarded {valley.KillPoints} points for Blugoslavian kill. Total: {valley.InsurgentPoints}");
- AnnounceToAll($"Insurgents: +{valley.KillPoints} points (Kill) - Total: {valley.InsurgentPoints}");
- }
- }
- }
- }
- private void CheckScoreAnnouncement(ValleyPointsComponent valley)
- {
- var currentTime = _timing.CurTime;
- // Announce scores every 5 minutes (300 seconds)
- if ((currentTime - valley.LastScoreAnnouncementTime).TotalSeconds >= 300f)
- {
- valley.LastScoreAnnouncementTime = currentTime;
- var elapsedMinutes = (currentTime - valley.GameStartTime).TotalMinutes;
- var remainingMinutes = valley.MatchDurationMinutes - elapsedMinutes;
- var scoreMessage = $"SCORE UPDATE ({remainingMinutes:F0} minutes remaining): " +
- $"Blugoslavia: {valley.BlugoslaviaPoints} points | " +
- $"Insurgents: {valley.InsurgentPoints} points";
- _sawmill.Info($"Score announcement: {scoreMessage}");
- AnnounceToAll(scoreMessage);
- }
- }
- }
|