Alright, I know there's already a good guide
to get the campaign working (I know, I've used it myself
), however I designed this to be more of a comprehensive "goto" version, easily copy-pasteable and extensively commented for better understanding. This version aspires to be very easily editable and clear on values, All the while explaining what everything is. This guide assumes you've done the work of creating ships, variants, and possibly weapons for your faction and are wanting them in. However, if this is viewed too similar to Paul's guide, feel free to remove/ignore it. Okay, now that's out of the way. I can get to the meat of the matter.
Alright, first off, you'll need to create a faction file. To do this, you'll need to make a few new folders for the required files. First off, make sure you place this first file in and call it <Faction>.faction (obviously the <Faction> is what you're calling your new guys:
C:\Program Files (x86)\Fractal Softworks\Starfarer\mods\<modname>\data\world\factionsOnce that's done, use the text in the spoiler as a guide for your file. Feel free to copy and paste it into your file, it's designed to help you, after all
<Faction>.faction
Spoiler
{
//Used as a unique identifier of your faction.
id:"<factionid>",
//Used to get the colour of the faction's fleets and station(s). It uses an RGBA (Red, Green, Blue, Alpha[transparency]) system for this
"color":[<red>,<green>,<blue>,<alpha>],
//Used for choosing what the faction will be named in-game.
"displayName":"<Factionname>",
//Gives your faction's ships a prefix on their names.
"shipNamePrefix":"<prefix>",
//For the ship names themselves, these are used for getting the lists of shipnames to draw from. In this case it's using the vanilla Roman and Greek sets. Vanilla name sources are NORSE, BEASTS, ROMAN, CELTIC, EGYPTIAN, AZTEC, JAPANESE and GREEK,
"shipNameSources":{
"ROMAN":2,
"GREEK":1,
},
//Gives the faction a description in the game Codex
"description":"<description>",
//Chooses the pool of names to give the fleet commanders. Choices are old english, modern and fringe.
"names":{
"old english":1,
},
//Used for choosing portraits for the fleet commanders. For example, these use Hegemony portraits
"portraits":{
"standard_male":[
"graphics/portraits/portrait_hegemony01.png",
"graphics/portraits/portrait_hegemony02.png",
"graphics/portraits/portrait_hegemony05.png",
"graphics/portraits/portrait_mercenary01.png",
"graphics/portraits/portrait_mercenary03.png",
"graphics/portraits/portrait13.png",
"graphics/portraits/portrait15.png",
],
"standard_female":[
"graphics/portraits/portrait_hegemony03.png",
"graphics/portraits/portrait_hegemony04.png",
"graphics/portraits/portrait_mercenary02.png",
"graphics/portraits/portrait16.png",
],
},
//This is where you start to put down the fleet types the faction uses. All fleet types are included here
"fleetCompositions":{
"patrol":{
//Name that shows up when the fleet is highlighted/met by the player
"displayName":"<displayname>",
//The maximum fleet points you want your fleet to be. Useful for creating fleets with different variable ships
"maxFleetPoints":<Fleet Point Cap>,
//The amount of supplies the fleet will have, dependent on the size of it
"daysWorthOfSupplies":[<min>, <max>],
//The amount of fuel the fleet will have. Again, dependent on the size of it
"lyWorthOfFuel":[<min>, <max>],
//How much extra crew the fleet will have. Possibly if you're wanting a fleet that has a low chance of being reduced due to short manpower after a tough fight
"extraCrewPercent":[<min>, <max>],
//How many marines you want present in the fleet
"marinesPercent":[<min>, <max>],
"ships":{
//Adds the different ship variant types in. Which you can have minimum and maximum values if you're wanting variety in your fleet
"<variantid>":[<min>, <max>],
//Wings are added in an identical way. This is merely added to avoid confusion
"<wingid":[<min>, <max>],
},
},
"raid":{
"displayName":"<displayname>",
"maxFleetPoints":<Fleet Point Cap>,
"daysWorthOfSupplies":[<min>, <max>],
"lyWorthOfFuel":[<min>, <max>],
"extraCrewPercent":[<min>, <max>],
"marinesPercent":[<min>, <max>],
"ships":{
"<variantid>":[<min>, <max>],
"<wingid":[<min>, <max>],
},
},
"attack":{
"displayName":"<displayname>",
"maxFleetPoints":<Fleet Point Cap>,
"daysWorthOfSupplies":[<min>, <max>],
"lyWorthOfFuel":[<min>, <max>],
"extraCrewPercent":[<min>, <max>],
"marinesPercent":[<min>, <max>],
"ships":{
"<variantid>":[<min>, <max>],
"<wingid":[<min>, <max>],
},
},
"systemDefense":{
"displayName":"<displayname>",
"maxFleetPoints":<Fleet Point Cap>,
"daysWorthOfSupplies":[<min>, <max>],
"lyWorthOfFuel":[<min>, <max>],
"extraCrewPercent":[<min>, <max>],
"marinesPercent":[<min>, <max>],
"ships":{
"<variantid>":[<min>, <max>],
"<wingid":[<min>, <max>],
},
},
"supplyConvoy":{
"displayName":"<displayname>",
"maxFleetPoints":<Fleet Point Cap>,
"daysWorthOfSupplies":[<min>, <max>],
"lyWorthOfFuel":[<min>, <max>],
"extraCrewPercent":[<min>, <max>],
"marinesPercent":[<min>, <max>],
"ships":{
"<variantid>":[<min>, <max>],
"<wingid":[<min>, <max>],
},
},
"fuelConvoy":{
"displayName":"<displayname>",
"maxFleetPoints":<Fleet Point Cap>,
"daysWorthOfSupplies":[<min>, <max>],
"lyWorthOfFuel":[<min>, <max>],
"extraCrewPercent":[<min>, <max>],
"marinesPercent":[<min>, <max>],
"ships":{
"<variantid>":[<min>, <max>],
"<wingid":[<min>, <max>],
},
},
},
"personnelConvoy":{
"displayName":"<displayname>",
"maxFleetPoints":<Fleet Point Cap>,
"daysWorthOfSupplies":[<min>, <max>],
"lyWorthOfFuel":[<min>, <max>],
"extraCrewPercent":[<min>, <max>],
"marinesPercent":[<min>, <max>],
"ships":{
"<variantid>":[<min>, <max>],
"<wingid":[<min>, <max>],
},
},
},
//Adds various traits to the fleet commanders. With different ones depending on the rank
"traits":{
"admiral":{
},
"captain":{
"cowardly":1,
"cautious":1,
"steady":1,
"aggressive":1,
"suicidal":1,
"fearless":1,
},
},
//Adds different dialogue, depending on the faction's attitude towards you, and of their situation compared to yours
"dialogue":{
"greetingFriendly":"<friendly chat>",
"greetingNeutral":"<indifference>",
"greetingHostileAggressive":"<anger>",
"greetingHostileTimid":"<pants crapping>",
}
},
Right, now that that's done, you'll need to get some .csv files in to tell the game where to find your faction stuff. First you will need a faction.csv in that same factions folder where your .faction file is. Using a spreadsheet software, like OpenOffice for example, make sure your csv looks like this
While you're in about the folder area, go up one level to
C:\Program Files (x86)\Fractal Softworks\Starfarer\mods\<modname>\data\worldand create another csv file called generators.csv this time. For this .csv, make sure it looks like
Again, for both of those, just replace the <Faction> bits with what's relevant for your faction. Now, with that done, we can start on the actual campaign stuff. The real juicy part of the campaign implementation. These next files should be placed in
C:\Program Files (x86)\Fractal Softworks\Starfarer\mods\<modname>\data\scripts\world
<Faction>Gen.java
Spoiler
//This section details the folder in which this will be. If you have a Corvus folder in world, then it'd be package.data.scripts.world.corvus for example
package data.scripts.world;
//Importing required libraries and such. Be sure to have all the required imports
import java.awt.Color;
import java.util.List;
import com.fs.starfarer.api.campaign.CampaignFleetAPI;
import com.fs.starfarer.api.campaign.CargoAPI;
import com.fs.starfarer.api.campaign.FactionAPI;
import com.fs.starfarer.api.campaign.FleetAssignment;
import com.fs.starfarer.api.campaign.SectorAPI;
import com.fs.starfarer.api.campaign.SectorEntityToken;
import com.fs.starfarer.api.campaign.SectorGeneratorPlugin;
import com.fs.starfarer.api.campaign.StarSystemAPI;
import com.fs.starfarer.api.campaign.CargoAPI.CrewXPLevel;
import com.fs.starfarer.api.fleet.FleetMemberType;
@SuppressWarnings("unchecked")
public class <Faction>Gen implements SectorGeneratorPlugin {
public void generate(SectorAPI sector) {
//These lines are getting data from vanilla Starfarer's Corvus System. Critical for locations and such
//Getting the system itself
StarSystemAPI system = sector.getStarSystem("Corvus");
//In this one, it's getting the abandoned station orbiting Corvus I. This line can be done with Stars, planets or stations. Basically any fixed object in a star system it will get by name in quotations
SectorEntityToken stock = system.getEntityByName("Abandoned Storage Facility");
//Creating a token in the system. Doesn't do much except act as a reference point for getting the planet placement right for any added
SectorEntityToken <token> = system.createToken(0, 0);
//Creates a new planet. <planet type> can be gas_giant, ice_giant, lava, frozen, barren, toxic, jungle, terran, desert, arid, or cryovolcanic. <angle> refers to the planet's bearing dependant on <token>. 0 would be to the “east”, with increasing values going anti-clockwise
SectorEntityToken <newPlanet> = system.addPlanet(<token>,"<Planet Name>", "<planet type>", <angle>, <planet size>, <distance from <token>>, <orbit speed>);
//If you so desire, you can even make an asteroid belt for yourself. <orbit-parent> refers to the entity that the belt orbits
system.addAsteroidBelt (<orbit-parent>, <number of asteroids>, <orbit radius>, <belt width>, <min orbit days>, <max orbit days>);
//This is where you create the station for your faction, or just another station for an existing faction, if you so please.
SectorEntityToken <station> = system.addOrbitalStation(<orbit-parent>, <angle>, <distance>, <orbit-period>, "<Station Name>", "<factionid>");
//Creates a spawn point for the faction's convoys. The combat/reconnaissance fleets will be dealt with in the next line
system.addSpawnPoint (new <Faction>ConvoySpawnPoint(sector, system, <daysinterval>, <maxfleets>, <spawn location>, <destination>));
//Creates a spawn point for the factions combat/reconnaissance fleets. Just a repetition of before, but still important, as the values will likely be different.
<Faction>SpawnPoint <Faction>Spawn = new <Faction>SpawnPoint(sector, system, <daysinterval>, <maxfleets>, <spawn location>);
//Actually adding the combat/reconnaissance spawn
system.addSpawnPoint(<Faction>Spawn);
//This for example, creates 4 fleets from the <Faction>Spawn.java specs. More on that when the time comes
for (int i = 0; i < 4; i ++) <Faction>Spawn.spawnFleet();
//Getting the cargo of the station. Vital for adding more cargo and ships to the station
CargoAPI <Faction>cargo = <station>.getCargo();
//Adding weapons to the station's initial inventory.
<station>cargo.addWeapons("<weaponid>", <quantity>);
//Adding misc. cargo to the station's inventory. In this instance it is regular crew and supplies
<station>cargo.addCrew(CrewXPLevel.REGULAR, <quantity>);
<station>cargo.addSupplies(<quantity>);
//Adding a ship to the station's inventory. Note that you'll have to repeat lines if you're wanting multiple of the same hull type
<station>cargo.addMothballedShip(FleetMemberType.SHIP, "<shipid>_Hull", null);
//Fighter wings are slightly different, but it's the same principle
<station>cargo.addMothballedShip(FleetMemberType.FIGHTER_WING, "<wingid>", null);
FactionAPI <factionid> = sector.getFaction("<factionid>");
<factionid>.setRelationship("<angryfaction>", -1);
<factionid>.setRelationship("<indifferent>", 0);
<factionid>.setRelationship("<bestfriends>", 1);
}
}
<FactionConvoySpawnPoint>.java
Spoiler
//Same as in <Faction>Gen, make sure this is the same as the hierarchy of your folders
package data.scripts.world;
//And yet again with this, make sure you have the imports correct
import java.util.List;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.Script;
import com.fs.starfarer.api.campaign.CampaignFleetAPI;
import com.fs.starfarer.api.campaign.CargoAPI;
import com.fs.starfarer.api.campaign.FleetAssignment;
import com.fs.starfarer.api.campaign.LocationAPI;
import com.fs.starfarer.api.campaign.SectorAPI;
import com.fs.starfarer.api.campaign.SectorEntityToken;
import com.fs.starfarer.api.fleet.FleetMemberType;
import com.fs.starfarer.api.campaign.CargoAPI.CrewXPLevel;
@SuppressWarnings("unchecked")
public class <faction>ConvoySpawnPoint extends BaseSpawnPoint {
//All of this is required for the script to work. So just copy this in
private final SectorEntityToken convoyDestination;
public <Faction>ConvoySpawnPoint(SectorAPI sector, LocationAPI location,
float daysInterval, int maxFleets, SectorEntityToken anchor,
SectorEntityToken convoyDestination) {
super(sector, location, daysInterval, maxFleets, anchor);
this.convoyDestination = convoyDestination;
}
//This line is for if you're wanting incremental convoys to add more stuff. For example if you want to simulate an arms race, or just have a bit of pacing with the way things are going
private static int convoyNumber = 0;
@Override
protected CampaignFleetAPI spawnFleet() {
String type = null;
//For creating different kinds of convoy, similar to the Hegemony ones, with different resources depending on convoy. Not required, but still worth knowing about
float r = (float) Math.random();
if (r > .6f) {
type = "fuelConvoy";
} else if (r > 0.3f) {
type = "personnelConvoy";
} else {
type = "supplyConvoy";
}
//Getting the location for spawning the supply fleets. Corvus is on a system of -15000 to 15000 for the grid size.
float angle = (float) ((float) Math.random() * Math.PI * 2f);
float x = (float) (Math.cos(angle) * <xcoord>f);
float y = (float) (Math.sin(angle) * <ycoord>f);
//Creates the convoy
CampaignFleetAPI fleet = getSector().createFleet("<factionid>", type);
getLocation().spawnFleet(getAnchor(), x, y, fleet);
//Changes the delivered goods depending on the convoy type
if (type.equals("supplyConvoy")) {
//In the case of a Supply convoy, delivers 5 random weapons from the list to the station
CargoAPI cargo = fleet.getCargo();
addRandomWeapons(cargo, 5);
}
else if (type.equals("personnelConvoy")) {
//However, if it's a personnel convoy, then the station will receive 25 elite experience crew, and 100 veteran experience crew
CargoAPI cargo = fleet.getCargo();
cargo.addCrew(CrewXPLevel.ELITE, 25);
cargo.addCrew(CrewXPLevel.VETERAN, 200);
}
//Line used for determining what random ships from the list of defined ships are actually added to the station on the delivery run
addRandomShips(fleet.getCargo(), (int) (Math.random() * 4f));
//Message for when the supply convoy has entered the system.
Script script = null;
if (type.equals("supplyConvoy")) {
script = createArrivedScript();
Global.getSectorAPI().addMessage("<Enter Fleet Entered System Message>");
}
//Tells the fleet to go to the station, then afterwards return to the point and despawn. To simulate delivering to the station, then moving to another system
fleet.addAssignment(FleetAssignment.DELIVER_RESOURCES, convoyDestination, 1000, script);
fleet.addAssignment(FleetAssignment.GO_TO_LOCATION_AND_DESPAWN, getAnchor(), 1000);
return fleet;
}
//Message for when the supply convoy has successfully delivered its cargo
private Script createArrivedScript() {
return new Script() {
public void run() {
Global.getSectorAPI().addMessage("<Enter Delivery Message>");
}
};
}
//Determining random weapons to add to the station from the list at the end
private void addRandomWeapons(CargoAPI cargo, int count) {
List weaponIds = getSector().getAllWeaponIds();
for (int i = 0; i < count; i++) {
String weapon = (String) weapons[(int) (weapons.length * Math.random())];
int quantity = (int)(Math.random() * 4f + 2f);
cargo.addWeapons(weapon, quantity);
}
}
//Determining random ships to add to the station from the list at the end. Including fighter wings
private void addRandomShips(CargoAPI cargo, int count) {
List weaponIds = getSector().getAllWeaponIds();
for (int i = 0; i < count; i++) {
if ((float) Math.random() > 0.4f) {
String wing = (String) wings[(int) (wings.length * Math.random())];
cargo.addMothballedShip(FleetMemberType.FIGHTER_WING, wing, null);
} else {
String ship = (String) ships[(int) (ships.length * Math.random())];
cargo.addMothballedShip(FleetMemberType.SHIP, ship, null);
}
}
}
//These three blocks are for defining the different types of ships, fighter wings and weapons the station stocks. No repetition of types is needed for this part.
private static String [] ships = {
"<shipid>_Hull",
};
private static String [] wings = {
"<wingid>",
};
private static String [] weapons = {
"<weaponid>",
};
}
<Faction>SpawnPoint.java
Spoiler
//Yet again, have this as the heirarchy of the folders.
package data.scripts.world;
//And another yet again, have the imports correct
import com.fs.starfarer.api.campaign.CampaignFleetAPI;
import com.fs.starfarer.api.campaign.FleetAssignment;
import com.fs.starfarer.api.campaign.LocationAPI;
import com.fs.starfarer.api.campaign.SectorAPI;
import com.fs.starfarer.api.campaign.SectorEntityToken;
import data.scripts.world.BaseSpawnPoint;
public class <Faction>SpawnPoint extends BaseSpawnPoint {
public <Faction>SpawnPoint(SectorAPI sector, LocationAPI location,
float daysInterval, int maxFleets, SectorEntityToken anchor) {
super(sector, location, daysInterval, maxFleets, anchor);
}
@Override
protected CampaignFleetAPI spawnFleet() {
//if ((float) Math.random() < 0.5f) return null;
//Like for the convoys, this is for determining the fleet type on a random basis.
String type = null;
float r = (float) Math.random();
if (r > .6f) {
type = "patrol";
} else if (r > 0.25f) {
type = "raid";
} else if (r > 0.10f) {
type = "attack";
} else {
type = "systemDefense";
}
//
CampaignFleetAPI fleet = getSector().createFleet("<factionid>", type);
getLocation().spawnFleet(getAnchor(), 0, 0, fleet);
fleet.setPreferredResupplyLocation(getAnchor());
//Giving the fleets orders depending just on what type of fleet it is. So the patrol, raid and attack fleets will go out and roam the Corvus system The systemDefense fleet, however, will stick by the station to safeguard it
if (type.equals("patrol") || type.equals("raid") || type.equals(“attack”)) {
fleet.addAssignment(FleetAssignment.RAID_SYSTEM, null, 10);
fleet.addAssignment(FleetAssignment.GO_TO_LOCATION_AND_DESPAWN, getAnchor(), 1000);
} else {
if ((float) Math.random() > 0.8f) {
fleet.addAssignment(FleetAssignment.PATROL_SYSTEM, null, 30);
fleet.addAssignment(FleetAssignment.GO_TO_LOCATION_AND_DESPAWN, getAnchor(), 1000);
} else {
fleet.addAssignment(FleetAssignment.DEFEND_LOCATION, getAnchor(), 20);
fleet.addAssignment(FleetAssignment.GO_TO_LOCATION_AND_DESPAWN, getAnchor(), 1000);
}
}
return fleet;
}
}
I hope this tutorial helps you get a faction up and running in the campaign. If you have any further questions, feel free to post 'em here
On a related note, any mistakes that need fixed in the tutorial (chances are I have made some), please tell me what they are. I want this to be a useful resource for people, not an error-laden kludgefest. Any other feedback is also greatly appreciated