Jump to content

Contract Modding Information for Mod Authors


MrHappyFace

Recommended Posts

With regards to the following in the example code in the OP:


protected override string GetDescription ()
{
//those 3 strings appear to do nothing
return TextGen.GenerateBackStories (Agent.Name, Agent.GetMindsetString (), "docking", "dock", "kill all humans", new System.Random ().Next());
}

I think that MissionSeed may actually be a more appropriate value for the last parameter of GenerateBackStories() like this


return TextGen.GenerateBackStories (Agent.Name, Agent.GetMindsetString (), "docking", "dock", "kill all humans", MissionSeed);

As I believe that will generate a consistent back story that won't change when you load a new savegame or what have you. I believe the primary purpose of MissionSeed is to provide that kind of persistence for randomly generated data without having to store the extra data (like the full back story string) to the save file.

Link to comment
Share on other sites

After playing around with this some, I think it may be a little more complicated than the above, in that the multipliers seem to not be nice round numbers.

I believe the values can be found in GameVariables. I suppose you could just print out all of the floats in there to see their values, though I think the actual calculations are more complicated than that.

It looks like agency mentality (which I haven't really looked into at all) also plays a factor.

Link to comment
Share on other sites

With regards to the following in the example code in the OP]

I think that MissionSeed may actually be better...

As I believe that will generate a consistent back story that won't change when you load a new savegame or what have you. I believe the primary purpose of MissionSeed is to provide that kind of persistence for randomly generated data without having to store the extra data (like the full back story string) to the save file.

That was never meant to be good, just an example, I also threw it together in 30 minutes, so its probably one of the worst blocks of code I've ever wrote (which is saying a lot)

:P

Link to comment
Share on other sites

That was never meant to be good, just an example, I also threw it together in 30 minutes, so its probably one of the worst blocks of code I've ever wrote (which is saying a lot)

:P

Wasn't a critique or anything, as I really appreciate you putting together this thread and the example code. It's actually saved me a heck of a lot of time in terms of at least providing a base-line template of how the contract system works :)

Just trying to provide additional info for folks as I discover it myself, given something like the use of a persistent seed like that to generate consistent random results might not be immediately apparent to everyone.

Link to comment
Share on other sites

So what is the seed actually generating a random number, sequence?

Yup, pretty much.

As far as I understand, it's just acting as a seed into the random number generator (if you pass it in as a parameter). As you probably know, computers can't produce random numbers, so any rand() type calls just generate a pseudorandom number based on a sequence of numbers determined by the seed value you pass in. Under most circumstances you pass in a seed based on the local computer's clock value or what have you so that you get a more random sequence out of it, but you can also pass in your own value if you want to generate the same sequence as in another instance.

Hence why in a game like Minecraft, you can get exactly the same world out of the same seed number input at creation without having to download a huge world file or something (the seed basically acts as the ultimate form of compression). In this case I suspect Squad has set things up so that a random seed is created on contract generation and saved with the contract, so that this value can then be used each time the random description is generated so that it is consistent each time the user views the same contract.

You can see the mission seed value attached to each of the contracts in persistent.sfs in lines like these:


seed = 1817138329

They seem to be automatically generated for custom contracts as well, as I haven't had to set the seed values for my own contracts.

Sorry if the above was more information than you desired :)

Edited by FlowerChild
Link to comment
Share on other sites

The problem is that if you want a repeating mission KSP looks at the arrchives and the Titles. If the titles match it will skip generation of that Contract again.

Just a small addendum on this point:

I don't *think* it's the actual titles that prevent repeating contracts, but rather the result of GetHashString(). I just created a repeating contract that has the same title over multiple instances ("Conduct a weather survey"), but which returns a different hash string for each (I based it on the GUID), and it appears I can generate as many instances of it as I would like.

Link to comment
Share on other sites

Just a small addendum on this point:

I don't *think* it's the actual titles that prevent repeating contracts, but rather the result of GetHashString(). I just created a repeating contract that has the same title over multiple instances ("Conduct a weather survey"), but which returns a different hash string for each (I based it on the GUID), and it appears I can generate as many instances of it as I would like.

Sounds good. I started to notice this also.

Link to comment
Share on other sites

Can a contract be created that provides a certain constant or reoccurring reward while a certain condition is met?

For example for a space station to be built for funding to be provided daily for each day the space station meets a certain criteria, such as a module being operational.

Link to comment
Share on other sites

It might also be possible to do that through the contract system as contract parameters can have rewards for each individual one (see the "Explore Body" contracts for reference). You could then have one parameter for setting up the station, which completes normally and pays a certain reward once, then another one which never completes but which pays out at the end of each day through its Update() function.

Not sure how many advantages that would offer over Malkuth's approach, but might be a little more intuitive for the player to have the contract constantly on display, and might provide you with an easy save/load mechanism since the parameters for the contract (like the timer) could then be stored within the contract itself.

Link to comment
Share on other sites

Another small discovery:

Turns out penalties for failing contracts aren't automatically applied if a contract is cancelled by the player (which is a bit silly given it opens the door to exploits where a player can cancel a contract just before they fail it to avoid penalties). However, overriding PenalizeCancellation() and calling PenalizeFailure() within it resolves this.

EDIT: Actually, if you are paying out an advance for a contract THAT (and only that) gets removed when the player cancels. I first noticed this on a contract where I was paying no advance but had penalties for failure attached, and just tried it out on one with an advanced payment. With no advance, nothing is deducted, with advance, only that is deducted. In both cases the failure penalties themselves are not applied.

Edited by FlowerChild
Link to comment
Share on other sites

It might also be possible to do that through the contract system as contract parameters can have rewards for each individual one (see the "Explore Body" contracts for reference). You could then have one parameter for setting up the station, which completes normally and pays a certain reward once, then another one which never completes but which pays out at the end of each day through its Update() function.

Not sure how many advantages that would offer over Malkuth's approach, but might be a little more intuitive for the player to have the contract constantly on display, and might provide you with an easy save/load mechanism since the parameters for the contract (like the timer) could then be stored within the contract itself.

That would work too, in fact if you have set amount of times set up, you could set up a parameter that follows some sort of countdown system you come up with. Each parameter you set up can be a certain time distance from the original Time when the countdown started.

So player puts station in orbit, you have a parameter that watches for this, when it finishes that parameter also starts a countdown based on 30 days each. Each 30 days you get a new payment.

If the contract is for 6 months you can have 6 more parameter set up each is 30 days from original time. So 0+ 30. 2nd 0 + 60, 3rd 0 + 90 (you will have to convert to seconds since thats what KSP follows).

That way each parameter you set up can be 30 days, and the player can keep tabs on where he is. The only downside is this will clutter up your Active contracts.

You could even keep it random if you wished. Set up the base contract for max 1 year, and make the amount of time random. So if random time is 3 months, only 3 months out of the year will generate for that contract.

Have fun.

Link to comment
Share on other sites

To avoid the contract-list clutter you *may* (I haven't tried this yet, but it looks like it's possible) be able to dynamically modify the parameters of the contract so that you only add the parameter for the next 30 days after the previous one is completed, and remove the previous one as well.

The contract list seems to be fairly robust in terms of updating to reflect changes in data. Like I have some contracts that are dependent on getting a specific Kerbal to specific places, which Kerbal it is determined on launch, and the list updates automatically to changes in title and such that reflect the Kerbal's name. Now, whether it would be able to handle parameters being added and removed on the fly is another story, but it's worth a shot.

EDIT: Or, you may be able to create all the parameters at start and only display the titles of the ones that are active. I noticed the part test contracts actually has a hidden parameter that acts as the parent of the others, so I think making them invisible is possible. Again, doing so dynamically is another story and I haven't tested it myself, but might be safer and/or easier than dynamically creating and removing the parameters, to just hide or display them based on context.

Edited by FlowerChild
Link to comment
Share on other sites

Just a small addendum on this point:

I don't *think* it's the actual titles that prevent repeating contracts, but rather the result of GetHashString(). I just created a repeating contract that has the same title over multiple instances ("Conduct a weather survey"), but which returns a different hash string for each (I based it on the GUID), and it appears I can generate as many instances of it as I would like.

Even though I wasn't sure exactly what it did, I took no chances with this having overlap: return (this.Root.MissionSeed.ToString() + this.Root.DateAccepted.ToString() + this.ID);

This is for a parameter, a contract wouldn't need the third number there. I don't think there's much room for overlap there.

Link to comment
Share on other sites

I believe it's the Contract.ID (not the Guid, the long ID) field that gets checked. This doesn't get saved so I assume it is generated on load, but I think it takes into account both contract hash strings and child parameter hash strings.

EDIT: Or, you may be able to create all the parameters at start and only display the titles of the ones that are active. I noticed the part test contracts actually has a hidden parameter that acts as the parent of the others, so I think making them invisible is possible.

What do you mean by hidden? It has the PartTest parameter that is the parent of all the others, but that one is always visible in the contract window.

Edited by DMagic
Link to comment
Share on other sites

What do you mean by hidden? It has the PartTest parameter that is the parent of all the others, but that one is always visible in the contract window.

Yeah, I mean the part test parameter. The part test contract itself is visible, but it looks like the parameter itself doesn't show up. There's an expandable list of notes instead, and I'm not sure whether that's coming from the contract or the parameter. Looking at it again, you may be right though in that there's a "test foo" item at the very top of the contract that may be associated with the parameter itself. On first look I think I had assumed that was associated with the contract.

Anyways, just theorizing and throwing out ideas based on what I've seen as I haven't tested it out myself.

Link to comment
Share on other sites

I believe the values can be found in GameVariables. I suppose you could just print out all of the floats in there to see their values, though I think the actual calculations are more complicated than that.

I started to dig into the GameVariables class today and just wanted to post emphasizing how much valuable info related to contracts is in there. I used it to zero out the reputation reward for recovering Kerbals (since that's so easily exploitable) for example in no time flat.

I'd highly recommend anyone modding contracts check it out.

Link to comment
Share on other sites

I started to dig into the GameVariables class today and just wanted to post emphasizing how much valuable info related to contracts is in there. I used it to zero out the reputation reward for recovering Kerbals (since that's so easily exploitable) for example in no time flat.

I'd highly recommend anyone modding contracts check it out.

I'm not sure if I ever posted it here, but I've mentioned it elsewhere. The GameVariable.GetContractDestinationWeight() method returns the CelestialBodyScienceParams RecoveryValue amount, the thing that determines how much science a vessel returned from a given planet is worth. The default values of which, along with all of the other ScienceParam values, can be found here: https://github.com/DMagic1/Science-Param-Loader/blob/master/ScienceParamLoader/ScienceParams.cfg

Link to comment
Share on other sites

  • 2 weeks later...

I'm reading the original post, and can't understand: how do you compile these cs files? Do you need MSVS or other IDE? Any chance to compile this on Linux?

God, why there's no Python API yet...

found the monodevelop tutorial

Edited by Kulebron
Link to comment
Share on other sites

Ok, I hope people are still following this thread as I just figured out something rather crucial about the contract system :)

I was noticing that as I was adding more and more contracts to the game, a lot of them were being generated only to be then removed by the contract system. There's a debug output that stock generates when this happens.

Playing around a bit, I figured out that the number of contracts *on offer* seems to be limited to a specific number per prestige level, that contracts above a certain prestige level are removed if the player's reputation is too low (I don't know the precise values involved as that would take a LOT of test cases to figure out), and that the number offered at a given prestige level seems to also be dependent on reputation.

So, with a bit of educated guess-work and a bunch of debugging output code, this is what I figured out:

When a contract has its Generate() function called, it already has its prestige level set to the prestige level of contract the contract system wishes to generate. In other words, if a new trivial prestige contract would create a contract that would exceed the limit on the number of trivial contracts that should be present at any given time Generate() will NOT be called with the prestige level set to trivial.

What this means is you should NOT return true from Generate() if you would be creating a contract at a prestige level other than the one specified. Either that, or you should tailor your contract generation so that it generates a contract only of the prestige level specified (i.e. you should NOT set it yourself).

A simple example from my own code where I have a contract that I only want to generate at the trivial prestige level in case any of that wasn't clear:


protected override bool Generate()
{
if ( prestige != ContractPrestige.Trivial )
{
return false;
}

// regular contract generation code here
}

This is probably also why when you create a bunch of custom contracts, certain stock ones stop being generated. The stock contracts are testing whether they should generate based on the prestige, while custom contracts that don't wind up saturating the system so that the stock ones no longer have room to generate in.

Anyways, like I said, I really hope the other contract mod authors are still following this thread, as this seems to be a rather major thing about the way the system works, and I'm still surprised I managed to figure it out given how less than obvious it is :)

Link to comment
Share on other sites

It seems like the mod contract glut is a problem that's not likely to go away.

There are a number of ways to generate fewer contracts of each type. One is to limit the number of each type as has been discussed before. Another is limiting the acceptable prestige rating as shown above (it would also be nice if the stock "explore" contracts didn't permanently take up a 3 star slot...).

I've also been trying to delay the point when my contracts are given. Anyone playing a long career game will surely have plenty of opportunities to use any given contract type, so there's no reason to spam them with 5 or 6 DMagic contracts as soon as they complete the first launch.

The base Contract class has a some methods to determine which planets you've been to, but they aren't very granular. GetBodies_Reached just returns all of the CelestialBodies you've visited; entering their SOI is enough to mark them as "reached".

If you want to get more specific you can use something like:


public override bool MeetRequirements()
{
return ProgressTracking.Instance.NodeComplete(new string[] { "Minmus", "ReturnFromOrbit" });
}

This example is in the MeetRequirements() method, but I've also been using them in Generate() too. This is the example from my asteroid contracts, you have to enter orbit around Minmus and return to Kerbin's surface before they will be generated (I figure this is a good demonstration of the ability to rendezvous with a relatively small target in a different orbit).

You can get really specific about the requirements for any contract type using these progressTracking nodes. You can use something like "Jool", "FlyBy" if you want to give contracts for Jool's moons.

I'm not sure what happens when other addons change the planet names, there might be a better way of specifying that (maybe use the flightglobals index to find the planet), but I think this is a good way to slow down contract generation, or just be more specific about what requirements you want before you start offering a contract.

Link to comment
Share on other sites

There are a number of ways to generate fewer contracts of each type. One is to limit the number of each type as has been discussed before. Another is limiting the acceptable prestige rating as shown above (it would also be nice if the stock "explore" contracts didn't permanently take up a 3 star slot...).

The thing is, from what I've discovered, limiting the number of a type of contract isn't enough on its own.

The problem with doing this is that if it causes the contract system to exceed the number it wants at a particular prestige level, then *it will forcibly remove an existing contract on offer*.

This is obviously bad as it will force stock contracts out of the loop, and particularly bad for you guys that care about compatibility with other mods as it may force another mod's contract out of the game. Beyond that, it means the contract list will be in a constant state of flux from update to update as contracts get forced deleted then regenerated.

IMO, we should probably NEVER be setting the prestige level of our own contracts for those reasons. Generate() isn't so much what the function name would imply, as it's more a request to generate a contract at a very specific prestige level. IMO, this would have been a heck of a lot clearer if prestige were passed in as a parameter, but it effectively functions as if it was.

I've also been trying to delay the point when my contracts are given. Anyone playing a long career game will surely have plenty of opportunities to use any given contract type, so there's no reason to spam them with 5 or 6 DMagic contracts as soon as they complete the first launch.

The base Contract class has a some methods to determine which planets you've been to, but they aren't very granular. GetBodies_Reached just returns all of the CelestialBodies you've visited; entering their SOI is enough to mark them as "reached".

For this one, what I've been doing is using the completion of "Explore Body" contracts as an indication that the player has not only reached a particular body, but is also capable of performing basic operations around it.

I have some commercial payload contracts which I only generate for particular bodies once the explore contract has been completed. In other cases, I have my own contracts whose completion I then make other contracts dependent upon.

So, you don't get a contract to attain orbit until you've completed the one for reaching space or what have you. You can basically setup your own generation conditions through the other contracts you create.

Edited by FlowerChild
Link to comment
Share on other sites

  • 4 weeks later...

Prestige: Trivial Significant Exceptional
Sun: 4 5 6
Kerbin: 1 1 2
Mun: 2 2 3
Minmus: 2 3 4
Moho: 7 9 10
Eve: 5 6 8
Duna: 5 6 8
Ike: 5 6 8
Jool: 6 8 9
Laythe: 8 10 12
Vall: 8 10 12
Bop: 8 10 12
Tylo: 8 10 12
Gilly: 6 8 9
Pol: 8 10 12
Dres: 6 8 9
Eeloo: 10 12 15

A small discovery based on the above:

The mystery value that is governing all this is RecoveryValue in CelestialBodyScienceParams, which in turn can be accessed through any CelestialBody.

As DMagic pointed out, it is then modified both by contract prestige and the contract's agent with different agents having different multipliers to both funds and prestige.

You can directly modify contract payments per body through RecoveryValue, just keep in mind you will also be modifying the science payout for recovering a vessel after returning from that body at the same time. Bit messy, but at least there's a way to modify it.

Link to comment
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.

×
×
  • Create New...