using System.Collections.Generic; using Game.Messages; using Game.State; using Model; using S3.Core; namespace S3.Modules.QuickActions; static class EndGearActions { public static bool TryNeighbor(Car car, Car.LogicalEnd end, out Car other, out Car.LogicalEnd otherEnd) { otherEnd = end == Car.LogicalEnd.A ? Car.LogicalEnd.B : Car.LogicalEnd.A; other = null!; try { return car.TryGetAdjacentCar(end, out other) && other != null; } catch { other = null!; return false; } } public static bool CanDisconnectAll(Car car, Car.LogicalEnd end) { return car[end].IsCoupled; } public static bool CanToggleCouple(Car car, Car.LogicalEnd end) { if (car[end].IsCoupled) return true; return TryNeighbor(car, end, out _, out _); } public static bool CanToggleAir(Car car, Car.LogicalEnd end) { if (car[end].IsAirConnected) return true; return car[end].IsCoupled && TryNeighbor(car, end, out _, out _); } public static void ToggleCouple(Car car, Car.LogicalEnd end) { bool couple = !car[end].IsCoupled; bool hasNeighbor = TryNeighbor(car, end, out Car other, out Car.LogicalEnd otherEnd); if (couple && !hasNeighbor) return; try { car.ApplyEndGearChange(end, Car.EndGearStateKey.IsCoupled, couple); if (hasNeighbor) other.ApplyEndGearChange(otherEnd, Car.EndGearStateKey.IsCoupled, couple); } catch (System.Exception e) { Log.Warn($"[quickactions] couple toggle failed: {e.Message}"); } } public static void ToggleAir(Car car, Car.LogicalEnd end) { bool connect = !car[end].IsAirConnected; if (connect && !car[end].IsCoupled) return; Car? other = null; try { other = car.CoupledTo(end) ?? car.AirConnectedTo(end); } catch { /* fall through */ } if (other == null && !TryNeighbor(car, end, out other, out _)) return; if (other == null) return; try { var msg = new SetGladhandsConnected(car.id, other.id, connect); if (!StateManager.CheckAuthorizedToSendMessage(msg)) return; StateManager.ApplyLocal(msg); } catch (System.Exception e) { Log.Warn($"[quickactions] air toggle failed: {e.Message}"); } } public static void ToggleCock(Car car, Car.LogicalEnd end) { float next = car[end].IsAnglecockOpen ? 0f : 1f; try { car.ApplyEndGearChange(end, Car.EndGearStateKey.Anglecock, next); if (car[end].IsCoupled && TryNeighbor(car, end, out Car other, out Car.LogicalEnd otherEnd)) other.ApplyEndGearChange(otherEnd, Car.EndGearStateKey.Anglecock, next); } catch (System.Exception e) { Log.Warn($"[quickactions] anglecock toggle failed: {e.Message}"); } } public static string Preview(Car car, Car.LogicalEnd end, HoverSlot slot) { return slot switch { HoverSlot.Cut => CutPreview(CountCut(car, end)), _ => "", }; } static string CutPreview(int n) { if (n <= 0) return ""; return $"Cut out {n} Cars"; } public static int CountCut(Car car, Car.LogicalEnd end) { if (!TryNeighbor(car, end, out Car other, out _)) return 0; return CollectAway(other, car).Count; } public static void DisconnectAll(Car car, Car.LogicalEnd end) { if (!TryNeighbor(car, end, out Car other, out Car.LogicalEnd otherEnd)) return; List detached = CollectAway(other, car); var s = QuickActionsModule.Settings; try { car.ApplyEndGearChange(end, Car.EndGearStateKey.Anglecock, 0f); other.ApplyEndGearChange(otherEnd, Car.EndGearStateKey.Anglecock, 0f); } catch (System.Exception e) { Log.Warn($"[quickactions] drop-all anglecock failed: {e.Message}"); } if (car[end].IsAirConnected) { try { var msg = new SetGladhandsConnected(car.id, other.id, false); if (StateManager.CheckAuthorizedToSendMessage(msg)) StateManager.ApplyLocal(msg); } catch (System.Exception e) { Log.Warn($"[quickactions] drop-all air failed: {e.Message}"); } } try { car.ApplyEndGearChange(end, Car.EndGearStateKey.IsCoupled, false); other.ApplyEndGearChange(otherEnd, Car.EndGearStateKey.IsCoupled, false); } catch (System.Exception e) { Log.Warn($"[quickactions] drop-all couple failed: {e.Message}"); } if (!s.dropHandbrakeOnCut) return; foreach (Car c in detached) { if (s.dropHandbrakeExcludeLocos && c is BaseLocomotive) continue; try { c.SetHandbrake(true); } catch (System.Exception e) { Log.Warn($"[quickactions] drop-all handbrake {c.DisplayName}: {e.Message}"); } } } public static bool IsMadeUp(Car car, Car.LogicalEnd end) { try { var g = car[end]; return g.IsCoupled && g.IsAirConnected && g.IsAnglecockOpen; } catch { return false; } } public static bool CanToggleJoint(Car car, Car.LogicalEnd end) { try { if (car[end].IsCoupled) return true; return TryNeighbor(car, end, out _, out _); } catch { return false; } } public static void ToggleJoint(Car car, Car.LogicalEnd end) { if (IsMadeUp(car, end)) DisconnectAll(car, end); else MakeUp(car, end); } public static void MakeUp(Car car, Car.LogicalEnd end) { bool hasNeighbor = TryNeighbor(car, end, out Car other, out Car.LogicalEnd otherEnd); if (!hasNeighbor) { try { if (!car[end].IsCoupled) return; } catch { return; } } try { if (!car[end].IsCoupled) { car.ApplyEndGearChange(end, Car.EndGearStateKey.IsCoupled, true); if (hasNeighbor) other.ApplyEndGearChange(otherEnd, Car.EndGearStateKey.IsCoupled, true); } } catch (System.Exception e) { Log.Warn($"[quickactions] make-up couple failed: {e.Message}"); } Car? airOther = null; try { airOther = car.CoupledTo(end) ?? other; } catch { airOther = other; } if (airOther != null) { try { if (!car[end].IsAirConnected) { var msg = new SetGladhandsConnected(car.id, airOther.id, true); if (StateManager.CheckAuthorizedToSendMessage(msg)) StateManager.ApplyLocal(msg); } } catch (System.Exception e) { Log.Warn($"[quickactions] make-up air failed: {e.Message}"); } } try { car.ApplyEndGearChange(end, Car.EndGearStateKey.Anglecock, 1f); if (hasNeighbor) other.ApplyEndGearChange(otherEnd, Car.EndGearStateKey.Anglecock, 1f); } catch (System.Exception e) { Log.Warn($"[quickactions] make-up Anglecock failed: {e.Message}"); } } static List CollectAway(Car start, Car blocked) { var list = new List(); var seen = new HashSet { blocked }; var stack = new Stack(); stack.Push(start); while (stack.Count > 0) { Car c = stack.Pop(); if (!seen.Add(c)) continue; list.Add(c); if (TryNeighbor(c, Car.LogicalEnd.A, out Car a, out _) && !seen.Contains(a)) stack.Push(a); if (TryNeighbor(c, Car.LogicalEnd.B, out Car b, out _) && !seen.Contains(b)) stack.Push(b); } return list; } }