Shadowmage

Rescue Contracts - Restricting Available Pod types (how to get target of a contract?)

Recommended Posts

Hi all;

It has always bugged me that the rescue Kerbal contracts spawn using a random part with crew capacity > 1.  As can be seen in numerous modded situations this causes many (sometimes hillarious) problems regarding kerbals being spawned in parts in invalid states (non-inflated inflatable modules) or that do not have hatches at all.

So.. I'm thinking about ways to -fix- this by restricting what pods a kerbal may be spawned in down to a manually curated list (initially just the stock pods, but end-users may add parts from other mods as they see fit).

My current idea is to subscribe to the 'new vessel created' event, and if that vessel was created by a rescue contract and is using a pod not on the 'approved pods' list, change that part out for one that is approved.  However... I am having difficulty locating any information that will link a specific vessel to a specific contract.  I cannot find any GameObject, Part, or Vessel fields in any of the contract related classes, nor any information in the vessel class that says it is a target of a contract.  I have already found how to pull a list of just the rescue contracts out of the contract system, but cannot tell what vessels they are linked to.

 

Is anyone aware of a method to get the target of a specific contract? 

 

Thanks in advance for any information on this problem,

Shadowmage

Edited by Shadowmage

Share this post


Link to post
Share on other sites

Haven't looked at the code, but could you try adding criteria to the pod selection? Something like "ElectricCharge > 0" and/or "Monopropellant > 0" might help cut out parts like the inflatables.

Share this post


Link to post
Share on other sites

Sadly I'm still stuck on the 'identifying vessels which were created for rescue contracts' stage...  Might be a few weeks/months before I get this one figured out.  I could probably get it done faster by bugging a dev to include the config in stock :)

 

Share this post


Link to post
Share on other sites

The rescue contracts save a "partID" field, that might be the flight id of the part itself, but I don't think there is anything specific about the vessel. So I guess you could watch the onNewVesselCreated event and check for that id, but the only way to actually get that id is to manually parse the save file (HighLogic.CurrentGame.config), which can be a bit of a pain.

Share this post


Link to post
Share on other sites

It's a lot easier to catch them when they're offered rather than after the contract has been accepted and the related parts have spawned. You can get around parsing HighLogic.CurrentGame by saving (and editing) the contract yourself to change those private variables

[KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
class RescueContractsUseLanderCans : MonoBehaviour
{
    private void Start()
    {
        GameEvents.Contract.onOffered.Add(OnContractOffered);
    }

    private void OnDestroy()
    {
        GameEvents.Contract.onOffered.Remove(OnContractOffered);
    }


    private enum RecoveryContractType
    {
        Kerbal = 1, // lone Kerbal, maybe in a part
        Part = 2, // Recover a part
        Compound = 3 // lone kerbal, plus also recover a part(s)
    }

    private class RecoveryContractInfo
    {
        [Persistent] public RecoveryContractType recoveryType;
        [Persistent] public string partName;
        [Persistent] public uint partID;

        private static bool EnumWithinRange<TEnum>(TEnum value)
        {
            return Enum.GetValues(typeof (TEnum)).Cast<TEnum>().Contains(value);
        }

        public bool IsValid()
        {
            return partID == 0 /* otherwise the contract was generated already */
                    && !string.IsNullOrEmpty(partName)
                    && EnumWithinRange(recoveryType);
        }
    }


    private static void OnContractOffered(Contract data)
    {
        var recoveryContract = data as RecoverAsset;
        if (recoveryContract == null)
            return;

        var contractConfig = new ConfigNode("Contract");
        data.Save(contractConfig);

        var info = new RecoveryContractInfo();
        if (!ConfigNode.LoadObjectFromConfig(info, contractConfig) || !info.IsValid())
        {
            Log.Error("Something went wrong while inspecting a recovery contract!");
            return;
        }

        if (info.recoveryType == RecoveryContractType.Part)
            return; // standard part recovery, if it doesn't involve a kerbal we don't care

        // All kerbal and compound recovery missions shall henceforth use the venerable landerCabinSmall
        contractConfig.SetValue("partName", "landerCabinSmall");

        Contract.Load(data, contractConfig);
    }
}

 

Share this post


Link to post
Share on other sites

Awesome, thanks for the info guys;  I'll see about throwing together a small modlet/micro-addon when I have time to sit down and go through what you've presented.

I was pretty sure there had to be a reasonable way to do this... but I've not spent any time learning/using/working with the contract system on the plugin side of things.

Share this post


Link to post
Share on other sites

Have thrown together the following for fixing rescue pods; untested so far...   will do some testing over the next few days/week, then recompile and pack it up as a separate add-on for general use.  Will have to in-line some utility methods, but should convert pretty easily.

It should randomly select a pod from the 'approvedPodTypes' array, which is constructed via root-level config nodes; so the approved list could easily be modified via MM patch for various mods' pods.  The array can have duplicate names added to it as a sort of weighting system (though was not the original intent).

I'm also operating directly on the ConfigNode level and skipping the intermediate data class, as the Enum that was referenced appears to be private/inaccessible (at least in the .dll's I'm using).
 

using System;
using System.Collections.Generic;
using UnityEngine;
using Contracts;

namespace SSTUTools
{
    [KSPAddon(KSPAddon.Startup.Instantly | KSPAddon.Startup.EveryScene, true)]
    class SSTURescueContractPartSelector : MonoBehaviour
    {

        private static string[] approvedPodTypes;
        private static System.Random rng;

        public void Start()
        {
            DontDestroyOnLoad(this);//persist this addon across scenes
            GameEvents.Contract.onOffered.Add(OnContractOffered);
            rng = new System.Random();
        }

        public void OnDestroy()
        {
            GameEvents.Contract.onOffered.Remove(OnContractOffered);
        }

        public void ModuleManagerPostLoad()
        {
            List<string> typesList = new List<string>();
            ConfigNode[] approvedPods = GameDatabase.Instance.GetConfigNodes("RESCUE_POD_TYPES");
            int len1 = approvedPods.Length;
            for (int i = 0; i < len1; i++)
            {
                string[] types = approvedPods[i].GetStringValues("pod");
                typesList.AddRange(types);
            }
            approvedPodTypes = typesList.ToArray();
        }

        public void OnContractOffered(Contract contract)
        {
            //TODO initial validation pass, reject all accepted, completed, rejected, etc contracts
            //if(contract.ContractState==Contract.State.??)

            ConfigNode contractData = new ConfigNode("CONTRACT");
            contract.Save(contractData);

            MonoBehaviour.print("Rescue Contract Part Validator examining offered contract: " + contract + "\n" + contractData);

            int partID = contractData.GetIntValue("partID");
            if (partID != 0)//part already assigned, contract is already accepted?
            {
                return;
            }


            // apparently this is a private enum somewhere, cannot reference at run-time through plugin code
            // instead have to rely on manually examining the enum.ordinal value (integer representation)
            // experimentations have shown that '2' is the recoveryType value for 'parts'
            // key = recoveryType
            // enum values:
            // 0 = ?  invalid/none?
            // 1 = ?  lone kerbal?
            // 2 = part
            // 3 = ?  1+2 (kerbal + part?)

            int type = contractData.GetIntValue("recoveryType");
            if (type != 2) { return; }//only care about part-recovery contracts

            string partName = contractData.GetStringValue("partName");
            if (!isValidRecoveryPod(partName))
            {
                contractData.SetValue("partName", getRandomRecoveyPod(), false);                
                Contract.Load(contract, contractData);
            }
        }

        private bool isValidRecoveryPod(string name)
        {
            //quick and dirty array.contains test for the input name
            return Array.Exists(approvedPodTypes, m => m == name);
        }

        private string getRandomRecoveyPod()
        {
            if (approvedPodTypes.Length <= 0) { return "landerCabinSmall"; }
            int chosen = rng.Next(approvedPodTypes.Length);
            return approvedPodTypes[chosen];
        }

    }
}

 

Thanks again for the info and examples, would have taken me weeks-to-months to track this stuff down on my own.

Share this post


Link to post
Share on other sites
5 hours ago, Shadowmage said:

as the Enum that was referenced appears to be private/inaccessible (at least in the .dll's I'm using).

It is, that's why I defined my own (also in code above). You might want to be more discerning about the contracts you save, though. The player can't accept contracts in flight barring some kind of mod and saving the contracts can be garbagey and slow. I've seen a mod at some point that constantly rejected certain contracts which would be a performance and garbage collection nightmare for you

Share this post


Link to post
Share on other sites
1 hour ago, xEvilReeperx said:

It is, that's why I defined my own (also in code above). You might want to be more discerning about the contracts you save, though. The player can't accept contracts in flight barring some kind of mod and saving the contracts can be garbagey and slow. I've seen a mod at some point that constantly rejected certain contracts which would be a performance and garbage collection nightmare for you

Noted;  its mostly prototype code at this point, haven't even compiled it to test it yet.  I'll do some profiling/debugging to see where/how it can be optimized; reducing/minimizing garbage generation is always a concern for me, and will be doing the same for this after I verify functionality.

Share this post


Link to post
Share on other sites
This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.