Jump to content

Contract Modding Information for Mod Authors


MrHappyFace

Recommended Posts

As you can see, there are now some contracts asking you to dock around Kerbin, of course, it needs some work (the insane rewards are insane), but it is basically the framework for a modded contract. It was also generated based on how far i had advanced (none at all, because it was a testing world, hence why it said Kerbin instead of Jool or something crazy like that)

Notes:

  • MOST Contract related stuff is found in the Contracts namespace
  • any class extending Contract (in the namespace Contracts) is automatically found and added

Creating a contract:

  1. Make a class that extends Contract, the game automatically finds all classes extending Contract and adds them in.
  2. override the following methods:
    • Generate()
    • CanBeCancelled()
    • CanBeDeclined()
    • GetHashString()
    • GetTitle()
    • GetDescription()
    • GetSynopsis()
    • MessageCompleted()
    • OnLoad()
    • OnSave()
    • MeetRequirements()

[*]In Generate(), you MUST call the following things:

  • base.SetExpiry()
  • base.SetScience(float reward, CelestialBody)
  • base.SetDeadlineYears(float numberOfYears, CelestialBody targetBody) OR base.SetDeadlineDays(float numberOfDays, CelestialBody targetBody)
  • base.SetReputation(float reward, float failiure, CelestialBody targetBody)
  • base.SetFunds(float advance, float reward, float failiure, targetBody)

[*]In Generate(), add some parameters using this.AddParameter(ContractParameter parameter, string id). I have no clue what id does, I just use null, which seems to work, Ill talk about ContractParameters later in this thread, but for now, just use new KerbalDeaths(), which will make you fail the contract if you kill any victims kerbals while the contract is active. The stock ContractParameters are in the namespace Contracts.Parameters, so try messing around with those.

[*]In MeetRequirements(), you can check if this contract should show up in mission control, and return false is it shouldn't and true if it should.

[*]the rest is customizable, but if you fill out the GetTitle(), GetDescription(), GetSynopsis(), and MessageCompleted(), it should show up in game. Mess around with the TextGen class (which, of course, is in the namespace Contracts)

ContractParameters:

  1. Make a class extending ContractParameter
  2. Override the following methods:
    • OnRegister()
    • OnUnregister()
    • OnLoad()
    • OnSave()
    • GetTitle()
    • GetHashString()

[*]In OnRegister(), put initialization code such as adding to a GameEvent, or instantiating a new MonoBehaviour

[*]In OnUnregister(), put code similar to what you would expect in OnDestroy(), such as removing a GameEvent added in OnRegister() or destroying a MonoBehaviour created in OnRegister()

[*]OnSave() and OnLoad() are where you handle persistence, keep in mind that in OnSave(), NEVER use node.SetValue(), ALWAYS usennode.AddValue()

[*]In GetTitle(), simply return the name of your parameter, like "Orbit " + targetBody.the name

[*]use base.SetComplete() to complete the contract.

Using ProgressTracking to select targets based on progress: such an imaginative title!

  • ​just mess around with the ProgressTracking class and the protected static methods in the Contract class, such as Contract.GetBodies_NextUnvisited(). Use these in Generate () in your contract class to select the targetBody parameter used in SetScience() and SetFunds() in your contract class. You should also pipe the targetBody parameter into the the ContractParameter you made.

Example Code:

Note: I dont care what you do with this code, its in the public domain

DockingContract.cs:


using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Contracts;
using Contracts.Parameters;
using KSP;
using KSPAchievements;
using ContractsPlus.Contracts.Parameters;

namespace ContractsPlus.Contracts
{
public class DockingContract : Contract
{
CelestialBody targetBody = null;

protected override bool Generate ()
{
targetBody = GetNextUnreachedTarget (1, true, false);
if (targetBody == null)
{
targetBody = Planetarium.fetch.Home;
Debug.LogWarning ("targetBody could not be computed, using Kerbin");
}

CelestialBodySubtree progress = null;
foreach (var node in ProgressTracking.Instance.celestialBodyNodes)
{
if (node.Body == targetBody)
progress = node;
}
if (progress == null)
{
Debug.LogError ("ProgressNode for targetBody " + targetBody.bodyName + " not found, terminating contract");
return false;
}

if (progress.docking.IsComplete)
{
Debug.Log ("Docking has already been completed for targetBody " + targetBody.bodyName + ", terminating contract");
return false;
}
bool manned = UnityEngine.Random.Range (0, 1) == 0;

this.AddParameter (new DockingParameter (targetBody, manned), null);
if (manned)
this.AddParameter (new KerbalDeaths(), null);

base.SetExpiry ();
base.SetScience (2.25f, targetBody);
base.SetDeadlineYears (1f, targetBody);
base.SetReputation (150f, 60f, targetBody);
base.SetFunds(15000f, 50000f, 35000f, targetBody);
return true;
}

public override bool CanBeCancelled ()
{
return true;
}
public override bool CanBeDeclined ()
{
return true;
}

protected override string GetHashString ()
{
return targetBody.bodyName;
}
protected override string GetTitle ()
{
return "Dock in orbit around " + targetBody.theName;
}
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());
}
protected override string GetSynopsys ()
{
return "Dock two vessels in orbit around " + targetBody.theName;
}
protected override string MessageCompleted ()
{
return "You have succesfully docked around " + targetBody.theName;
}

protected override void OnLoad (ConfigNode node)
{
int bodyID = int.Parse(node.GetValue ("targetBody"));
foreach(var body in FlightGlobals.Bodies)
{
if (body.flightGlobalsIndex == bodyID)
targetBody = body;
}
}
protected override void OnSave (ConfigNode node)
{
int bodyID = targetBody.flightGlobalsIndex;
node.AddValue ("targetBody", bodyID);
}

//for testing purposes
public override bool MeetRequirements ()
{
return true;
}

protected static CelestialBody GetNextUnreachedTarget(int depth, bool removeSun, bool removeKerbin)
{
var bodies = Contract.GetBodies_NextUnreached (depth, null);
if (bodies != null)
{
if (removeSun)
bodies.Remove (Planetarium.fetch.Sun);
if (removeKerbin)
bodies.Remove (Planetarium.fetch.Home);

if (bodies.Count > 0)
return bodies [UnityEngine.Random.Range (0, bodies.Count - 1)];
}
return null;
}
}
}

DockingParameter.cs:


using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Contracts;
using KSP;
using KSPAchievements;

namespace ContractsPlus.Contracts.Parameters
{
public class DockingParameter : ContractParameter
{
public CelestialBody targetBody;
public bool manned = false;

public DockingParameter(CelestialBody target, bool manned)
{
this.targetBody = target;
this.manned = manned;
}

protected override string GetHashString ()
{
return targetBody.bodyName;
}
protected override string GetTitle ()
{
return "Dock two vessels in orbit around " + targetBody.theName;
}

protected override void OnRegister ()
{
GameEvents.onPartCouple.Add (OnDock);
}
protected override void OnUnregister ()
{
GameEvents.onPartCouple.Remove (OnDock);
}

protected override void OnSave (ConfigNode node)
{
int bodyID = int.Parse(node.GetValue ("targetBody"));
foreach(var body in FlightGlobals.Bodies)
{
if (body.flightGlobalsIndex == bodyID)
targetBody = body;
}
}
protected override void OnLoad (ConfigNode node)
{
int bodyID = targetBody.flightGlobalsIndex;
node.AddValue ("targetBody", bodyID);
}

private void OnDock(GameEvents.FromToAction<Part, Part> action)
{
if (manned)
{
if (action.from.vessel.GetVesselCrew ().Count > 0 && action.to.vessel.GetVesselCrew ().Count > 0)
{
if (action.from.vessel.mainBody == targetBody && action.to.vessel.mainBody)
{
base.SetComplete ();
}
}
}
else
{
if (action.from.vessel.mainBody == targetBody && action.to.vessel.mainBody)
{
base.SetComplete ();
}
}
}
}
}

:)

Feel free to contribute and/or correct me if im wrong.

Edited by MrHappyFace
added source code
Link to comment
Share on other sites

Any plug-in coder would like to make a user-friendly interface ?

Basically, provide the same things but a in config file like:

name=a new contract

summary = do this

reward = a shiny kandy

description = do this or that this way

conditions = ...

restrictions = (science/parts/... allowed)

delay = 3.5 hours/days/months/years (or just h/d/m/y for parsing ease)

... other relevant fields...

what do you think ?

Link to comment
Share on other sites

Except plugins, especially on this forum (what with all this "post source and license") are, generally, a hassle. An easy, human-readable config would allow much more flexibility. Also, I've been hoping that it'd be possible to tweak Squad contract so that, for example, RSS users could have real company names and logos in there. There must be a plugin that allows editing contracts with a config file.

Link to comment
Share on other sites

Except plugins, especially on this forum (what with all this "post source and license") are, generally, a hassle. An easy, human-readable config would allow much more flexibility. Also, I've been hoping that it'd be possible to tweak Squad contract so that, for example, RSS users could have real company names and logos in there. There must be a plugin that allows editing contracts with a config file.

Company names and pictures are in GameData/Squad/Agencies

It looks pretty easy to add/change companies using module manager, but this is untested.

Notes:

  • MOST Contract related stuff is found in the Contracts namespace
  • any class extending Contract (in the namespace Contracts) is automatically found and added

That's almost as easy as I was hoping it would be. Thanks for taking the time to figure this all out.

Link to comment
Share on other sites

I approve of this thread! I was looking at the same classes last night, and came to the same conclusions. ContractParameters are fairly straightforward as well. Have you figured out what ContractPredicates are? From what I understand, they are abstract classes that Contracts and Parameters build on, and thus are not something that you really have to worry about, but I might be wrong.

EDIT: Contract ideas - satellite deployment, docking, payload deployment and/or docking, base/station building, orbital adjustment (object spawns with incorrect orbit, move to correct inclination/keosynchronous orbit whatev) using docking/claw, debris cleanup, easter egg hunt (gives player vague location and asks them to find something interesting vaguely - one shot mission), sample collection (kind of like the flag placement mission except the sample has to make it back to Kerbin to complete the mission.) ARM, Repair (player goes to spawned object and uses some context sensitive actions in EVA).

Edited by Arsonide
Link to comment
Share on other sites

EDIT: Contract ideas - satellite deployment, docking, payload deployment and/or docking, base/station building, orbital adjustment (object spawns with incorrect orbit, move to correct inclination/keosynchronous orbit whatev) using docking/claw, debris cleanup, easter egg hunt (gives player vague location and asks them to find something interesting vaguely - one shot mission), sample collection (kind of like the flag placement mission except the sample has to make it back to Kerbin to complete the mission.) ARM, Repair (player goes to spawned object and uses some context sensitive actions in EVA).

Once I figure out how to actually make contracts, missions like these and more will all be part of the "Cabana Corporation Research & Development: Sub-Contracting" (working title) pack. Along with possibly a passive "salary" addition, which I've covered elsewhere on a thread...

Link to comment
Share on other sites

That's all I can really think of for stock, perhaps there are more creative options, like a race contract that has you go between two waypoints as fast as possible, starting a timer when you hit the first waypoint, and failing if you don't make it to the second one in time, with constrictions like "cannot leave the ground" or "cannot touch the ground" or "must be splashed down" for boat racing. There are some mods that lend themselves well to contract types, like Kethane could have "scan this hex" or "drill x kethane out of this hex".

One thing I would like to point out if you plan on doing these...I will be working on this too, but my time is limited...if you add them and an agency, make the agency optional, for people who want the extra contract variety, but with the stock agencies. Perhaps release two versions. (Just my two cents.)

Link to comment
Share on other sites

Update: I added ContractParameter and ProgressTracking information and uploaded some example code.

I really appreciate it man, this was my first thought about 0.24 when I realized it only had five templates: "we need to double that number". Your pioneering is a great help to the game and the community.

Link to comment
Share on other sites

I really appreciate it man, this was my first thought about 0.24 when I realized it only had five templates: "we need to double that number". Your pioneering is a great help to the game and the community.

Indeed.

I'll be getting on the modding wagon post-haste!

Link to comment
Share on other sites

Thanks for this. I'll be adding contracts to my own mod soon and I'll make to sure to post here if I learn anything useful.

It would be great if this could be stickied (or maybe moved to the plugin forum and stickied) like the science experiment thread is.

Link to comment
Share on other sites

Thanks for this. I'll be adding contracts to my own mod soon and I'll make to sure to post here if I learn anything useful.

It would be great if this could be stickied (or maybe moved to the plugin forum and stickied) like the science experiment thread is.

Do you plan to ad some new types of contracts?

Link to comment
Share on other sites

I assume we can add new agencies by simply including a config file with the AGENT{} with the name, logo (and I'm guessing the scaled logo is just smaller?), and whatever modifiers you want to add.

It seems to add it according to the log file during loading, though I haven't actually added any actual contracts, so I can't tell for sure if it's working.

Edit: Yep, this works for adding new agents.

Edited by DMagic
Link to comment
Share on other sites

Huzzah! A contract to "Do something" asks you to collect some science from Kerbin orbit. I'll have to do something about that terrible scaled logo..., Edit: or maybe just not use the scaled logo for both types...

RNfWHEp.png

So am I thinking about this right that we'll need a new instance of the Contract class for every contract we make?

Then I guess the idea would be to make the contract parameters as flexible as we need them to be. Alternatively, would it be possible to create a config file with parameters for several contracts, then write some code to load those parameters, create a new instance of our Contract-derived class, and pass those parameters into it? I probably need to spend more time thinking about the Contract.Parameters.

Edited by DMagic
Link to comment
Share on other sites

Yeah, you really want to make the parameters as single purposed as possible. Like when I was designing the rover contract, I had some check in there to make sure that the rover was launched after the contract was accepted (so that the player couldn't just accept a contract to drive on the mun and use a rover that was already sitting there), and about half way through it I decided that would actually apply to many different types of contracts, so I split it off into it's own parameter, as you can see in the screenshot.

Link to comment
Share on other sites

If you want to force it to a specific agent you need:


base.agent = new Contracts.Agents.Agent("DMagic", "logo URL", "logo Scaled URL");

in your Generate code, with the URLs being the location of the logos specified in the Agent config file.

Edit: Use the code in the next post to fetch an existing agent rather than creating a new one.

Edited by DMagic
Link to comment
Share on other sites

I'm making* a new type of contract:

*attempting and failing miserably

https://i.imgur.com/wa3gHeC.png

EDIT: DMagic, shouldnt you use


base.agent = Contracts.Agents.AgentList.Instance.GetAgent("DMagic");

It woudl be nice if it required specific orbit parameters, for example molniya orbit-like. Players are launching exuatorial all the time, except rare cases of when they want to encounter asterid or put satellite on polar orbit.

Also, is it possible to make use of asteroid/stranded kerbal spawning system to make game spawn its own ships?

Edited by kiwiak
Link to comment
Share on other sites

One orbital parameter could be geostationary, if you get vessel.orbit.period and compare it to the orbital period of the celestial body (it's in there somewhere too) and see how close they are versus an allowance of sorts, you can detect a synchronous orbit pretty easily.

My rover missions are done for the most part, if you guys want to help me hunt bugs in the plugin, I started a thread here: http://forum.kerbalspaceprogram.com/threads/87106-0-24-WIP-Extra-Contracts

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