diff --git a/Client/Client.csproj b/Client/Client.csproj index 00b2c93ae..d8e4263a0 100644 --- a/Client/Client.csproj +++ b/Client/Client.csproj @@ -97,6 +97,10 @@ + + + + diff --git a/Client/Network/NetworkReceiver.cs b/Client/Network/NetworkReceiver.cs index 0ac30c5f1..3758a12b1 100644 --- a/Client/Network/NetworkReceiver.cs +++ b/Client/Network/NetworkReceiver.cs @@ -15,6 +15,7 @@ using LunaClient.Systems.Scenario; using LunaClient.Systems.Screenshot; using LunaClient.Systems.SettingsSys; +using LunaClient.Systems.ShareProgress; using LunaClient.Systems.Status; using LunaClient.Systems.VesselDockSys; using LunaClient.Systems.VesselEvaSys; @@ -204,6 +205,9 @@ private static void EnqueueMessageToSystem(IServerMessageBase msg) case ServerMessageType.Facility: FacilitySystem.Singleton.EnqueueMessage(msg); break; + case ServerMessageType.ShareProgress: + ShareProgressSystem.Singleton.EnqueueMessage(msg); + break; case ServerMessageType.Screenshot: ScreenshotSystem.Singleton.EnqueueMessage(msg); break; diff --git a/Client/Systems/Lock/LockSystem.cs b/Client/Systems/Lock/LockSystem.cs index b637c918c..c304edc6b 100644 --- a/Client/Systems/Lock/LockSystem.cs +++ b/Client/Systems/Lock/LockSystem.cs @@ -93,13 +93,21 @@ public void AcquireSpectatorLock(Guid vesselId) } /// - /// Aquire the spectator lock on the given vessel + /// Aquire the asteroid lock for the current player /// public void AcquireAsteroidLock() { AcquireLock(new LockDefinition(LockType.Asteroid, SettingsSystem.CurrentSettings.PlayerName)); } + /// + /// Aquire the contract lock for the current player. + /// + public void AcquireContractLock() + { + AcquireLock(new LockDefinition(LockType.Contract, SettingsSystem.CurrentSettings.PlayerName)); + } + #endregion #region ReleaseLocks @@ -196,6 +204,10 @@ public void ReleasePlayerLocks(LockType type) locksToRelease = LockQuery.SpectatorLockExists(SettingsSystem.CurrentSettings.PlayerName) ? new[] { LockQuery.GetSpectatorLock(SettingsSystem.CurrentSettings.PlayerName) } : new LockDefinition[0]; break; + case LockType.Contract: + locksToRelease = LockQuery.ContractLockOwner() == playerName ? + new[] { LockQuery.ContractLock() } : new LockDefinition[0]; + break; default: throw new ArgumentOutOfRangeException(nameof(type), type, null); } diff --git a/Client/Systems/Network/NetworkSystem.cs b/Client/Systems/Network/NetworkSystem.cs index 418eea898..7aebfdde7 100644 --- a/Client/Systems/Network/NetworkSystem.cs +++ b/Client/Systems/Network/NetworkSystem.cs @@ -20,6 +20,7 @@ using LunaClient.Systems.Scenario; using LunaClient.Systems.Screenshot; using LunaClient.Systems.SettingsSys; +using LunaClient.Systems.ShareProgress; using LunaClient.Systems.Status; using LunaClient.Systems.TimeSyncer; using LunaClient.Systems.VesselDockSys; @@ -294,6 +295,10 @@ private void NetworkUpdate() ScreenshotSystem.Singleton.Enabled = true; CraftLibrarySystem.Singleton.Enabled = true; BugSystem.Singleton.Enabled = true; + if (SettingsSystem.ServerSettings.GameMode != GameMode.Sandbox) + { + ShareProgressSystem.Singleton.Enabled = SettingsSystem.ServerSettings.ShareProgress; + } PlayerColorSystem.Singleton.MessageSender.SendPlayerColorToServer(); StatusSystem.Singleton.MessageSender.SendOwnStatus(); NetworkSimpleMessageSender.SendMotdRequest(); diff --git a/Client/Systems/SettingsSys/SettingsMessageHandler.cs b/Client/Systems/SettingsSys/SettingsMessageHandler.cs index 9ee343f69..468b28c73 100644 --- a/Client/Systems/SettingsSys/SettingsMessageHandler.cs +++ b/Client/Systems/SettingsSys/SettingsMessageHandler.cs @@ -18,6 +18,7 @@ public void HandleMessage(IServerMessageBase msg) SettingsSystem.ServerSettings.WarpMode = msgData.WarpMode; SettingsSystem.ServerSettings.GameMode = msgData.GameMode; + SettingsSystem.ServerSettings.ShareProgress = msgData.ShareProgress; SettingsSystem.ServerSettings.TerrainQuality = msgData.TerrainQuality; SettingsSystem.ServerSettings.AllowCheats = msgData.AllowCheats; SettingsSystem.ServerSettings.AllowSackKerbals = msgData.AllowSackKerbals; diff --git a/Client/Systems/SettingsSys/SettingsServerStructure.cs b/Client/Systems/SettingsSys/SettingsServerStructure.cs index f3c1f1b07..971625c89 100644 --- a/Client/Systems/SettingsSys/SettingsServerStructure.cs +++ b/Client/Systems/SettingsSys/SettingsServerStructure.cs @@ -10,6 +10,7 @@ public class SettingsServerStructure public GameParameters.AdvancedParams ServerAdvancedParameters { get; set; } = new GameParameters.AdvancedParams(); public CommNetParams ServerCommNetParameters { get; set; } = new CommNetParams(); public GameMode GameMode { get; set; } + public bool ShareProgress { get; set; } public TerrainQuality TerrainQuality { get; set; } public bool AllowCheats { get; set; } public bool AllowSackKerbals { get; set; } diff --git a/Client/Systems/ShareProgress/ShareProgressEvents.cs b/Client/Systems/ShareProgress/ShareProgressEvents.cs new file mode 100644 index 000000000..34f6e3ac9 --- /dev/null +++ b/Client/Systems/ShareProgress/ShareProgressEvents.cs @@ -0,0 +1,206 @@ +using Contracts; +using LunaClient.Base; +using LunaClient.Network; +using LunaClient.Utilities; +using LunaCommon.Message.Data.ShareProgress; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaClient.Systems.ShareProgress +{ + class ShareProgressEvents : SubSystem + { + #region EventHandling + public void FundsChanged(double value, TransactionReasons reason) + { + if (System.IncomingFundsProcessing) + return; + + var msgData = NetworkMain.CliMsgFactory.CreateNewMessageData(); + msgData.Funds = value; + msgData.Reason = reason.ToString(); + System.MessageSender.SendMessage(msgData); + LunaLog.Log("Funds changed to: " + value + " with reason: " + reason.ToString()); + } + + public void ScienceChanged(float value, TransactionReasons reason) + { + if (System.IncomingScienceProcessing) + return; + + var msgData = NetworkMain.CliMsgFactory.CreateNewMessageData(); + msgData.Science = value; + msgData.Reason = reason.ToString(); + System.MessageSender.SendMessage(msgData); + LunaLog.Log("Science changed to: " + value + " with reason: " + reason.ToString()); + } + + public void ReputationChanged(float value, TransactionReasons reason) + { + if (System.IncomingReputationProcessing) + return; + + var msgData = NetworkMain.CliMsgFactory.CreateNewMessageData(); + msgData.Reputation = value; + msgData.Reason = reason.ToString(); + System.MessageSender.SendMessage(msgData); + LunaLog.Log("Reputation changed to: " + value + " with reason: " + reason.ToString()); + } + + public void TechnologyResearched(GameEvents.HostTargetAction data) + { + if (System.IncomingTechnologyProcessing) + return; + + if (data.target == RDTech.OperationResult.Successful) + { + var msgData = NetworkMain.CliMsgFactory.CreateNewMessageData(); + msgData.TechID = data.host.techID; + LunaLog.Log("Technology unlocked: " + msgData.TechID); + System.MessageSender.SendMessage(msgData); + } + } + + public void ContractAccepted(Contract contract) + { + if (System.IncomingContractsProcessing) + return; + + SendContractUpdate(contract); + LunaLog.Log("Contract accepted: " + contract.ContractGuid); + } + + public void ContractCancelled(Contract contract) + { + if (System.IncomingContractsProcessing) + return; + + SendContractUpdate(contract); + LunaLog.Log("Contract cancelled: " + contract.ContractGuid); + } + + public void ContractCompleted(Contract contract) + { + if (System.IncomingContractsProcessing) + return; + + SendContractUpdate(contract); + LunaLog.Log("Contract completed: " + contract.ContractGuid); + } + + public void ContractsListChanged() + { + LunaLog.Log("Contract list changed."); + } + + public void ContractsLoaded() + { + LunaLog.Log("Contracts loaded."); + } + + public void ContractDeclined(Contract contract) + { + if (System.IncomingContractsProcessing) + return; + + SendContractUpdate(contract); + LunaLog.Log("Contract declined: " + contract.ContractGuid); + } + + public void ContractFailed(Contract contract) + { + if (System.IncomingContractsProcessing) + return; + + SendContractUpdate(contract); + LunaLog.Log("Contract failed: " + contract.ContractGuid); + } + + public void ContractFinished(Contract contract) + { + /* + Doesn't need to be synchronized because there is no ContractFinished state. + Also the contract will be finished on the contract complete / failed / cancelled / ... + */ + } + + public void ContractOffered(Contract contract) + { + //This should be only called on the client with the contract lock, because he has the generationCount != 0. + LunaLog.Log("Contract offered: " + contract.ContractGuid + " - " + contract.Title); + SendContractUpdate(contract); + } + + public void ContractParameterChanged(Contract contract, ContractParameter contractParameter) + { + SendContractUpdate(contract); + LunaLog.Log("Contract parameter changed on:" + contract.ContractGuid.ToString()); + } + + public void ContractRead(Contract contract) + { + LunaLog.Log("Contract read:" + contract.ContractGuid.ToString()); + } + + public void ContractSeen(Contract contract) + { + LunaLog.Log("Contract seen:" + contract.ContractGuid.ToString()); + } + #endregion + + #region PrivateMethods + private void SendContractUpdate(Contract[] contracts) + { + //Convert the Contract's to ContractInfo's. + List contractInfos = new List(); + foreach (Contract contract in contracts) + { + ConfigNode configNode = this.ConvertContractToConfigNode(contract); + if (configNode == null) + { + break; + } + + byte[] data = ConfigNodeSerializer.Serialize(configNode); + int numBytes = data.Length; + + contractInfos.Add(new ContractInfo() + { + ContractGuid = contract.ContractGuid, + Data = data, + NumBytes = numBytes + }); + } + + //Build the packet and send it. + var msgData = NetworkMain.CliMsgFactory.CreateNewMessageData(); + msgData.Contracts = contractInfos.ToArray(); + msgData.ContractCount = msgData.Contracts.Length; + System.MessageSender.SendMessage(msgData); + } + + private void SendContractUpdate(Contract contract) + { + this.SendContractUpdate(new Contract[] { contract }); + } + + private ConfigNode ConvertContractToConfigNode(Contract contract) + { + ConfigNode configNode = new ConfigNode(); + try + { + contract.Save(configNode); + } + catch (Exception e) + { + LunaLog.LogError($"[LMP]: Error while saving contract: {e}"); + return null; + } + + return configNode; + } + #endregion + } +} diff --git a/Client/Systems/ShareProgress/ShareProgressMessageHandler.cs b/Client/Systems/ShareProgress/ShareProgressMessageHandler.cs new file mode 100644 index 000000000..1235cfd8e --- /dev/null +++ b/Client/Systems/ShareProgress/ShareProgressMessageHandler.cs @@ -0,0 +1,290 @@ +using Contracts; +using KSP.UI.Screens; +using LunaClient.Base; +using LunaClient.Base.Interface; +using LunaClient.Utilities; +using LunaCommon.Message.Data.ShareProgress; +using LunaCommon.Message.Interface; +using LunaCommon.Message.Types; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaClient.Systems.ShareProgress +{ + class ShareProgressMessageHandler : SubSystem, IMessageHandler + { + public ConcurrentQueue IncomingMessages { get; set; } = new ConcurrentQueue(); + + public void HandleMessage(IServerMessageBase msg) + { + if (!(msg.Data is ShareProgressBaseMsgData msgData)) return; + switch (msgData.ShareProgressMessageType) + { + case ShareProgressMessageType.FundsUpdate: + { + this.FundsUpdate((ShareProgressFundsMsgData)msgData); + break; + } + case ShareProgressMessageType.ScienceUpdate: + { + this.ScienceUpdate((ShareProgressScienceMsgData)msgData); + break; + } + case ShareProgressMessageType.ReputationUpdate: + { + this.ReputationUpdate((ShareProgressReputationMsgData)msgData); + break; + } + case ShareProgressMessageType.TechnologyUpdate: + { + this.TechnologyUpdate((ShareProgressTechnologyMsgData)msgData); + break; + } + case ShareProgressMessageType.ContractUpdate: + { + this.ContractUpdate((ShareProgressContractMsgData)msgData); + break; + } + } + } + + #region PrivateMethods + private void FundsUpdate(ShareProgressFundsMsgData data) + { + System.IncomingFundsProcessing = true; + Funding.Instance.SetFunds(data.Funds, TransactionReasons.None); + System.IncomingFundsProcessing = false; + LunaLog.Log("FundsUpdate received - funds changed to: " + data.Funds); + } + + private void ScienceUpdate(ShareProgressScienceMsgData data) + { + System.IncomingScienceProcessing = true; + ResearchAndDevelopment.Instance.SetScience(data.Science, TransactionReasons.None); + System.IncomingScienceProcessing = false; + LunaLog.Log("ScienceUpdate received - science changed to: " + data.Science); + } + + private void ReputationUpdate(ShareProgressReputationMsgData data) + { + System.IncomingReputationProcessing = true; + Reputation.Instance.SetReputation(data.Reputation, TransactionReasons.None); + System.IncomingReputationProcessing = false; + LunaLog.Log("ReputationUpdate received - reputation changed to: " + data.Reputation); + } + + private void TechnologyUpdate(ShareProgressTechnologyMsgData data) + { + System.IncomingTechnologyProcessing = true; + ProtoTechNode[] nodes = AssetBase.RnDTechTree.GetTreeTechs(); + foreach (ProtoTechNode n in nodes) + { + if (n.techID == data.TechID) + { + ResearchAndDevelopment.Instance.UnlockProtoTechNode(n); + } + } + + ResearchAndDevelopment.RefreshTechTreeUI(); + System.IncomingTechnologyProcessing = false; + LunaLog.Log("TechnologyUpdate received - technology unlocked: " + data.TechID); + } + + private void ContractUpdate(ShareProgressContractMsgData data) + { + //Don't listen to these events for the time this message is processing. + System.IncomingContractsProcessing = true; + System.IncomingFundsProcessing = true; + System.IncomingScienceProcessing = true; + System.IncomingReputationProcessing = true; + System.SaveBasicProgress(); //Save the current funds, science and reputation for restoring after the contract changes were applied. + + LunaLog.Log("IncomingContractsProcessing=true"); + + foreach (ContractInfo cInfo in data.Contracts) + { + Contract incomingContract = this.ConvertByteArrayToContract(cInfo.Data, cInfo.NumBytes); + if (incomingContract == null) + break; + + int contractIndex = ContractSystem.Instance.Contracts.FindIndex(c => c.ContractGuid == cInfo.ContractGuid); + + if (contractIndex != -1) + { + //found the contract in the local ContractSystem + this.UpdateContract(contractIndex, incomingContract); + } + else + { + //There is no matching contract in the local ContractSystem + this.AddContract(incomingContract); + } + } + + System.RestoreBasicProgress(); //Restore funds, science and reputation in case the contract action changed some of that. + //Listen to the events again. + System.IncomingFundsProcessing = false; + System.IncomingScienceProcessing = false; + System.IncomingReputationProcessing = false; + System.IncomingContractsProcessing = false; + LunaLog.Log("IncomingContractsProcessing=false"); + GameEvents.Contract.onContractsListChanged.Fire(); + } + + /// + /// Convert a byte array to a ConfigNode and the to a Contract. + /// If anything goes wrong it will return null. + /// + /// The byte array that represents the configNode + /// The length of the byte array + /// + private Contract ConvertByteArrayToContract(byte[] data, int numBytes) + { + ConfigNode node; + try + { + node = ConfigNodeSerializer.Deserialize(data, numBytes); + } + catch (Exception e) + { + LunaLog.LogError($"[LMP]: Error while deserializing contract configNode: {e}"); + return null; + } + + if (node == null) + { + LunaLog.LogError("[LMP]: Error, the contract configNode was null."); + return null; + } + + Contract contract; + try + { + string value = node.GetValue("type"); + node.RemoveValues("type"); + Type contractType = ContractSystem.GetContractType(value); + contract = Contract.Load((Contract)Activator.CreateInstance(contractType), node); + } + catch (Exception e) + { + LunaLog.LogError($"[LMP]: Error while deserializing contract: {e}"); + return null; + } + + return contract; + } + + /// + /// Updates the local contract at given index in the ContractSystem.Instance.Contracts list + /// with the given incomingContract data. + /// + /// Index in ContractSystem.Instance.Contracts + /// Wanted contract + private void UpdateContract(int contractIndex, Contract incomingContract) + { + if (ContractSystem.Instance.Contracts[contractIndex].ContractState != incomingContract.ContractState) + { + //Do the same action on the contract that the incoming contract has already done. + switch (incomingContract.ContractState) + { + case Contract.State.Active: + ContractSystem.Instance.Contracts[contractIndex].Accept(); + break; + case Contract.State.Cancelled: + ContractSystem.Instance.Contracts[contractIndex].Cancel(); + break; + case Contract.State.Completed: + ContractSystem.Instance.Contracts[contractIndex].Complete(); + break; + case Contract.State.Declined: + ContractSystem.Instance.Contracts[contractIndex].Decline(); + break; + case Contract.State.Failed: + ContractSystem.Instance.Contracts[contractIndex].Fail(); + break; + case Contract.State.Offered: + ContractSystem.Instance.Contracts[contractIndex].Offer(); + break; + case Contract.State.Withdrawn: + ContractSystem.Instance.Contracts[contractIndex].Withdraw(); + break; + } + } + else + { + //The incoming contract has the same state as the current one (so it doesn't have changed). + + //Maybe update the parameters and trigger some parameter changed event or something simelar. + + //Or replace the complete contract and hope everything goes fine: + ContractSystem.Instance.Contracts[contractIndex].Unregister(); + ContractSystem.Instance.Contracts[contractIndex] = incomingContract; + if (ContractSystem.Instance.Contracts[contractIndex].ContractState == Contract.State.Active) + { + ContractSystem.Instance.Contracts[contractIndex].Register(); + } + } + + LunaLog.Log("ContractUpdate received - contract state changed on: " + incomingContract.ContractGuid.ToString() + " - " + incomingContract.Title); + } + + /// + /// Adds a contract to the local ContractSystem. + /// + /// + private void AddContract(Contract incomingContract) + { + if (!incomingContract.IsFinished()) + { + ContractSystem.Instance.Contracts.Add(incomingContract); + int contractIndex = ContractSystem.Instance.Contracts.FindIndex(c => c.ContractGuid == incomingContract.ContractGuid); + + //Trigger the contract events manually because the incoming contract object has already the state that it should have. + ContractSystem.Instance.Contracts[contractIndex].OnStateChange.Fire(incomingContract.ContractState); + switch (ContractSystem.Instance.Contracts[contractIndex].ContractState) + { + case Contract.State.Active: + ContractSystem.Instance.Contracts[contractIndex].Register(); + GameEvents.Contract.onAccepted.Fire(ContractSystem.Instance.Contracts[contractIndex]); + break; + case Contract.State.Cancelled: + GameEvents.Contract.onCancelled.Fire(ContractSystem.Instance.Contracts[contractIndex]); + GameEvents.Contract.onFailed.Fire(ContractSystem.Instance.Contracts[contractIndex]); + GameEvents.Contract.onFinished.Fire(ContractSystem.Instance.Contracts[contractIndex]); + break; + case Contract.State.Completed: + GameEvents.Contract.onCompleted.Fire(ContractSystem.Instance.Contracts[contractIndex]); + GameEvents.Contract.onFinished.Fire(ContractSystem.Instance.Contracts[contractIndex]); + break; + case Contract.State.Declined: + GameEvents.Contract.onDeclined.Fire(ContractSystem.Instance.Contracts[contractIndex]); + break; + case Contract.State.Failed: + GameEvents.Contract.onFailed.Fire(ContractSystem.Instance.Contracts[contractIndex]); + GameEvents.Contract.onFinished.Fire(ContractSystem.Instance.Contracts[contractIndex]); + break; + case Contract.State.Offered: + GameEvents.Contract.onOffered.Fire(ContractSystem.Instance.Contracts[contractIndex]); + break; + case Contract.State.Withdrawn: + GameEvents.Contract.onFinished.Fire(ContractSystem.Instance.Contracts[contractIndex]); + break; + } + } + else + { + incomingContract.Unregister(); + if (incomingContract.ContractState == Contract.State.Completed || incomingContract.ContractState == Contract.State.DeadlineExpired || incomingContract.ContractState == Contract.State.Failed || incomingContract.ContractState == Contract.State.Cancelled) + { + ContractSystem.Instance.ContractsFinished.Add(incomingContract); + } + } + + LunaLog.Log("New contract added: " + incomingContract.ContractGuid + " - " + incomingContract.Title); + } + #endregion + } +} diff --git a/Client/Systems/ShareProgress/ShareProgressMessageSender.cs b/Client/Systems/ShareProgress/ShareProgressMessageSender.cs new file mode 100644 index 000000000..30250b13e --- /dev/null +++ b/Client/Systems/ShareProgress/ShareProgressMessageSender.cs @@ -0,0 +1,20 @@ +using LunaClient.Base; +using LunaClient.Base.Interface; +using LunaClient.Network; +using LunaCommon.Message.Client; +using LunaCommon.Message.Interface; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaClient.Systems.ShareProgress +{ + class ShareProgressMessageSender : SubSystem, IMessageSender + { + public void SendMessage(IMessageData msg) + { + TaskFactory.StartNew(() => NetworkSender.QueueOutgoingMessage(MessageFactory.CreateNew(msg))); + } + } +} diff --git a/Client/Systems/ShareProgress/ShareProgressSystem.cs b/Client/Systems/ShareProgress/ShareProgressSystem.cs new file mode 100644 index 000000000..8e5872447 --- /dev/null +++ b/Client/Systems/ShareProgress/ShareProgressSystem.cs @@ -0,0 +1,189 @@ +using Contracts; +using LunaClient.Base; +using LunaClient.Systems.Lock; +using LunaClient.Systems.SettingsSys; +using LunaCommon.Enums; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; + +namespace LunaClient.Systems.ShareProgress +{ + /// + /// A system for synchronizing progress between the clients + /// (funds, science, reputation, technology, contracts) + /// + class ShareProgressSystem : MessageSystem + { + public override string SystemName { get; } = nameof(ShareProgressSystem); + + private ShareProgressEvents ShareProgressEvents { get; } = new ShareProgressEvents(); + + public bool IncomingFundsProcessing; + public bool IncomingScienceProcessing; + public bool IncomingReputationProcessing; + public bool IncomingTechnologyProcessing; + public bool IncomingContractsProcessing; + + private int _defaultContractGenerateIterations; + + private double _savedFunds; + private float _savedScience; + private float _savedReputation; + + #region UnityMethods + protected override void OnEnabled() + { + base.OnEnabled(); + + IncomingFundsProcessing = false; + IncomingScienceProcessing = false; + IncomingReputationProcessing = false; + IncomingTechnologyProcessing = false; + IncomingContractsProcessing = false; + _defaultContractGenerateIterations = ContractSystem.generateContractIterations; + _savedFunds = 0; + _savedScience = 0; + _savedReputation = 0; + + if (SettingsSystem.ServerSettings.GameMode != GameMode.Sandbox) + { + SubscribeToBasicEvents(); + + if (SettingsSystem.ServerSettings.GameMode == GameMode.Career) + { + TryGetContractLock(); + SetupRoutine(new RoutineDefinition(10000, RoutineExecution.Update, TryGetContractLock)); + + SubscribeToContractEvents(); + } + } + } + + protected override void OnDisabled() + { + base.OnDisabled(); + + if (SettingsSystem.ServerSettings.GameMode != GameMode.Sandbox) + { + UnsubscribeFromBasicEvents(); + + if (SettingsSystem.ServerSettings.GameMode == GameMode.Career) + { + UnsubscribeFromContractEvents(); + } + } + } + #endregion + + #region PublicMethods + /// + /// Saves the current funds, science and reputation in memory. + /// So they can be later applied again with RestoreBasicProgress. + /// + public void SaveBasicProgress() + { + if (SettingsSystem.ServerSettings.GameMode != GameMode.Sandbox) + { + _savedScience = ResearchAndDevelopment.Instance.Science; + + if (SettingsSystem.ServerSettings.GameMode == GameMode.Career) + { + _savedFunds = Funding.Instance.Funds; + _savedReputation = Reputation.Instance.reputation; + } + } + } + + /// + /// Restores the funds, science and repuation that was saved before. + /// + public void RestoreBasicProgress() + { + if (SettingsSystem.ServerSettings.GameMode != GameMode.Sandbox) + { + ResearchAndDevelopment.Instance.SetScience(_savedScience, TransactionReasons.None); + + if (SettingsSystem.ServerSettings.GameMode == GameMode.Career) + { + Funding.Instance.SetFunds(_savedFunds, TransactionReasons.None); + Reputation.Instance.SetReputation(_savedReputation, TransactionReasons.None); + } + } + } + #endregion + + #region PrivateMethods + private void SubscribeToBasicEvents() + { + GameEvents.OnFundsChanged.Add(ShareProgressEvents.FundsChanged); + GameEvents.OnReputationChanged.Add(ShareProgressEvents.ReputationChanged); + GameEvents.OnScienceChanged.Add(ShareProgressEvents.ScienceChanged); + GameEvents.OnTechnologyResearched.Add(ShareProgressEvents.TechnologyResearched); + } + + private void UnsubscribeFromBasicEvents() + { + GameEvents.OnFundsChanged.Remove(ShareProgressEvents.FundsChanged); + GameEvents.OnReputationChanged.Remove(ShareProgressEvents.ReputationChanged); + GameEvents.OnScienceChanged.Remove(ShareProgressEvents.ScienceChanged); + GameEvents.OnTechnologyResearched.Remove(ShareProgressEvents.TechnologyResearched); + } + + private void SubscribeToContractEvents() + { + GameEvents.Contract.onAccepted.Add(ShareProgressEvents.ContractAccepted); + GameEvents.Contract.onCancelled.Add(ShareProgressEvents.ContractCancelled); + GameEvents.Contract.onCompleted.Add(ShareProgressEvents.ContractCompleted); + GameEvents.Contract.onContractsListChanged.Add(ShareProgressEvents.ContractsListChanged); + GameEvents.Contract.onContractsLoaded.Add(ShareProgressEvents.ContractsLoaded); + GameEvents.Contract.onDeclined.Add(ShareProgressEvents.ContractDeclined); + GameEvents.Contract.onFailed.Add(ShareProgressEvents.ContractFailed); + GameEvents.Contract.onFinished.Add(ShareProgressEvents.ContractFinished); + GameEvents.Contract.onOffered.Add(ShareProgressEvents.ContractOffered); + GameEvents.Contract.onParameterChange.Add(ShareProgressEvents.ContractParameterChanged); + GameEvents.Contract.onRead.Add(ShareProgressEvents.ContractRead); + GameEvents.Contract.onSeen.Add(ShareProgressEvents.ContractSeen); + } + + private void UnsubscribeFromContractEvents() + { + GameEvents.Contract.onAccepted.Remove(ShareProgressEvents.ContractAccepted); + GameEvents.Contract.onCancelled.Remove(ShareProgressEvents.ContractCancelled); + GameEvents.Contract.onCompleted.Remove(ShareProgressEvents.ContractCompleted); + GameEvents.Contract.onContractsListChanged.Remove(ShareProgressEvents.ContractsListChanged); + GameEvents.Contract.onContractsLoaded.Remove(ShareProgressEvents.ContractsLoaded); + GameEvents.Contract.onDeclined.Remove(ShareProgressEvents.ContractDeclined); + GameEvents.Contract.onFailed.Remove(ShareProgressEvents.ContractFailed); + GameEvents.Contract.onFinished.Remove(ShareProgressEvents.ContractFinished); + GameEvents.Contract.onOffered.Remove(ShareProgressEvents.ContractOffered); + GameEvents.Contract.onParameterChange.Remove(ShareProgressEvents.ContractParameterChanged); + GameEvents.Contract.onRead.Remove(ShareProgressEvents.ContractRead); + GameEvents.Contract.onSeen.Remove(ShareProgressEvents.ContractSeen); + } + + private void TryGetContractLock() + { + if (!LockSystem.LockQuery.ContractLockExists()) + { + LockSystem.Singleton.AcquireContractLock(); + } + + //Update the ContractSystem generation depending on if the current player has the lock or not. + if (!LockSystem.LockQuery.ContractLockBelongsToPlayer(SettingsSystem.CurrentSettings.PlayerName)) + { + ContractSystem.generateContractIterations = 0; + LunaLog.Log("You have no ContractLock and are not allowed to generate contracts."); + } + else + { + ContractSystem.generateContractIterations = _defaultContractGenerateIterations; + LunaLog.Log("You have the ContractLock and you will generate contracts."); + } + } + #endregion + } +} diff --git a/Client/Windows/Systems/SystemsDrawer.cs b/Client/Windows/Systems/SystemsDrawer.cs index b74225ca6..b92113bb7 100644 --- a/Client/Windows/Systems/SystemsDrawer.cs +++ b/Client/Windows/Systems/SystemsDrawer.cs @@ -9,6 +9,7 @@ using LunaClient.Systems.PlayerColorSys; using LunaClient.Systems.PlayerConnection; using LunaClient.Systems.Scenario; +using LunaClient.Systems.ShareProgress; using LunaClient.Systems.TimeSyncer; using LunaClient.Systems.VesselDockSys; using LunaClient.Systems.VesselFairingsSys; @@ -81,6 +82,11 @@ private void PrintSystemButtons() { GroupSystem.Singleton.Enabled = GUILayout.Toggle(GroupSystem.Singleton.Enabled, "ON/OFF", ButtonStyle); } + _shareProgress = GUILayout.Toggle(_shareProgress, "Share progress system", ButtonStyle); + if (_shareProgress) + { + ShareProgressSystem.Singleton.Enabled = GUILayout.Toggle(ShareProgressSystem.Singleton.Enabled, "ON/OFF", ButtonStyle); + } _kerbal = GUILayout.Toggle(_kerbal, "Kerbal system", ButtonStyle); if (_kerbal) { diff --git a/Client/Windows/Systems/SystemsWindow.cs b/Client/Windows/Systems/SystemsWindow.cs index 2cb827ff8..3f4204ccf 100644 --- a/Client/Windows/Systems/SystemsWindow.cs +++ b/Client/Windows/Systems/SystemsWindow.cs @@ -57,6 +57,7 @@ public override bool Display private static bool _timeSyncer; private static bool _toolbar; private static bool _warp; + private static bool _shareProgress; #endregion diff --git a/Common/Common.csproj b/Common/Common.csproj index 493445b2d..eadbe86f6 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -58,8 +58,10 @@ + + @@ -185,6 +187,13 @@ + + + + + + + @@ -233,6 +242,7 @@ + @@ -251,6 +261,7 @@ + diff --git a/Common/Enums/ClientMessageType.cs b/Common/Enums/ClientMessageType.cs index 7d55f0be0..d6f45aec9 100644 --- a/Common/Enums/ClientMessageType.cs +++ b/Common/Enums/ClientMessageType.cs @@ -20,5 +20,6 @@ public enum ClientMessageType Groups = 16, Facility = 17, Screenshot = 18, + ShareProgress = 19, } } \ No newline at end of file diff --git a/Common/Enums/ServerMessageType.cs b/Common/Enums/ServerMessageType.cs index 3721d68f0..cb953399d 100644 --- a/Common/Enums/ServerMessageType.cs +++ b/Common/Enums/ServerMessageType.cs @@ -21,5 +21,6 @@ public enum ServerMessageType Groups = 17, Facility = 18, Screenshot = 19, + ShareProgress = 20, } } \ No newline at end of file diff --git a/Common/Locks/LockQuery.cs b/Common/Locks/LockQuery.cs index 9c867b0ce..c6431de23 100644 --- a/Common/Locks/LockQuery.cs +++ b/Common/Locks/LockQuery.cs @@ -35,6 +35,8 @@ public bool LockBelongsToPlayer(LockType type, Guid vesselId, string playerName) if (LockStore.UnloadedUpdateLocks.TryGetValue(vesselId, out var unloadedUpdateLock)) return unloadedUpdateLock.Type == LockType.UnloadedUpdate && unloadedUpdateLock.VesselId == vesselId && unloadedUpdateLock.PlayerName == playerName; break; + case LockType.Contract: + return LockStore.ContractLock?.PlayerName == playerName; default: throw new ArgumentOutOfRangeException(nameof(type), type, null); } @@ -57,6 +59,8 @@ public bool LockExists(LockType type, Guid vesselId) return LockStore.UpdateLocks.ContainsKey(vesselId); case LockType.UnloadedUpdate: return LockStore.UnloadedUpdateLocks.ContainsKey(vesselId); + case LockType.Contract: + return LockStore.ContractLock != null; default: throw new ArgumentOutOfRangeException(nameof(type), type, null); } @@ -83,6 +87,8 @@ private string GetLockOwner(LockType type, Guid vesselId) if (LockStore.UnloadedUpdateLocks.TryGetValue(vesselId, out var unloadedUpdateLock)) return unloadedUpdateLock.PlayerName; break; + case LockType.Contract: + return LockStore.ContractLock?.PlayerName; default: throw new ArgumentOutOfRangeException(nameof(type), type, null); } @@ -143,6 +149,8 @@ public bool LockExists(LockDefinition lockDefinition) return LockStore.ControlLocks.ContainsKey(lockDefinition.VesselId); case LockType.Spectator: return LockStore.SpectatorLocks.ContainsKey(lockDefinition.PlayerName); + case LockType.Contract: + return LockStore.ContractLock != null; default: throw new ArgumentOutOfRangeException(); } @@ -170,6 +178,8 @@ public LockDefinition GetLock(LockType lockType, string playerName, Guid vesselI case LockType.Spectator: LockStore.SpectatorLocks.TryGetValue(playerName, out existingLock); break; + case LockType.Contract: + return LockStore.ContractLock; default: throw new ArgumentOutOfRangeException(); } diff --git a/Common/Locks/LockQueryContract.cs b/Common/Locks/LockQueryContract.cs new file mode 100644 index 000000000..d2f7ddb70 --- /dev/null +++ b/Common/Locks/LockQueryContract.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaCommon.Locks +{ + /// + /// Class that retrieve locks specific for contracts + /// + public partial class LockQuery + { + /// + /// Checks if the contract lock is assigned + /// + public bool ContractLockExists() + { + return LockExists(LockType.Contract, Guid.Empty); + } + + /// + /// Checks if the contract lock belongs to given player + /// + public bool ContractLockBelongsToPlayer(string playerName) + { + return LockBelongsToPlayer(LockType.Contract, Guid.Empty, playerName); + } + + /// + /// Gets the contract lock owner + /// + public string ContractLockOwner() + { + return GetLockOwner(LockType.Contract, Guid.Empty); + } + + /// + /// Gets the contract lock + /// + public LockDefinition ContractLock() + { + return GetLock(LockType.Contract, string.Empty, Guid.Empty); + } + } +} diff --git a/Common/Locks/LockStore.cs b/Common/Locks/LockStore.cs index a718a87ac..fd5ef96ba 100644 --- a/Common/Locks/LockStore.cs +++ b/Common/Locks/LockStore.cs @@ -39,6 +39,16 @@ public class LockStore /// internal ConcurrentDictionary SpectatorLocks { get; set; } = new ConcurrentDictionary(); + /// + /// Provides a lock around modifications to the ContractLock object to ensure both atomic changes to the ContractLock and a memory barrier for changes to the ContractLock + /// + private readonly object _contractSyncLock = new object(); + + /// + /// You can't have more than one user with the contract lock so it's a simple object + /// + internal LockDefinition ContractLock { get; set; } + /// /// Adds or replace the given lock to the storage /// @@ -67,6 +77,15 @@ public void AddOrUpdateLock(LockDefinition lockDefinition) case LockType.Spectator: SpectatorLocks.AddOrUpdate(lockDefinition.PlayerName, lockDefinition, (key, existingVal) => lockDefinition); break; + case LockType.Contract: + lock (_contractSyncLock) + { + if (ContractLock == null) + ContractLock = new LockDefinition(LockType.Contract, lockDefinition.PlayerName); + else + ContractLock.PlayerName = lockDefinition.PlayerName; + } + break; default: throw new ArgumentOutOfRangeException(); } @@ -97,6 +116,12 @@ public void RemoveLock(LockDefinition lockDefinition) case LockType.Spectator: SpectatorLocks.TryRemove(lockDefinition.PlayerName, out _); break; + case LockType.Contract: + lock (_contractSyncLock) + { + ContractLock = null; + } + break; default: throw new ArgumentOutOfRangeException(); } @@ -127,6 +152,12 @@ public void RemoveLock(LockType lockType, string playerName, Guid vesselId) case LockType.Spectator: SpectatorLocks.TryRemove(playerName, out _); break; + case LockType.Contract: + lock (_contractSyncLock) + { + ContractLock = null; + } + break; default: throw new ArgumentOutOfRangeException(); } diff --git a/Common/Locks/LockType.cs b/Common/Locks/LockType.cs index 24d4e791c..f40a10fa2 100644 --- a/Common/Locks/LockType.cs +++ b/Common/Locks/LockType.cs @@ -39,6 +39,11 @@ public enum LockType /// The spectator lock specifies if a user is spectating a vessel or not. /// A vessel can have several spectators /// - Spectator + Spectator, + + /// + /// The contract lock is owned by only 1 player and it defines who can generate new contracts. + /// + Contract } } diff --git a/Common/Message/Client/ShareProgressCliMsg.cs b/Common/Message/Client/ShareProgressCliMsg.cs new file mode 100644 index 000000000..8169fa655 --- /dev/null +++ b/Common/Message/Client/ShareProgressCliMsg.cs @@ -0,0 +1,37 @@ +using Lidgren.Network; +using LunaCommon.Enums; +using LunaCommon.Message.Client.Base; +using LunaCommon.Message.Data.ShareProgress; +using LunaCommon.Message.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaCommon.Message.Client +{ + public class ShareProgressCliMsg : CliMsgBase + { + /// + internal ShareProgressCliMsg() { } + + /// + public override string ClassName { get; } = nameof(ShareProgressCliMsg); + + /// + protected override Dictionary SubTypeDictionary { get; } = new Dictionary + { + [(ushort)ShareProgressMessageType.FundsUpdate] = typeof(ShareProgressFundsMsgData), + [(ushort)ShareProgressMessageType.ScienceUpdate] = typeof(ShareProgressScienceMsgData), + [(ushort)ShareProgressMessageType.ReputationUpdate] = typeof(ShareProgressReputationMsgData), + [(ushort)ShareProgressMessageType.TechnologyUpdate] = typeof(ShareProgressTechnologyMsgData), + [(ushort)ShareProgressMessageType.ContractUpdate] = typeof(ShareProgressContractMsgData), + }; + + public override ClientMessageType MessageType => ClientMessageType.ShareProgress; + + protected override int DefaultChannel => 18; + + public override NetDeliveryMethod NetDeliveryMethod => NetDeliveryMethod.ReliableOrdered; + } +} diff --git a/Common/Message/Data/Settings/SetingsReplyMsgData.cs b/Common/Message/Data/Settings/SetingsReplyMsgData.cs index 14d534454..d0bbdea57 100644 --- a/Common/Message/Data/Settings/SetingsReplyMsgData.cs +++ b/Common/Message/Data/Settings/SetingsReplyMsgData.cs @@ -13,6 +13,7 @@ internal SettingsReplyMsgData() { } public WarpMode WarpMode; public GameMode GameMode; + public bool ShareProgress; public TerrainQuality TerrainQuality; public bool AllowCheats; public bool AllowSackKerbals; @@ -73,6 +74,7 @@ internal override void InternalSerialize(NetOutgoingMessage lidgrenMsg) lidgrenMsg.Write((int)WarpMode); lidgrenMsg.Write((int)GameMode); + lidgrenMsg.Write(ShareProgress); lidgrenMsg.Write((int)TerrainQuality); lidgrenMsg.Write(AllowCheats); lidgrenMsg.Write(AllowSackKerbals); @@ -132,6 +134,7 @@ internal override void InternalDeserialize(NetIncomingMessage lidgrenMsg) WarpMode = (WarpMode)lidgrenMsg.ReadInt32(); GameMode = (GameMode)lidgrenMsg.ReadInt32(); + ShareProgress = lidgrenMsg.ReadBoolean(); TerrainQuality = (TerrainQuality)lidgrenMsg.ReadInt32(); AllowCheats = lidgrenMsg.ReadBoolean(); AllowSackKerbals = lidgrenMsg.ReadBoolean(); diff --git a/Common/Message/Data/ShareProgress/ContractInfo.cs b/Common/Message/Data/ShareProgress/ContractInfo.cs new file mode 100644 index 000000000..518bd9dfe --- /dev/null +++ b/Common/Message/Data/ShareProgress/ContractInfo.cs @@ -0,0 +1,42 @@ +using Lidgren.Network; +using LunaCommon.Message.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaCommon.Message.Data.ShareProgress +{ + /// + /// Wrapper for transmitting the ksp Contract objects. + /// + public class ContractInfo + { + public Guid ContractGuid; + public int NumBytes; + public byte[] Data = new byte[0]; + + public void Serialize(NetOutgoingMessage lidgrenMsg) + { + lidgrenMsg.Write(ContractGuid.ToString()); + lidgrenMsg.Write(NumBytes); + lidgrenMsg.Write(Data, 0, NumBytes); + } + + public void Deserialize(NetIncomingMessage lidgrenMsg) + { + ContractGuid = new Guid(lidgrenMsg.ReadString()); + + NumBytes = lidgrenMsg.ReadInt32(); + if (Data.Length < NumBytes) + Data = new byte[NumBytes]; + + lidgrenMsg.ReadBytes(Data, 0, NumBytes); + } + + public int GetByteCount() + { + return ContractGuid.ToString().GetByteCount() + sizeof(int) + sizeof(byte) * NumBytes; + } + } +} diff --git a/Common/Message/Data/ShareProgress/ShareProgressBaseMsgData.cs b/Common/Message/Data/ShareProgress/ShareProgressBaseMsgData.cs new file mode 100644 index 000000000..afdae7d42 --- /dev/null +++ b/Common/Message/Data/ShareProgress/ShareProgressBaseMsgData.cs @@ -0,0 +1,35 @@ +using Lidgren.Network; +using LunaCommon.Message.Base; +using LunaCommon.Message.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaCommon.Message.Data.ShareProgress +{ + public abstract class ShareProgressBaseMsgData : MessageData + { + /// + internal ShareProgressBaseMsgData() { } + public override ushort SubType => (ushort)(int)ShareProgressMessageType; + public virtual ShareProgressMessageType ShareProgressMessageType => throw new NotImplementedException(); + + public string ObjectId; + + internal override void InternalSerialize(NetOutgoingMessage lidgrenMsg) + { + lidgrenMsg.Write(ObjectId); + } + + internal override void InternalDeserialize(NetIncomingMessage lidgrenMsg) + { + ObjectId = lidgrenMsg.ReadString(); + } + + internal override int InternalGetMessageSize() + { + return ObjectId.GetByteCount(); + } + } +} diff --git a/Common/Message/Data/ShareProgress/ShareProgressContractMsgData.cs b/Common/Message/Data/ShareProgress/ShareProgressContractMsgData.cs new file mode 100644 index 000000000..1c504110c --- /dev/null +++ b/Common/Message/Data/ShareProgress/ShareProgressContractMsgData.cs @@ -0,0 +1,65 @@ +using Lidgren.Network; +using LunaCommon.Message.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaCommon.Message.Data.ShareProgress +{ + /// + /// Data packet for sending contracts between clients. + /// + public class ShareProgressContractMsgData : ShareProgressBaseMsgData + { + /// + internal ShareProgressContractMsgData() { } + public override ShareProgressMessageType ShareProgressMessageType => ShareProgressMessageType.ContractUpdate; + + public int ContractCount; + public ContractInfo[] Contracts = new ContractInfo[0]; + + public override string ClassName { get; } = nameof(ShareProgressContractMsgData); + + internal override void InternalSerialize(NetOutgoingMessage lidgrenMsg) + { + base.InternalSerialize(lidgrenMsg); + + lidgrenMsg.Write(ContractCount); + + for (var i = 0; i < ContractCount; i++) + { + Contracts[i].Serialize(lidgrenMsg); + } + } + + internal override void InternalDeserialize(NetIncomingMessage lidgrenMsg) + { + base.InternalDeserialize(lidgrenMsg); + + ContractCount = lidgrenMsg.ReadInt32(); + if (Contracts.Length < ContractCount) + Contracts = new ContractInfo[ContractCount]; + + + for (var i = 0; i < ContractCount; i++) + { + if (Contracts[i] == null) + Contracts[i] = new ContractInfo(); + + Contracts[i].Deserialize(lidgrenMsg); + } + } + + internal override int InternalGetMessageSize() + { + var arraySize = 0; + for (var i = 0; i < ContractCount; i++) + { + arraySize += Contracts[i].GetByteCount(); + } + + return base.InternalGetMessageSize() + sizeof(int) + arraySize; + } + } +} diff --git a/Common/Message/Data/ShareProgress/ShareProgressFundsMsgData.cs b/Common/Message/Data/ShareProgress/ShareProgressFundsMsgData.cs new file mode 100644 index 000000000..035ce97d9 --- /dev/null +++ b/Common/Message/Data/ShareProgress/ShareProgressFundsMsgData.cs @@ -0,0 +1,46 @@ +using Lidgren.Network; +using LunaCommon.Message.Base; +using LunaCommon.Message.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaCommon.Message.Data.ShareProgress +{ + /// + /// Data packet for sending funds changes between clients. + /// + public class ShareProgressFundsMsgData : ShareProgressBaseMsgData + { + /// + internal ShareProgressFundsMsgData() { } + public override ShareProgressMessageType ShareProgressMessageType => ShareProgressMessageType.FundsUpdate; + + public double Funds; + public string Reason; + + public override string ClassName { get; } = nameof(ShareProgressFundsMsgData); + + internal override void InternalSerialize(NetOutgoingMessage lidgrenMsg) + { + base.InternalSerialize(lidgrenMsg); + + lidgrenMsg.Write(Funds); + lidgrenMsg.Write(Reason); + } + + internal override void InternalDeserialize(NetIncomingMessage lidgrenMsg) + { + base.InternalDeserialize(lidgrenMsg); + + Funds = lidgrenMsg.ReadDouble(); + Reason = lidgrenMsg.ReadString(); + } + + internal override int InternalGetMessageSize() + { + return base.InternalGetMessageSize() + sizeof(double); + } + } +} diff --git a/Common/Message/Data/ShareProgress/ShareProgressReputationMsgData.cs b/Common/Message/Data/ShareProgress/ShareProgressReputationMsgData.cs new file mode 100644 index 000000000..d39e241d7 --- /dev/null +++ b/Common/Message/Data/ShareProgress/ShareProgressReputationMsgData.cs @@ -0,0 +1,45 @@ +using Lidgren.Network; +using LunaCommon.Message.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaCommon.Message.Data.ShareProgress +{ + /// + /// Data packet for sending reputation changes between clients. + /// + public class ShareProgressReputationMsgData : ShareProgressBaseMsgData + { + /// + internal ShareProgressReputationMsgData() { } + public override ShareProgressMessageType ShareProgressMessageType => ShareProgressMessageType.ReputationUpdate; + + public float Reputation; + public string Reason; + + public override string ClassName { get; } = nameof(ShareProgressReputationMsgData); + + internal override void InternalSerialize(NetOutgoingMessage lidgrenMsg) + { + base.InternalSerialize(lidgrenMsg); + + lidgrenMsg.Write(Reputation); + lidgrenMsg.Write(Reason); + } + + internal override void InternalDeserialize(NetIncomingMessage lidgrenMsg) + { + base.InternalDeserialize(lidgrenMsg); + + Reputation = lidgrenMsg.ReadFloat(); + Reason = lidgrenMsg.ReadString(); + } + + internal override int InternalGetMessageSize() + { + return base.InternalGetMessageSize() + sizeof(float); + } + } +} diff --git a/Common/Message/Data/ShareProgress/ShareProgressScienceMsgData.cs b/Common/Message/Data/ShareProgress/ShareProgressScienceMsgData.cs new file mode 100644 index 000000000..d12764f99 --- /dev/null +++ b/Common/Message/Data/ShareProgress/ShareProgressScienceMsgData.cs @@ -0,0 +1,45 @@ +using Lidgren.Network; +using LunaCommon.Message.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaCommon.Message.Data.ShareProgress +{ + /// + /// Data packet for sending science changes between clients. + /// + public class ShareProgressScienceMsgData : ShareProgressBaseMsgData + { + /// + internal ShareProgressScienceMsgData() { } + public override ShareProgressMessageType ShareProgressMessageType => ShareProgressMessageType.ScienceUpdate; + + public float Science; + public string Reason; + + public override string ClassName { get; } = nameof(ShareProgressScienceMsgData); + + internal override void InternalSerialize(NetOutgoingMessage lidgrenMsg) + { + base.InternalSerialize(lidgrenMsg); + + lidgrenMsg.Write(Science); + lidgrenMsg.Write(Reason); + } + + internal override void InternalDeserialize(NetIncomingMessage lidgrenMsg) + { + base.InternalDeserialize(lidgrenMsg); + + Science = lidgrenMsg.ReadFloat(); + Reason = lidgrenMsg.ReadString(); + } + + internal override int InternalGetMessageSize() + { + return base.InternalGetMessageSize() + sizeof(float); + } + } +} diff --git a/Common/Message/Data/ShareProgress/ShareProgressTechnologyMsgData.cs b/Common/Message/Data/ShareProgress/ShareProgressTechnologyMsgData.cs new file mode 100644 index 000000000..00368e4df --- /dev/null +++ b/Common/Message/Data/ShareProgress/ShareProgressTechnologyMsgData.cs @@ -0,0 +1,43 @@ +using Lidgren.Network; +using LunaCommon.Message.Base; +using LunaCommon.Message.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaCommon.Message.Data.ShareProgress +{ + /// + /// Data packet for sending technology unlocks between clients. + /// + public class ShareProgressTechnologyMsgData : ShareProgressBaseMsgData + { + /// + internal ShareProgressTechnologyMsgData() { } + public override ShareProgressMessageType ShareProgressMessageType => ShareProgressMessageType.TechnologyUpdate; + + public string TechID; + + public override string ClassName { get; } = nameof(ShareProgressTechnologyMsgData); + + internal override void InternalSerialize(NetOutgoingMessage lidgrenMsg) + { + base.InternalSerialize(lidgrenMsg); + + lidgrenMsg.Write(TechID); + } + + internal override void InternalDeserialize(NetIncomingMessage lidgrenMsg) + { + base.InternalDeserialize(lidgrenMsg); + + TechID = lidgrenMsg.ReadString(); + } + + internal override int InternalGetMessageSize() + { + return base.InternalGetMessageSize() + TechID.GetByteCount(); + } + } +} diff --git a/Common/Message/Server/ShareProgressSrvMsg.cs b/Common/Message/Server/ShareProgressSrvMsg.cs new file mode 100644 index 000000000..59564506f --- /dev/null +++ b/Common/Message/Server/ShareProgressSrvMsg.cs @@ -0,0 +1,37 @@ +using Lidgren.Network; +using LunaCommon.Enums; +using LunaCommon.Message.Data.ShareProgress; +using LunaCommon.Message.Server.Base; +using LunaCommon.Message.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaCommon.Message.Server +{ + public class ShareProgressSrvMsg : SrvMsgBase + { + /// + internal ShareProgressSrvMsg() { } + + /// + public override string ClassName { get; } = nameof(ShareProgressSrvMsg); + + /// + protected override Dictionary SubTypeDictionary { get; } = new Dictionary + { + [(ushort)ShareProgressMessageType.FundsUpdate] = typeof(ShareProgressFundsMsgData), + [(ushort)ShareProgressMessageType.ScienceUpdate] = typeof(ShareProgressScienceMsgData), + [(ushort)ShareProgressMessageType.ReputationUpdate] = typeof(ShareProgressReputationMsgData), + [(ushort)ShareProgressMessageType.TechnologyUpdate] = typeof(ShareProgressTechnologyMsgData), + [(ushort)ShareProgressMessageType.ContractUpdate] = typeof(ShareProgressContractMsgData), + }; + + public override ServerMessageType MessageType => ServerMessageType.ShareProgress; + + protected override int DefaultChannel => 19; + + public override NetDeliveryMethod NetDeliveryMethod => NetDeliveryMethod.ReliableOrdered; + } +} diff --git a/Common/Message/Types/ShareProgressMessageType.cs b/Common/Message/Types/ShareProgressMessageType.cs new file mode 100644 index 000000000..d60b40601 --- /dev/null +++ b/Common/Message/Types/ShareProgressMessageType.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LunaCommon.Message.Types +{ + public enum ShareProgressMessageType + { + FundsUpdate = 0, + ScienceUpdate = 1, + ReputationUpdate = 2, + TechnologyUpdate = 3, + ContractUpdate = 4, + } +} diff --git a/Scripts/CopyToKSPDirectory.bat b/Scripts/CopyToKSPDirectory.bat index 0bf83c2de..57a9ad688 100644 --- a/Scripts/CopyToKSPDirectory.bat +++ b/Scripts/CopyToKSPDirectory.bat @@ -3,14 +3,14 @@ ::Set the directories in the setdirectories.bat file if you want a different folder than Kerbal Space Program ::EXAMPLE: -::SET KSPPATH=C:\Kerbal Space Program -::SET KSPPATH2=C:\Kerbal Space Program2 +SET KSPPATH=C:\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program +SET KSPPATH2=C:\Users\Malte\Desktop\Kerbal Space Program call "%~dp0\SetDirectories.bat" IF DEFINED KSPPATH (ECHO KSPPATH is defined) ELSE (SET KSPPATH=C:\Kerbal Space Program) IF DEFINED KSPPATH2 (ECHO KSPPATH2 is defined) - -SET SOLUTIONCONFIGURATION=%1 +::%1 +SET SOLUTIONCONFIGURATION=Debug mkdir "%KSPPATH%\GameData\LunaMultiplayer\" IF DEFINED KSPPATH2 (mkdir "%KSPPATH2%\GameData\LunaMultiplayer\") diff --git a/Server/Message/Reader/SettingsMsgReader.cs b/Server/Message/Reader/SettingsMsgReader.cs index 7a9afd65c..56683afb5 100644 --- a/Server/Message/Reader/SettingsMsgReader.cs +++ b/Server/Message/Reader/SettingsMsgReader.cs @@ -20,6 +20,7 @@ public override void HandleMessage(ClientStructure client, IClientMessageBase me var msgData = ServerContext.ServerMessageFactory.CreateNewMessageData(); msgData.WarpMode = GeneralSettings.SettingsStore.WarpMode; msgData.GameMode = GeneralSettings.SettingsStore.GameMode; + msgData.ShareProgress = GeneralSettings.SettingsStore.ShareProgress; msgData.TerrainQuality = GeneralSettings.SettingsStore.TerrainQuality; msgData.AllowCheats = GeneralSettings.SettingsStore.Cheats; msgData.AllowSackKerbals = GeneralSettings.SettingsStore.AllowSackKerbals; diff --git a/Server/Message/Reader/ShareProgressMsgReader.cs b/Server/Message/Reader/ShareProgressMsgReader.cs new file mode 100644 index 000000000..82ffcd25a --- /dev/null +++ b/Server/Message/Reader/ShareProgressMsgReader.cs @@ -0,0 +1,40 @@ +using LunaCommon.Message.Data.ShareProgress; +using LunaCommon.Message.Interface; +using Server.Client; +using Server.Message.Reader.Base; +using LunaCommon.Message.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Server.System; + +namespace Server.Message.Reader +{ + public class ShareProgressMsgReader : ReaderBase + { + public override void HandleMessage(ClientStructure client, IClientMessageBase message) + { + var data = (ShareProgressBaseMsgData)message.Data; + switch (data.ShareProgressMessageType) + { + case ShareProgressMessageType.FundsUpdate: + ShareProgressSystem.FundsReceived(client, (ShareProgressFundsMsgData)data); + break; + case ShareProgressMessageType.ScienceUpdate: + ShareProgressSystem.ScienceReceived(client, (ShareProgressScienceMsgData)data); + break; + case ShareProgressMessageType.ReputationUpdate: + ShareProgressSystem.ReputationReceived(client, (ShareProgressReputationMsgData)data); + break; + case ShareProgressMessageType.TechnologyUpdate: + ShareProgressSystem.TechnologyReceived(client, (ShareProgressTechnologyMsgData)data); + break; + case ShareProgressMessageType.ContractUpdate: + ShareProgressSystem.ContractsReceived(client, (ShareProgressContractMsgData)data); + break; + } + } + } +} diff --git a/Server/Server.csproj b/Server/Server.csproj index 45129dcf7..680a41c89 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -119,6 +119,7 @@ + @@ -155,6 +156,7 @@ + diff --git a/Server/Server/MessageReceiver.cs b/Server/Server/MessageReceiver.cs index 172133509..84ab7b3b1 100644 --- a/Server/Server/MessageReceiver.cs +++ b/Server/Server/MessageReceiver.cs @@ -39,6 +39,7 @@ public class MessageReceiver [ClientMessageType.Groups] = new GroupMsgReader(), [ClientMessageType.Facility] = new FacilityMsgReader(), [ClientMessageType.Screenshot] = new ScreenshotMsgReader(), + [ClientMessageType.ShareProgress] = new ShareProgressMsgReader(), }; #endregion diff --git a/Server/Settings/Definition/SettingsDefinition.cs b/Server/Settings/Definition/SettingsDefinition.cs index 949dc7f1f..3fae5b08c 100644 --- a/Server/Settings/Definition/SettingsDefinition.cs +++ b/Server/Settings/Definition/SettingsDefinition.cs @@ -60,6 +60,9 @@ public class SettingsDefinition [XmlComment(Value = "Specify the game Type. Values: Sandbox, Career, Science")] public GameMode GameMode { get; set; } = GameMode.Sandbox; + [XmlComment(Value = "Experimental! Only for Career / Science GameMode. It synchronizes the player progression like funds / science / reputation / technology live to other players (like a coop mode).")] + public bool ShareProgress { get; set; } = false; + [XmlComment(Value = "Enable mod control. WARNING: Only consider turning off mod control for private servers. " + "The game will constantly complain about missing parts if there are missing mods. " + "Read this wiki page: https://github.com/LunaMultiplayer/LunaMultiplayer/wiki/Mod-file to understand how it works")] diff --git a/Server/System/ShareProgressSystem.cs b/Server/System/ShareProgressSystem.cs new file mode 100644 index 000000000..1e87c3af4 --- /dev/null +++ b/Server/System/ShareProgressSystem.cs @@ -0,0 +1,62 @@ +using LunaCommon.Message.Data.ShareProgress; +using LunaCommon.Message.Server; +using Server.Client; +using Server.Context; +using Server.Log; +using Server.Server; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Server.System +{ + public static class ShareProgressSystem + { + public static void FundsReceived(ClientStructure client, ShareProgressFundsMsgData data) + { + LunaLog.Debug("Funds received: " + data.Funds + " - reason: " + data.Reason); + + //send the funds update to all other clients + MessageQueuer.RelayMessage(client, data); + } + + public static void ScienceReceived(ClientStructure client, ShareProgressScienceMsgData data) + { + LunaLog.Debug("Science received: " + data.Science + " - reason: " + data.Reason); + + //send the science update to all other clients + MessageQueuer.RelayMessage(client, data); + } + + public static void ReputationReceived(ClientStructure client, ShareProgressReputationMsgData data) + { + LunaLog.Debug("Reputation received: " + data.Reputation + " - reason: " + data.Reason); + + //send the reputation update to all other clients + MessageQueuer.RelayMessage(client, data); + } + + public static void TechnologyReceived(ClientStructure client, ShareProgressTechnologyMsgData data) + { + LunaLog.Debug("Technology unlocked: " + data.TechID); + + //send the technology update to all other clients + MessageQueuer.RelayMessage(client, data); + } + + public static void ContractsReceived(ClientStructure client, ShareProgressContractMsgData data) + { + LunaLog.Debug("Contract data received:"); + + foreach (var item in data.Contracts) + { + LunaLog.Debug(item.ContractGuid.ToString()); + } + + //send the contract update to all other clients + MessageQueuer.RelayMessage(client, data); + } + } +}