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);
+ }
+ }
+}