<commit_before>﻿using LunaClient.VesselIgnore;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using UniLinq;

namespace LunaClient.Systems.VesselProtoSys
{
    public class VesselToProtoRefresh
    {
        private static readonly FieldInfo CrewField = typeof(ProtoVessel).GetField("crew", BindingFlags.Instance | BindingFlags.NonPublic);
        
        /// <summary>
        /// Here we refresh the protovessel based on a vessel. 
        /// Vessel -----------> Protovessel
        /// This method is much better than calling vessel.BackupVessel() as it does not generate garbage.
        /// Also it returns true or false if there are substantial changes in the vessel that require updating the whole definition to the server and clients.
        /// </summary>
        public static bool RefreshVesselProto(Vessel vessel)
        {
            return RefreshVesselProto(vessel, vessel.protoVessel);
        }

        /// <summary>
        /// Here we refresh the protovessel based on a vessel. 
        /// Vessel -----------> Protovessel
        /// This method is much better than calling vessel.BackupVessel() as it does not generate garbage.
        /// Also it returns true or false if there are substantial changes in the vessel that require updating the whole definition to the server and clients.
        /// </summary>
        public static bool RefreshVesselProto(Vessel vessel, ProtoVessel protoVesselToRefresh)
        {
            if (vessel.protoVessel == null)
            {
                vessel.protoVessel = new ProtoVessel(vessel);
                return true;
            }

            var rootPartIndex = GetRootPartIndex(vessel);
            var vesselHasChanges = vessel.Landed && !vessel.protoVessel.landed || vessel.Splashed && !vessel.protoVessel.splashed ||
                vessel.currentStage != vessel.protoVessel.stage || vessel.protoVessel.rootIndex != rootPartIndex;

            vessel.protoVessel.vesselRef = vessel;
            vessel.protoVessel.vesselRef.protoVessel = vessel.protoVessel;
            vessel.protoVessel.vesselID = vessel.id;

            vessel.protoVessel.orbitSnapShot.semiMajorAxis = vessel.orbit.semiMajorAxis;
            vessel.protoVessel.orbitSnapShot.eccentricity = vessel.orbit.eccentricity;
            vessel.protoVessel.orbitSnapShot.inclination = vessel.orbit.inclination;
            vessel.protoVessel.orbitSnapShot.argOfPeriapsis = vessel.orbit.argumentOfPeriapsis;
            vessel.protoVessel.orbitSnapShot.LAN = vessel.orbit.LAN;
            vessel.protoVessel.orbitSnapShot.meanAnomalyAtEpoch = vessel.orbit.meanAnomalyAtEpoch;
            vessel.protoVessel.orbitSnapShot.epoch = vessel.orbit.epoch;
            vessel.protoVessel.orbitSnapShot.ReferenceBodyIndex = FlightGlobals.Bodies.IndexOf(vessel.orbit.referenceBody);

            vessel.protoVessel.refTransform = vessel.referenceTransformId;
            vessel.protoVessel.vesselType = vessel.vesselType;
            vessel.protoVessel.situation = vessel.situation;
            vessel.protoVessel.landed = vessel.Landed;
            vessel.protoVessel.landedAt = vessel.landedAt;
            vessel.protoVessel.displaylandedAt = vessel.displaylandedAt;
            vessel.protoVessel.splashed = vessel.Splashed;
            vessel.protoVessel.vesselName = vessel.vesselName;
            vessel.protoVessel.missionTime = vessel.missionTime;
            vessel.protoVessel.launchTime = vessel.launchTime;
            vessel.protoVessel.lastUT = vessel.lastUT;
            vessel.protoVessel.autoClean = vessel.AutoClean;
            vessel.protoVessel.autoCleanReason = vessel.AutoCleanReason;
            vessel.protoVessel.wasControllable = vessel.IsControllable;

            vessel.protoVessel.CoM = vessel.localCoM;
            vessel.protoVessel.latitude = vessel.latitude;
            vessel.protoVessel.longitude = vessel.longitude;
            vessel.protoVessel.altitude = vessel.altitude;
            vessel.protoVessel.height = vessel.heightFromTerrain;
            vessel.protoVessel.normal = vessel.terrainNormal;
            vessel.protoVessel.rotation = vessel.srfRelRotation;
            vessel.protoVessel.stage = vessel.currentStage;
            vessel.protoVessel.persistent = vessel.isPersistent;

            vesselHasChanges |= RefreshParts(vessel);
            vesselHasChanges |= RefreshCrew(vessel);

            RefreshActionGroups(vessel);

            return vesselHasChanges;
        }

        /// <summary>
        /// Refresh the action groups of the protovessel based on the values of the vessel.
        /// Changes to action groups do not trigger a change to "vesselHasChanges" as the
        /// action groups are sent using the VesselUpdateSys
        /// </summary>
        private static void RefreshActionGroups(Vessel vessel)
        {
            if (vessel.protoVessel.vesselRef.ActionGroups.groups.Count != vessel.protoVessel.actionGroups.CountValues)
            {
                vessel.protoVessel.actionGroups.ClearData();
                vessel.protoVessel.vesselRef.ActionGroups.Save(vessel.protoVessel.actionGroups);
            }
            else
            {
                for (var i = 0; i < vessel.protoVessel.vesselRef.ActionGroups.groups.Count; i++)
                {
                    var currentVal = vessel.protoVessel.vesselRef.ActionGroups.groups[i];
                    var protoVal = vessel.protoVessel.actionGroups.values[i].value.Substring(0, vessel.protoVessel.actionGroups.values[i].value.IndexOf(",", StringComparison.Ordinal));

                    if (!bool.TryParse(protoVal, out var boolProtoVal) || currentVal != boolProtoVal)
                    {
                        vessel.protoVessel.actionGroups.values[i].value = string.Concat(
                            vessel.protoVessel.vesselRef.ActionGroups.groups[i].ToString(), ", ",
                            vessel.protoVessel.vesselRef.ActionGroups.cooldownTimes[i].ToString(CultureInfo.InvariantCulture));
                    }
                }
            }
        }

        /// <summary>
        /// Refreshes the crew from the vessel and returns true if there's a change
        /// </summary>
        private static bool RefreshCrew(Vessel vessel)
        {
            if (vessel.crewedParts != vessel.protoVessel?.crewedParts || vessel.crewableParts != vessel.protoVessel?.crewableParts)
            {
                ((List<ProtoCrewMember>)CrewField?.GetValue(vessel.protoVessel))?.Clear();
                vessel.protoVessel?.RebuildCrewCounts();
                return true;
            }

            return false;
        }

        /// <summary>
        /// Refreshes protovessel parts, protovessel parts modules and protovessel parts resources.
        /// Will return true if there's a change in the part count or a part module has changed
        /// Will NOT return true if the change is only in a part resource
        /// </summary>
        private static bool RefreshParts(Vessel vessel)
        {
            if (vessel.parts.Count != vessel.protoVessel.protoPartSnapshots.Count)
            {
                vessel.protoVessel.protoPartSnapshots.Clear();

                foreach (var part in vessel.parts.Where(p => p.State != PartStates.DEAD))
                {
                    vessel.protoVessel.protoPartSnapshots.Add(new ProtoPartSnapshot(part, vessel.protoVessel));
                }

                foreach (var part in vessel.protoVessel.protoPartSnapshots)
                {
                    part.storePartRefs();
                }<commit_msg>Fixed detection of vessel crew changes<commit_after>﻿using LunaClient.VesselIgnore;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using UniLinq;

namespace LunaClient.Systems.VesselProtoSys
{
    public class VesselToProtoRefresh
    {
        private static readonly FieldInfo CrewField = typeof(ProtoVessel).GetField("crew", BindingFlags.Instance | BindingFlags.NonPublic);
        
        /// <summary>
        /// Here we refresh the protovessel based on a vessel. 
        /// Vessel -----------> Protovessel
        /// This method is much better than calling vessel.BackupVessel() as it does not generate garbage.
        /// Also it returns true or false if there are substantial changes in the vessel that require updating the whole definition to the server and clients.
        /// </summary>
        public static bool RefreshVesselProto(Vessel vessel)
        {
            return RefreshVesselProto(vessel, vessel.protoVessel);
        }

        /// <summary>
        /// Here we refresh the protovessel based on a vessel. 
        /// Vessel -----------> Protovessel
        /// This method is much better than calling vessel.BackupVessel() as it does not generate garbage.
        /// Also it returns true or false if there are substantial changes in the vessel that require updating the whole definition to the server and clients.
        /// </summary>
        public static bool RefreshVesselProto(Vessel vessel, ProtoVessel protoVesselToRefresh)
        {
            if (vessel.protoVessel == null)
            {
                vessel.protoVessel = new ProtoVessel(vessel);
                return true;
            }

            var rootPartIndex = GetRootPartIndex(vessel);
            var vesselHasChanges = vessel.Landed && !vessel.protoVessel.landed || vessel.Splashed && !vessel.protoVessel.splashed ||
                vessel.currentStage != vessel.protoVessel.stage || vessel.protoVessel.rootIndex != rootPartIndex;

            vessel.protoVessel.vesselRef = vessel;
            vessel.protoVessel.vesselRef.protoVessel = vessel.protoVessel;
            vessel.protoVessel.vesselID = vessel.id;

            vessel.protoVessel.orbitSnapShot.semiMajorAxis = vessel.orbit.semiMajorAxis;
            vessel.protoVessel.orbitSnapShot.eccentricity = vessel.orbit.eccentricity;
            vessel.protoVessel.orbitSnapShot.inclination = vessel.orbit.inclination;
            vessel.protoVessel.orbitSnapShot.argOfPeriapsis = vessel.orbit.argumentOfPeriapsis;
            vessel.protoVessel.orbitSnapShot.LAN = vessel.orbit.LAN;
            vessel.protoVessel.orbitSnapShot.meanAnomalyAtEpoch = vessel.orbit.meanAnomalyAtEpoch;
            vessel.protoVessel.orbitSnapShot.epoch = vessel.orbit.epoch;
            vessel.protoVessel.orbitSnapShot.ReferenceBodyIndex = FlightGlobals.Bodies.IndexOf(vessel.orbit.referenceBody);

            vessel.protoVessel.refTransform = vessel.referenceTransformId;
            vessel.protoVessel.vesselType = vessel.vesselType;
            vessel.protoVessel.situation = vessel.situation;
            vessel.protoVessel.landed = vessel.Landed;
            vessel.protoVessel.landedAt = vessel.landedAt;
            vessel.protoVessel.displaylandedAt = vessel.displaylandedAt;
            vessel.protoVessel.splashed = vessel.Splashed;
            vessel.protoVessel.vesselName = vessel.vesselName;
            vessel.protoVessel.missionTime = vessel.missionTime;
            vessel.protoVessel.launchTime = vessel.launchTime;
            vessel.protoVessel.lastUT = vessel.lastUT;
            vessel.protoVessel.autoClean = vessel.AutoClean;
            vessel.protoVessel.autoCleanReason = vessel.AutoCleanReason;
            vessel.protoVessel.wasControllable = vessel.IsControllable;

            vessel.protoVessel.CoM = vessel.localCoM;
            vessel.protoVessel.latitude = vessel.latitude;
            vessel.protoVessel.longitude = vessel.longitude;
            vessel.protoVessel.altitude = vessel.altitude;
            vessel.protoVessel.height = vessel.heightFromTerrain;
            vessel.protoVessel.normal = vessel.terrainNormal;
            vessel.protoVessel.rotation = vessel.srfRelRotation;
            vessel.protoVessel.stage = vessel.currentStage;
            vessel.protoVessel.persistent = vessel.isPersistent;

            vesselHasChanges |= RefreshParts(vessel);
            vesselHasChanges |= RefreshCrew(vessel);

            RefreshActionGroups(vessel);

            return vesselHasChanges;
        }

        /// <summary>
        /// Refresh the action groups of the protovessel based on the values of the vessel.
        /// Changes to action groups do not trigger a change to "vesselHasChanges" as the
        /// action groups are sent using the VesselUpdateSys
        /// </summary>
        private static void RefreshActionGroups(Vessel vessel)
        {
            if (vessel.protoVessel.vesselRef.ActionGroups.groups.Count != vessel.protoVessel.actionGroups.CountValues)
            {
                vessel.protoVessel.actionGroups.ClearData();
                vessel.protoVessel.vesselRef.ActionGroups.Save(vessel.protoVessel.actionGroups);
            }
            else
            {
                for (var i = 0; i < vessel.protoVessel.vesselRef.ActionGroups.groups.Count; i++)
                {
                    var currentVal = vessel.protoVessel.vesselRef.ActionGroups.groups[i];
                    var protoVal = vessel.protoVessel.actionGroups.values[i].value.Substring(0, vessel.protoVessel.actionGroups.values[i].value.IndexOf(",", StringComparison.Ordinal));

                    if (!bool.TryParse(protoVal, out var boolProtoVal) || currentVal != boolProtoVal)
                    {
                        vessel.protoVessel.actionGroups.values[i].value = string.Concat(
                            vessel.protoVessel.vesselRef.ActionGroups.groups[i].ToString(), ", ",
                            vessel.protoVessel.vesselRef.ActionGroups.cooldownTimes[i].ToString(CultureInfo.InvariantCulture));
                    }
                }
            }
        }

        /// <summary>
        /// Refreshes the crew from the vessel and returns true if there's a change
        /// </summary>
        private static bool RefreshCrew(Vessel vessel)
        {
            if (vessel.GetCrewCount() != vessel.protoVessel.GetVesselCrew().Count || vessel.crewedParts != vessel.protoVessel?.crewedParts || 
                vessel.crewableParts != vessel.protoVessel?.crewableParts)
            {
                ((List<ProtoCrewMember>)CrewField?.GetValue(vessel.protoVessel))?.Clear();
                vessel.protoVessel?.RebuildCrewCounts();
                return true;
            }

            return false;
        }

        /// <summary>
        /// Refreshes protovessel parts, protovessel parts modules and protovessel parts resources.
        /// Will return true if there's a change in the part count or a part module has changed
        /// Will NOT return true if the change is only in a part resource
        /// </summary>
        private static bool RefreshParts(Vessel vessel)
        {
            if (vessel.parts.Count != vessel.protoVessel.protoPartSnapshots.Count)
            {
                vessel.protoVessel.protoPartSnapshots.Clear();

                foreach (var part in vessel.parts.Where(p => p.State != PartStates.DEAD))
                {
                    vessel.protoVessel.protoPartSnapshots.Add(new ProtoPartSnapshot(part, vessel.protoVessel));
                }

                foreach (var part in vessel.protoVessel.protoPartSnapshots)
                {
                    part.storePartRefs();
                }
{'fraction_non_alphanumeric': 0.08803802826072801, 'fraction_numerical': 0.0002604675392329231, 'mean_word_length': 2.171174891596118, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4259397', 'n_tokens_mistral': 4362, 'n_tokens_neox': 4057, 'n_words': 829}