Jump to content

Contract Modding Information for Mod Authors


MrHappyFace

Recommended Posts

It looks like contractsInExistance just gives you the total of all types of contracts.

Use ContractSystem.Instance.GetCurrentContracts<*YourContractType*>().Count() to find out the number of contracts of a given type. This presumably counts both active and offered contracts.

GetCurrentActiveContracts probably returns the number of accepted contracts of a given type.

Yeah I figured something else out, but will try this method also.

Edit yeah that works much better less code. :)

Edited by malkuth
Link to comment
Share on other sites

Ok, if you do not mind, I have a stupid question. Is the function "OnDock()" a function that we are overriding or is a function we are completely defining from scratch?

Thank you.

That is made from scratch and is part of DockingParameter Code. Thats the part that decides if you are docked or not and uses the OnPartCouble Event to trigger the check.

To write a contract you need the contract and the Parameter. Contract is the actual contract that you see in game. The Parameter is where your code is to see if the contract is complete or not. So in this case we want to check for a docking event. and that is what dockingParameter does.

Link to comment
Share on other sites

Okay how the heck can a ContractParameter tell if it's in a contract that has been accepted? I have contract parameters now that create objects, and the contracts that are *sitting in the mission board, that haven't been accepted yet* are creating them too. The creation code is happening in the ContractParameter, so how can a parameter see if it's parent has been accepted or not?

I've tried various methods, it's not a trivial thing, as far as I can tell.

Edit: this.Root.ContractState == Contract.State.Active ... guess it was that trivial. :P

Edited by Arsonide
Link to comment
Share on other sites

Something noticed wonder if anyone else noticed. While testing contracts I have noticed that if you have a contract and working in it. All older versions that are stored in missioncontrol completed contracts get wiped if you change the contracts or add to them.

Link to comment
Share on other sites

It looks like contractsInExistance just gives you the total of all types of contracts.

Use ContractSystem.Instance.GetCurrentContracts<*YourContractType*>().Count() to find out the number of contracts of a given type. This presumably counts both active and offered contracts.

GetCurrentActiveContracts probably returns the number of accepted contracts of a given type.

After working some more with these. And developing more contracts I have come to conclusion that GetCurrentContracts is actaully all contracts including Archived or finished ones. This means this contract can only ever be done once while the Archives contain the finished contract not what I want. :( Which brings us back to the original problem that too much contract spam and allows the player to spam contracts of same type with same vessel. Not a good situation.

I tried this.Root.ContractState == Contract.State.Active which is the same as GetCurrentActiveContracts.

But if your doing a simple check like in Generate()



if (this.Root.ContractState == Contract.State.Active)
{
Debug.Log("contract is generated right now terminating Deliver Satellite");
return false;
}

this won't stop contract spam because its looking only for active contracts (or accepted)

there is a contract.State.Offered but does not seem to work.

Must be a way to do it, because the default contracts don't get spammed.

I will keep trying to figure it out, and might just have to accept that contract spam is going to happen and player is going to be able to cheat it.

Edit Update : Found a way around this. But I just realized something. Seems anything that is in Archives as finished doesn't show up again. Is there a cool down on contracts when they are finished? Or after finished are they never able to be done again.

I never noticed this before lol.

Edited by malkuth
Link to comment
Share on other sites

Edit Mistake I made a error in stating that public int totalContracts = ContractSystem.Instance.GetCurrentContracts<OrbitalScanContract>().Count(); counts Completed missions. this is not the case and I have updated the post with the correct info. The problem was the combined issues making it seem that was the case.

-------------------------------------------------

Ok, the info above I pretty much figured out whats going on. Still have a little work on finding some things out but here goes.

When you finish a mission KSp saves it to your persistent file. And then adds your completed mission as part of the Archives. All well and good.

The problem is that if you want a repeating mission KSP looks at the archives and the GetHashString(). If the GetHashString() match it will skip generation of that Contract again.

So this part of code is what is recorded into the persistent file.


protected override string GetHashString()
{
return "Conduct Orbital Research around kerbin" ;
}

this is title of the contract which is fine. The problem is that once finished this title will be recorded and since the title never really changes you can never get this Contract again. You could go like this.


protected override string GetHashString()
{
return return "Conduct Orbital Research around kerbin" + " - Total Done: " + TotalFinished;
}

again this is more dynamic. If the bodytype is kerbin for first contract and is finished. Then Duna for 2nd time contract. The 2nd contract will show up. But the kerbin one will never show again (its already done in case its generated again as kerbin).

So in a nutshell if you want repeating missions on kerbin this is still a problem. Be back to this in a little bit.

2nd problem that annoys me about contracts, is the amount of Same Contract spam you get that are offered. To many of same contracts is bad because the player can select all of them and do them at same time with same vessel (unless you code around it)

Using this to control how many contracts you have at 1 time works..

public int totalContracts = ContractSystem.Instance.GetCurrentContracts<OrbitalScanContract>().Count();

if you get the count, then you can do a check against that count and cancel generation of contract if it fails. Like below.

Snippet of code




public int totalContracts;
public int TotalFinished;


protected override bool Generate()
{
totalContracts = ContractSystem.Instance.GetCurrentContracts<DeliverSatellite>().Count();
TotalFinished = ContractSystem.Instance.GetCompletedContracts<DeliverSatellite>().Count();

Debug.Log("Satellite Delivery Totalcontracts " + totalContracts + " - " + " Total Finsihed " + TotalFinished);
if (totalContracts >= 1)
{
Debug.Log("contract is generated right now terminating Normal Satellite Mission");
Debug.Log("count is " + totalContracts);
return false;
}
//Place Rest of code.
}

I use totalcontracts for the actual check.

And I use TotalFinished for this next part. You don't need totalFinished, unless you want an easy way to change titles.

Now to the title. Again you can come up with your own little thing if you wish but simply adding this to a title will change it everytime if you want Repeated Missions.


protected override string GetHashString()
{
return "Conduct Orbital Research around. " + targetBody.theName + ": " + TotalFinished ;
}

so HashString will look like this in case of targeBody is kerbin.

Conduct Orbital Research Around Kerbin: 1

I'm using the TotalFinsished float to change the title by how many of the same titles are completed.

More testing is needed but seems to be working now.

Hope this helps.

Edited by malkuth
Link to comment
Share on other sites

I was curious about the multipliers used for setting rewards and penalties for contracts so I just printed out results from each body with all values set at 1.

If you set a Celestial Body in the methods for this (setFunds, setScience, etc...) it multiplies whatever number you put in a by a certain value. I don't know if these numbers are stored somewhere like the science multipliers, that would be nice for people who want to reduce rewards.

Edit: Actually, they are affected by contract prestige too.

Those values are:


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

Edited by DMagic
Link to comment
Share on other sites

I was curious about the multipliers used for setting rewards and penalties for contracts so I just printed out results from each body with all values set at 1.

If you set a Celestial Body in the methods for this (setFunds, setScience, etc...) it multiplies whatever number you put in a by a certain value. I don't know if these numbers are stored somewhere like the science multipliers, that would be nice for people who want to reduce rewards.

Edit: Actually, they are affected by contract prestige too.

Those values are:


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

Dam, wish I would of know this before I made my own multipliers for the planets. :)

Link to comment
Share on other sites

Just wanted to share a useful tidbit I discovered for removing stock contract types that you may not want active.

The following seems to work well:


if ( ContractSystem.ContractTypes.Contains( typeof( Contracts.Templates.RescueKerbal ) ) )
{
ContractSystem.ContractTypes.Remove( typeof( Contracts.Templates.RescueKerbal ) );
}

I'm triggering it within the onContractsLoaded GameEvent, and so far so good. Previously, I was deleting the individual contract instances I didn't want with the onContractsListChanged GameEvent, but unfortunately discovered that this would result in the Mission Control GUI sometimes winding up with lingering remnants of those contracts in the GUI if the contracts were generated while that screen was active (like if the player declines one, causing others to be generated on the spot).

This on the other hand removes the mission types themselves, so they aren't generated in the first place.

The above may also come in very handy for modifying stock contracts as I'm planning to use it to remove the stock ones entirely and replace them by child classes of my own that insert additional requirements such as not having tests for jet engines on the Mun and such ;)

EDIT: And as a follow up to what I was planning above, it seems to also work very well.

For example, I just removed all part testing contracts on the surface of Kerbin, first by deleting the stock PartTest contracts with the onContractsLoaded GameEvent as follows:


if ( ContractSystem.ContractTypes.Contains( typeof( Contracts.Templates.PartTest ) ) )
{
ContractSystem.ContractTypes.Remove( typeof( Contracts.Templates.PartTest ) );
}

And then created my own child class of PartTest that inserts additional conditions into Generate():


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

using Contracts;
using Contracts.Parameters;

namespace BTSM
{
class BTSMContractPartTest : Contracts.Templates.PartTest
{
protected override bool Generate()
{
if ( base.Generate() )
{
if ( BTSMUtils.IsSurfaceOrSplashedKerbinContract( this ) )
{
Debug.Log( "BTSMContractPartTest: Deleting Kerbin Surface Part Test Contract: " + Title );

return false;
}

return true;
}

return false;
}
}
}

Seems to work perfectly. Because the contracts fail within Generate() instead of being removed afterwards, the Mission Control GUI updates properly, and doesn't display the discarded contracts.

Edited by FlowerChild
Link to comment
Share on other sites

As a small addendum to what I posted above about removing stock contracts:

The one circumstance I've found where it doesn't work out right is when the player starts a new career save immediately after starting up KSP. If they load an old career first, then quit to main, then start a new one, it works fine which is why this caught me.

The problem is that the onContractsLoaded game event is only called when existing contracts are loaded, not when the contract system itself is loaded (as in, when the ContractSystem.ContractTypes list is initialized).

The workaround I used for this is unfortunately not very clean, as in the above scenario the instances of the initial stock contracts are created immediately right after the ContractSystem is initialized with no game event taking place between the two. What I wound up doing is also deleting the stock contract types with onContractsListChanged IF a static flag I have set indicates that those contracts haven't already been deleted (as far as I can tell ContractTypes is only ever initialized once, no matter how many saved games are loaded or created), and if they haven't, following that by clearing the list of existing contracts on offer through a call to ContractSystem.Instance.ClearContractsCurrent(), which seems to force the system to reinitialize. In practice, the flag acts as a cludgy indication of whether this is the first time contracts have been generated within a save game (since OnContractsLoaded() will be called first if not).

Like I said, not particularly clean, and doesn't make for a good code snippet as a result, but it does work. Such code will be in the next version of BTSM I'm releasing shortly if further reference is desired.

Edited by FlowerChild
Link to comment
Share on other sites

Just wanted to share a useful tidbit I discovered for removing stock contract types that you may not want active.

Thanks for this. It was a help for a couple of the simple things I'm fiddling around with.

It's nice that the devs give us these hooks, but it would be nicer if they told us exactly when and in what situations they fire. (Is it before or after the dialog or widget is generated?) And it would have been nice to have an onContractGenerated event, or better yet, give us some degree of control over the ContractSystem static methods, particularly the one used to generate new contracts.

It's also typical of KSP code that they don't trigger modifier events upon initialization. For instance, OnFundsChanged is not called when the initial funding amount is set. onContractListChanged is not called when the initial contracts are added to the list. Argh.

Link to comment
Share on other sites

Thanks for this. It was a help for a couple of the simple things I'm fiddling around with.

It's nice that the devs give us these hooks, but it would be nicer if they told us exactly when and in what situations they fire. (Is it before or after the dialog or widget is generated?) And it would have been nice to have an onContractGenerated event, or better yet, give us some degree of control over the ContractSystem static methods, particularly the one used to generate new contracts.

It's also typical of KSP code that they don't trigger modifier events upon initialization. For instance, OnFundsChanged is not called when the initial funding amount is set. onContractListChanged is not called when the initial contracts are added to the list. Argh.

I had a huge issue with this. Use to use OnSceneChange for MCE code to do certain things like Charge for certain items. This does not work for .24 with funds, because you can't change anything like funds during a scenechange. The scene has to be established for things to stick. Was very annoying.

Link to comment
Share on other sites


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

using Contracts;
using Contracts.Parameters;

namespace BTSM
{
class BTSMContractPartTest : Contracts.Templates.PartTest
{
protected override bool Generate()
{
if ( base.Generate() )
{
if ( BTSMUtils.IsSurfaceOrSplashedKerbinContract( this ) )
{
Debug.Log( "BTSMContractPartTest: Deleting Kerbin Surface Part Test Contract: " + Title );

return false;
}

return true;
}

return false;
}
}
}

Seems to work perfectly. Because the contracts fail within Generate() instead of being removed afterwards, the Mission Control GUI updates properly, and doesn't display the discarded contracts.

I think Generate has to return true for it to even add it to the list at all, and, as far as I know, base.Generate() returns false, so try removing the

if(base.Generate())...

Edit:

As a small addendum to what I posted above about removing stock contracts...

If you remove the stock contracts from that list, then what happens to existing worlds with those contracts? Why not just prevent them from generating, or add something to listen for new contracts and obliterate the ones you don't want. This way, the existing contracts can still be completed, but no more will generate.

Edited by MrHappyFace
Link to comment
Share on other sites

Anyone figure out yet how to make parameter goals in a contract follow a certain path.

Like goal 2 can't be done without goal 1 being done. Or goal 2 can't be done by same vessel that did goal 1.

Must be a way to stop all goals from firing at same time when same goal.

I thought of having. Values saved outside the contract system. Like bool that turns true when goal 1 is done. The problem is of course that ship that did goal 1 will instantly fire off goal 2.

Guess having bool and ship Id get attached would work, but it seems the contract system only sets up the initial values at gen.

I don't know just trying to figure it out, without making too much of hack.

Link to comment
Share on other sites

I thought of having. Values saved outside the contract system. Like bool that turns true when goal 1 is done. The problem is of course that ship that did goal 1 will instantly fire off goal 2.

Guess having bool and ship Id get attached would work, but it seems the contract system only sets up the initial values at gen.

I guess you'd have to pass variables up and down from contract parameter, to the contract, then back down to the next parameter.

If you want to block someone from using the same vessel for parameters one and two you could have a null vessel in the parent contract. Then set that vessel whenever the firsts parameter finishes. Then have the the second parameter check the parent contract's vessel property. If the contract's vessel is null then the second parameter can't complete, if the current vessel is the same as the contract's vessel then the second parameter can't complete, only when the vessel is not null, and different from the first could the contract complete.

You would just have to make sure to save and load all of this information so it doesn't get lost.

Link to comment
Share on other sites

I think Generate has to return true for it to even add it to the list at all, and, as far as I know, base.Generate() returns false, so try removing the

That doesn't really make sense. I would then wind up testing additional conditions on non-existent experiments that failed during stock generation and thus may have variables in invalid states given I have no idea at what point in the initialization process the stock generation failed. I also *want* stock generation for the part test contracts there, as I certainly don't want to go through the trouble of coding the whole generation process for them myself when all I want is an additional condition on them. And yes, base.Generate() returns true there every time stock would normally generate a partTest contract.

I should also note that the code I posted works great, and I've released it to the public already within BTSM so others have been hammering on it as well. Thus, it doesn't need to be "fixed" :)

If you remove the stock contracts from that list, then what happens to existing worlds with those contracts?

As far as I know they should be fine. My code doesn't wipe out the class itself, just the instance of the class in the list that is used to generate the contracts, so any existing references to the class shouldn't run into trouble.

It's not something I've tested mind you given the mod I'm doing this for is the kinda thing you don't install on an existing save.

Why not just prevent them from generating

If you know a way to "just" do that, I'm all ears :)

And really, that's exactly what my code above does.

or add something to listen for new contracts and obliterate the ones you don't want.

If you read through my posts above, that was the method I was using previous to figuring out the above. Problem with that is if the contracts are generated while you're in the mission control screen, the UI elements are also generated for them, and even if you then remove the contract, the UI elements remain potentially causing all kinds of problems when the player interacts with them. So, I started digging for a way to get around that, and the above is what resulted.

EDIT: I think I may know where the confusion is originating here. Note that my custom part test contract above isn't just a contract: it's a child class of the stock PartTest contract template. Thus when I'm calling base.Generate(), I'm not calling Contract.Generate() (which I suspect is what you thought), but rather PartTest.Generate().

Edited by FlowerChild
Link to comment
Share on other sites

Another small useful tidbit I just discovered:

If you do not wish to have a deadline or expiry date for a mission, you can omit SetExpiry() and SetDeadlineYears() in your generate function and instead put in the follow:


deadlineType = DeadlineType.None;
expiryType = DeadlineType.None;

This allows generation on one-time "story" missions like the early-game stock ones for setting an altitude record, first getting into space, or what have you.

Link to comment
Share on other sites

I finally got around to figuring out exactly what all of the "value" fields are in the persistent file.

Each contract's final entry (before anything custom added in the save method) is the "value =" string, with a 12 comma delineated entries.

From left to right these are: (I think the time fields are doubles and the rewards are floats)


1: Time in seconds until an offered contract expires
2: Time in seconds of a contract's duration after it is accepted
3: Funds: Advance payment
4: Funds: Completion payment
5: Funds: Penalty amount
6: Science: Completion reward
7: Reputation: Completion reward
8: Reputation: Penalty amount
9: Universal Time: Time in seconds when an offered contract will expire
10: Universal Time: Time in seconds when the contract was accepted
11: Universal Time: Time in seconds when an accepted contract will end
12: Universal Time: Time in seconds when a contract was failed/expired (I don't think this triggers for successful completion)

And the contract parameters:


1: Funds: Completion payment
2: Funds: Penalty amount
3: Science: Completion reward
4: Reputation: Completion reward
5: Reputation: Penalty amount

The last few entries on the contract's value line are particularly useful if you are testing something and don't want to reset or start a new contract. You can just edit the time values and continue on with the same contract.

Link to comment
Share on other sites

Those values are:


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

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.

Like, I seem to be getting a 1.09 multiplier for Trivial Kerbin missions, and that's a multiplier to the prestige value, while it seems to act as a divisor to the funds values :P

Also, the numbers seem to be rounded off in the mission control screen, whereas when you actually complete the contract, you get different fractional values.

I am also beginning to suspect that the player's current prestige level may act as an additional modifier on the payouts. Seems to be rather messy overall, which I began to discover when I wanted certain contracts to payout nice round values like say 25,000 funds and 40 prestige :)

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...