private static bool ProcessCoupleInternal(Guid vesselId, Guid coupledVesselId, uint partFlightId, uint coupledPartFlightId, CoupleTrigger trigger) { if (!VesselCommon.DoVesselChecks(vesselId)) { return(false); } //If the coupling is against our OWN vessel we must FORCE the loading var forceLoad = FlightGlobals.ActiveVessel && (FlightGlobals.ActiveVessel.id == vesselId || FlightGlobals.ActiveVessel.id == coupledVesselId); _dominantVessel = FlightGlobals.FindVessel(vesselId); if (_dominantVessel == null) { return(false); } if (!_dominantVessel.loaded && forceLoad) { _dominantVessel.Load(); } _weakVessel = FlightGlobals.FindVessel(coupledVesselId); if (_weakVessel == null) { return(false); } if (!_weakVessel.loaded && forceLoad) { _weakVessel.Load(); } var protoPart = _dominantVessel.protoVessel.GetProtoPart(partFlightId); var coupledProtoPart = _weakVessel.protoVessel.GetProtoPart(coupledPartFlightId); if (protoPart != null && coupledProtoPart != null) { if (protoPart.partRef && coupledProtoPart.partRef) { VesselCoupleSystem.Singleton.IgnoreEvents = true; //Remember! The weak vessel must couple with the DOMINANT vessel and not the other way around! switch (trigger) { case CoupleTrigger.DockingNode: var weakDockingNode = coupledProtoPart.partRef.FindModuleImplementing <ModuleDockingNode>(); if (weakDockingNode) { var dominantDockingNode = protoPart.partRef.FindModuleImplementing <ModuleDockingNode>(); if (dominantDockingNode) { weakDockingNode.DockToVessel(dominantDockingNode); } } break; case CoupleTrigger.GrappleNode: var grappleModule = coupledProtoPart.partRef.FindModuleImplementing <ModuleGrappleNode>(); if (grappleModule) { GrappleMethod.Invoke(grappleModule, new object[] { coupledProtoPart, protoPart }); } break; case CoupleTrigger.Kerbal: var kerbalEva = coupledProtoPart.partRef.FindModuleImplementing <KerbalEVA>(); if (kerbalEva) { var seat = KerbalSeatField.GetValue(kerbalEva) as KerbalSeat; if (seat) { kerbalEva.fsm.RunEvent(kerbalEva.On_seatBoard); } } break; case CoupleTrigger.Other: coupledProtoPart.partRef.Couple(protoPart.partRef); break; } VesselCoupleSystem.Singleton.IgnoreEvents = false; return(true); } } return(false); }
/// <summary> /// This method will take a vessel and update all it's parts and proto based on a protovessel we received /// Protovessel --------------> Vessel & ProtoVessel /// This way we avoid having to unload and reload a vessel with it's terrible performance /// </summary> public static void UpdateVesselPartsFromProtoVessel(Vessel vessel, ProtoVessel protoVessel, IEnumerable <uint> vesselPartsId = null) { if (vessel == null || protoVessel == null || vessel.state == Vessel.State.DEAD) { return; } if (vessel.id != protoVessel.vesselID) { LunaLog.LogError($"Tried to update a vessel id {vessel.id} with a protovessel of vessel id {protoVessel.vesselID}"); return; } var vesselProtoPartIds = vesselPartsId ?? protoVessel.protoPartSnapshots.Select(p => p.flightID); //If vessel is UNLOADED it won't have parts so we must take them from the proto... var vesselPartsIds = vessel.loaded ? vessel.parts.Select(p => p.flightID) : vessel.protoVessel.protoPartSnapshots.Select(p => p.flightID); var hasMissingparts = vesselProtoPartIds.Except(vesselPartsIds).Any(); if (hasMissingparts || !VesselCommon.IsSpectating && (!vessel.Landed && protoVessel.landed || !vessel.Splashed && protoVessel.splashed)) { //Reload the whole vessel if vessel lands/splashes as otherwise map view puts the vessel next to the other player. //Better to reload if has missing parts as creating them dinamically is a PIA VesselLoader.ReloadVessel(protoVessel); return; } var hasCrewChanges = false; //Never do vessel.protoVessel = protoVessel; not even if the vessel is not loaded as when it gets loaded the parts are created in the active vessel //and not on the target vessel //Run trough all the vessel parts and protoparts. //Vessel.parts will be empty if vessel is unloaded. var protoPartsToRemove = new List <ProtoPartSnapshot>(); for (var i = 0; i < vessel.protoVessel.protoPartSnapshots.Count; i++) { var protoPartToUpdate = vessel.protoVessel.protoPartSnapshots[i]; var partSnapshot = VesselCommon.FindProtoPartInProtovessel(protoVessel, protoPartToUpdate.flightID); if (partSnapshot == null) //Part does not exist in the protovessel definition so kill it { protoPartsToRemove.Add(protoPartToUpdate); continue; } AdjustCrewMembersInProtoPart(protoPartToUpdate, partSnapshot); protoPartToUpdate.state = partSnapshot.state; UpdatePartModulesInProtoPart(protoPartToUpdate, partSnapshot); UpdateProtoVesselResources(protoPartToUpdate, partSnapshot); var part = protoPartToUpdate.partRef; if (part != null) //Part can be null if the vessel is unloaded!! { //Remove or add crew members in given part and detect if there have been any change hasCrewChanges |= AdjustCrewMembersInPart(part, partSnapshot); //Set part "state" field... Important for fairings for example... StateField?.SetValue(part, partSnapshot.state); part.ResumeState = part.State; UpdatePartModules(partSnapshot, part); UpdateVesselResources(partSnapshot, part); UpdatePartFairings(partSnapshot, part); } } //Now kill both parts and protoparts that don't exist for (var i = 0; i < protoPartsToRemove.Count; i++) { //Part can be null if the vessel is unloaded. In this case, no need to kill it as it's already gone from the game. protoPartsToRemove[i].partRef?.Die(); vessel.protoVessel.protoPartSnapshots.Remove(protoPartsToRemove[i]); } if (hasCrewChanges) { //We must always refresh the crew in every part of the vessel, even if we don't spectate vessel.RebuildCrewList(); //IF we are spectating we must fix the portraits of the kerbals if (FlightGlobals.ActiveVessel?.id == vessel.id) { //If you don't call spawn crew and you do a crew transfer the transfered crew won't appear in the portraits... Client.Singleton.StartCoroutine(CallbackUtil.DelayedCallback(0.25f, () => { FlightGlobals.ActiveVessel?.SpawnCrew(); })); //If you don't call this the kerbal portraits appear in black... Client.Singleton.StartCoroutine(CallbackUtil.DelayedCallback(0.5f, () => { KerbalPortraitGallery.Instance?.SetActivePortraitsForVessel(FlightGlobals.ActiveVessel); })); } } }
/// <summary> /// If we get the Update lock, force the getting of the unloaded update lock. /// If we get a control lock, force getting the update and unloaded update /// </summary> public void LockAcquire(LockDefinition lockDefinition) { switch (lockDefinition.Type) { case LockType.Control: if (lockDefinition.PlayerName == SettingsSystem.CurrentSettings.PlayerName) { if (VesselCommon.IsSpectating) { VesselLockSystem.Singleton.StopSpectating(); } LockSystem.Singleton.AcquireUpdateLock(lockDefinition.VesselId, true); LockSystem.Singleton.AcquireUnloadedUpdateLock(lockDefinition.VesselId, true); LockSystem.Singleton.AcquireKerbalLock(lockDefinition.VesselId, true); //As we got the lock of that vessel, remove its FS and position updates //This is done so even if the vessel has queued updates, we ignore them as we are controlling it VesselCommon.RemoveVesselFromSystems(lockDefinition.VesselId); } else { //Somebody else got the control of our active vessel so start spectating if (FlightGlobals.ActiveVessel && FlightGlobals.ActiveVessel.id == lockDefinition.VesselId) { System.StartSpectating(lockDefinition.VesselId); } //If some other player got the control lock release the update lock in case we have it if (LockSystem.LockQuery.UpdateLockBelongsToPlayer(lockDefinition.VesselId, SettingsSystem.CurrentSettings.PlayerName)) { LockSystem.LockStore.RemoveLock(LockSystem.LockQuery.GetUpdateLock(lockDefinition.VesselId)); LockSystem.LockStore.AddOrUpdateLock(new LockDefinition(LockType.UnloadedUpdate, lockDefinition.PlayerName, lockDefinition.VesselId)); } //If some other player got the control lock release the unloaded update lock in case we have it if (LockSystem.LockQuery.UnloadedUpdateLockBelongsToPlayer(lockDefinition.VesselId, SettingsSystem.CurrentSettings.PlayerName)) { LockSystem.LockStore.RemoveLock(LockSystem.LockQuery.GetUnloadedUpdateLock(lockDefinition.VesselId)); LockSystem.LockStore.AddOrUpdateLock(new LockDefinition(LockType.UnloadedUpdate, lockDefinition.PlayerName, lockDefinition.VesselId)); } //TODO:We should release the kerbals locks? } break; case LockType.Update: if (lockDefinition.PlayerName != SettingsSystem.CurrentSettings.PlayerName) { //If some other player got the update lock release the unloaded update lock just in case we have it if (LockSystem.LockQuery.UnloadedUpdateLockBelongsToPlayer(lockDefinition.VesselId, SettingsSystem.CurrentSettings.PlayerName)) { LockSystem.LockStore.RemoveLock(LockSystem.LockQuery.GetUnloadedUpdateLock(lockDefinition.VesselId)); LockSystem.LockStore.AddOrUpdateLock(new LockDefinition(LockType.UnloadedUpdate, lockDefinition.PlayerName, lockDefinition.VesselId)); } //TODO:We should release the kerbals locks? } else { LockSystem.Singleton.AcquireUnloadedUpdateLock(lockDefinition.VesselId, true); LockSystem.Singleton.AcquireKerbalLock(lockDefinition.VesselId, true); } break; } }
public void HandleMessage(IMessageData messageData) { var msgData = messageData as VesselUpdateMsgData; if (msgData == null || !System.UpdateSystemBasicReady || VesselCommon.UpdateIsForOwnVessel(msgData.VesselId)) { return; } var update = new VesselUpdate { Id = Guid.NewGuid(), ReceiveTime = Time.fixedTime, PlanetTime = msgData.PlanetTime, Stage = msgData.Stage, SentTime = msgData.GameSentTime, ActiveEngines = msgData.ActiveEngines, StoppedEngines = msgData.StoppedEngines, Decouplers = msgData.Decouplers, AnchoredDecouplers = msgData.AnchoredDecouplers, Clamps = msgData.Clamps, Docks = msgData.Docks, VesselId = msgData.VesselId, BodyName = msgData.BodyName, Rotation = msgData.Rotation, FlightState = new FlightCtrlState { mainThrottle = msgData.MainThrottle, wheelThrottleTrim = msgData.WheelThrottleTrim, X = msgData.X, Y = msgData.Y, Z = msgData.Z, killRot = msgData.KillRot, gearUp = msgData.GearUp, gearDown = msgData.GearDown, headlight = msgData.Headlight, wheelThrottle = msgData.WheelThrottle, roll = msgData.Roll, yaw = msgData.Yaw, pitch = msgData.Pitch, rollTrim = msgData.RollTrim, yawTrim = msgData.YawTrim, pitchTrim = msgData.PitchTrim, wheelSteer = msgData.WheelSteer, wheelSteerTrim = msgData.WheelSteerTrim }, ActionGrpControls = msgData.ActiongroupControls, IsSurfaceUpdate = msgData.IsSurfaceUpdate }; if (update.IsSurfaceUpdate) { update.Position = msgData.Position; update.Velocity = msgData.Velocity; update.Acceleration = msgData.Acceleration; } else { update.Orbit = msgData.Orbit; } if (!System.ReceivedUpdates.ContainsKey(update.VesselId)) { System.ReceivedUpdates.Add(update.VesselId, new Queue <VesselUpdate>()); } if (System.ReceivedUpdates[update.VesselId].Count + 1 > VesselUpdateInterpolationSystem.MaxTotalUpdatesInQueue) { System.ReceivedUpdates[update.VesselId].Dequeue(); } System.ReceivedUpdates[update.VesselId].Enqueue(update); }
public VesselProtoUpdate(ConfigNode vessel, Guid vesselId) { VesselId = vesselId; ProtoVessel = VesselCommon.CreateSafeProtoVesselFromConfigNode(vessel, vesselId); }
public void ProcessPartFieldSync() { var vessel = FlightGlobals.FindVessel(VesselId); if (vessel == null) { return; } if (!VesselCommon.DoVesselChecks(VesselId)) { return; } var part = vessel.protoVessel.GetProtoPart(PartFlightId); if (part != null) { var module = part.FindProtoPartModuleInProtoPart(ModuleName); if (module != null) { switch (FieldType) { case PartSyncFieldType.Boolean: module.moduleValues.SetValue(FieldName, BoolValue); PartModuleEvent.onPartModuleBoolFieldProcessed.Fire(module, FieldName, BoolValue); break; case PartSyncFieldType.Integer: module.moduleValues.SetValue(FieldName, IntValue); PartModuleEvent.onPartModuleIntFieldProcessed.Fire(module, FieldName, IntValue); break; case PartSyncFieldType.Float: module.moduleValues.SetValue(FieldName, FloatValue); PartModuleEvent.onPartModuleFloatFieldProcessed.Fire(module, FieldName, FloatValue); break; case PartSyncFieldType.Double: module.moduleValues.SetValue(FieldName, DoubleValue); PartModuleEvent.onPartModuleDoubleFieldProcessed.Fire(module, FieldName, DoubleValue); break; case PartSyncFieldType.Vector3: module.moduleValues.SetValue(FieldName, VectorValue); PartModuleEvent.onPartModuleVectorFieldProcessed.Fire(module, FieldName, VectorValue); break; case PartSyncFieldType.Quaternion: module.moduleValues.SetValue(FieldName, QuaternionValue); PartModuleEvent.onPartModuleQuaternionFieldProcessed.Fire(module, FieldName, QuaternionValue); break; case PartSyncFieldType.String: module.moduleValues.SetValue(FieldName, StrValue); PartModuleEvent.onPartModuleStringFieldProcessed.Fire(module, FieldName, StrValue); break; case PartSyncFieldType.Enum: module.moduleValues.SetValue(FieldName, StrValue); PartModuleEvent.onPartModuleEnumFieldProcessed.Fire(module, FieldName, IntValue, StrValue); break; case PartSyncFieldType.Object: module.moduleValues.SetValue(FieldName, StrValue); PartModuleEvent.onPartModuleObjectFieldProcessed.Fire(module, FieldName, StrValue); //We do not set the value of objects in the module as we cannot be sure if they can be transformed from a string back to the object break; default: throw new ArgumentOutOfRangeException(); } } } }
/// <summary> /// The debris that is on the safety bubble SHOULD NEVER be synced with the server and at the same time /// it won't exist for any other player so here we just remove it in a routine /// </summary> private void RemoveSafetyBubbleDebris() { DebrisInSafetyBubbleToRemove.Clear(); DebrisInSafetyBubbleToRemove.AddRange(FlightGlobals.Vessels.Where(v => v != null && v.state == Vessel.State.INACTIVE && v.vesselType != VesselType.Flag && v.id != FlightGlobals.ActiveVessel?.id && VesselCommon.IsInSafetyBubble(v))); foreach (var vessel in DebrisInSafetyBubbleToRemove) { if (vessel == null) { continue; } LunaLog.Log($"[LMP]: Vessel {vessel.id} name {vessel.vesselName} it's an inactive vessel inside the safety bubble."); KillVessel(vessel.id); } }
/// <summary> /// Check vessels that must be reloaded /// </summary> private void CheckVesselsToRefresh() { try { if ((DateTime.UtcNow - LastReloadCheck).TotalMilliseconds > 1500 && ProtoSystemBasicReady && !VesselCommon.ActiveVesselIsInSafetyBubble()) { VesselsToRefresh.Clear(); //We get the vessels that already exist VesselsToRefresh.AddRange(VesselsProtoStore.AllPlayerVessels .Where(pv => pv.Value.VesselExist && pv.Value.VesselHasUpdate) .Select(v => v.Key)); //Do not iterate directly trough the AllPlayerVessels dictionary as the collection can be modified in another threads! foreach (var vesselIdToReload in VesselsToRefresh) { if (VesselRemoveSystem.VesselWillBeKilled(vesselIdToReload)) { continue; } //Do not handle vessel proto updates over our OWN active vessel if we are not spectating //If there is an undetected dock (our protovessel has been modified) it will be detected //in the docksystem if (vesselIdToReload == FlightGlobals.ActiveVessel?.id && !VesselCommon.IsSpectating) { continue; } if (VesselsProtoStore.AllPlayerVessels.TryGetValue(vesselIdToReload, out var vesselProtoUpdate)) { CurrentlyUpdatingVesselId = vesselIdToReload; ProtoToVesselRefresh.UpdateVesselPartsFromProtoVessel(vesselProtoUpdate.Vessel, vesselProtoUpdate.ProtoVessel, vesselProtoUpdate.VesselParts.Keys); vesselProtoUpdate.VesselHasUpdate = false; CurrentlyUpdatingVesselId = Guid.Empty; } } LastReloadCheck = DateTime.UtcNow; } } catch (Exception e) { LunaLog.LogError($"[LMP]: Error in CheckVesselsToReload {e}"); } }
private static bool PositionUpdateIsTooOld(VesselPositionUpdate update) { return(update.GameTimeStamp < TimeSyncSystem.UniversalTime - VesselCommon.PositionAndFlightStateMessageOffsetSec(update.PingSec)); }
/// <summary> /// Kills a vessel. /// </summary> public void KillVessel(Guid vesselId, string reason) { VesselCommon.RemoveVesselFromSystems(vesselId); KillVessel(FlightGlobals.fetch.LmpFindVessel(vesselId), reason); }