Adding New Roaming Job Objectives - JBurlison/Pandaros.API GitHub Wiki

Adding new objectives is pretty straight forward. We will use the miner as an example for this tutorial.

Step 1

For the first step we need to add the objective block. For this, we are using the mining machine. for our farmer example, this could be a chicken coop, cow, goat for example.

    public class MinerBlock : CSType
    {
        public override string name { get; set; } = GameLoader.NAMESPACE + ".Miner";
        public override string icon { get; set; } = GameLoader.ICON_PATH + "MiningMachine.png";
        public override bool? isPlaceable { get; set; } = true;
        public override string onPlaceAudio { get; set; } = "stonePlace";
        public override string onRemoveAudio { get; set; } = "stoneDelete";
        public override bool? isSolid { get; set; } = true;
        public override string sideall { get; set; } = "SELF";
        public override string mesh { get; set; } = GameLoader.MESH_PATH + "MiningMachine.obj";
        public override List<string> categories { get; set; } = new List<string>()
        {
            "machine"
        };
    }

Step 2

For step 2 we add the texture mapping.

    public class MinerTexture : CSTextureMapping
    {
        public override string name => GameLoader.NAMESPACE + ".Miner";
        public override string albedo => GameLoader.BLOCKS_ALBEDO_PATH + "MiningMachine.png";
    }

Step 3

Step 3 we add the recipe. We add it as optional as its locked behind research. We set the job that crafts it to the advanced crafter settlers has.

    public class MinerRecipe : ICSRecipe
    {
        public List<RecipeItem> requires => new List<RecipeItem>()
        {
            new RecipeItem(ColonyBuiltIn.ItemTypes.IRONRIVET.Name, 6),
            new RecipeItem(ColonyBuiltIn.ItemTypes.IRONWROUGHT.Name, 2),
            new RecipeItem(ColonyBuiltIn.ItemTypes.COPPERPARTS.Name, 6),
            new RecipeItem(ColonyBuiltIn.ItemTypes.COPPERNAILS.Name, 6),
            new RecipeItem(ColonyBuiltIn.ItemTypes.COPPERTOOLS.Name, 1),
            new RecipeItem(ColonyBuiltIn.ItemTypes.PLANKS.Name, 4),
            new RecipeItem(ColonyBuiltIn.ItemTypes.BRONZEPICKAXE.Name, 2)
        };

        public List<RecipeItem> results => new List<RecipeItem>()
        {
            new RecipeItem(GameLoader.NAMESPACE + ".Miner")
        };

        public CraftPriority defaultPriority => CraftPriority.Medium;

        public bool isOptional => true;

        public int defaultLimit => 5;

        public string Job => AdvancedCrafterRegister.JOB_NAME;

        public string name => GameLoader.NAMESPACE + ".Miner";
    }

Step 4

Next is registering the block as a roaming job objective. We will go over action callbacks in the next step. Work time is how often the DoWork method is called and the item index is the mining machine we declared in step 1. ENSURE YOUR ROAMING JOB HAS THE ObjectiveCategory in its configuration.

    public class MinerRegister : IRoamingJobObjective
    {
        public string name => GameLoader.NAMESPACE + ".Miner";
        public float WorkTime => 4;
        public ItemId ItemIndex => ItemId.GetItemId(GameLoader.NAMESPACE + ".Miner");
        public Dictionary<string, IRoamingJobObjectiveAction> ActionCallbacks { get; } = new Dictionary<string, IRoamingJobObjectiveAction>()
        {
            { MachineConstants.REFUEL, new RefuelMachineAction() },
            { MachineConstants.REPAIR, new RepairMiner() },
            { MachineConstants.RELOAD, new ReloadMiner() }
        };

        public string ObjectiveCategory => MachineConstants.MECHANICAL;

        public void DoWork(Colony colony, RoamingJobState machineState)
        {
            if ((!colony.OwnerIsOnline() && SettlersConfiguration.OfflineColonies) || colony.OwnerIsOnline())
                if (machineState.GetActionEnergy(MachineConstants.REPAIR) > 0 &&
                    machineState.GetActionEnergy(MachineConstants.REFUEL) > 0 &&
                    machineState.NextTimeForWork < Time.SecondsSinceStartDouble)
                {
                    machineState.SubtractFromActionEnergy(MachineConstants.REPAIR, 0.02f);
                    machineState.SubtractFromActionEnergy(MachineConstants.REFUEL, 0.05f);

                    if (World.TryGetTypeAt(machineState.Position.Add(0, -1, 0), out ItemTypes.ItemType itemBelow) &&
                        itemBelow.CustomDataNode != null &&
                        itemBelow.CustomDataNode.TryGetAs("minerIsMineable", out bool minable) &&
                        minable)
                    {
                        var itemList = itemBelow.OnRemoveItems;

                        if (itemList != null && itemList.Count > 0)
                        {
                            var mineTime = itemBelow.CustomDataNode.GetAsOrDefault("minerMiningTime", machineState.RoamingJobSettings.WorkTime);
                            machineState.NextTimeForWork = mineTime + Time.SecondsSinceStartDouble;

                            for (var i = 0; i < itemList.Count; i++)
                                if (Random.NextDouble() <= itemList[i].chance)
                                    colony.Stockpile.Add(itemList[i].item);

                            AudioManager.SendAudio(machineState.Position.Vector, GameLoader.NAMESPACE + ".MiningMachineAudio");
                            Indicator.SendIconIndicatorNear(machineState.Position.Add(0, 1, 0).Vector, new IndicatorState(mineTime, itemList.FirstOrDefault().item.Type));
                        }
                        else
                        {
                            machineState.NextTimeForWork = machineState.RoamingJobSettings.WorkTime + Time.SecondsSinceStartDouble;
                            Indicator.SendIconIndicatorNear(machineState.Position.Add(0, 1, 0).Vector, new IndicatorState(machineState.RoamingJobSettings.WorkTime, ColonyBuiltIn.ItemTypes.ERRORIDLE.Name));
                        }
                    }
                    else
                    {
                        machineState.NextTimeForWork = machineState.RoamingJobSettings.WorkTime + Time.SecondsSinceStartDouble;
                        Indicator.SendIconIndicatorNear(machineState.Position.Add(0, 1, 0).Vector, new IndicatorState(machineState.RoamingJobSettings.WorkTime, ColonyBuiltIn.ItemTypes.ERRORIDLE.Name));
                    }

                }
        }
    }

Step 5

Job Actions itself. For this example I am using repair. In reality a objective can have as many actions as you define. For a cow for example, you may have cleaning the cow and milking the cow. For miner, there is repair, reload and fuel. The action energy is consumed during the DoWork method in step 4. In the objective actions we are restoring that energy. the return value is the ItemId of the icon you wish to display above your roaming jobs head.

    public class RepairMiner : IRoamingJobObjectiveAction
    {
        public string name => MachineConstants.REPAIR;

        public float TimeToPreformAction => 10;

        public string AudioKey => GameLoader.NAMESPACE + ".HammerAudio";

        public ItemId ObjectiveLoadEmptyIcon => ItemId.GetItemId(GameLoader.NAMESPACE + ".Repairing");

        public ItemId PreformAction(Colony colony, RoamingJobState state)
        {
            var retval = ItemId.GetItemId(GameLoader.NAMESPACE + ".Repairing");

            if (!colony.OwnerIsOnline() && SettlersConfiguration.OfflineColonies || colony.OwnerIsOnline())
            {
                if (state.GetActionEnergy(MachineConstants.REPAIR) < .75f)
                {
                    var repaired = false;
                    var requiredForFix = new List<InventoryItem>();
                    var stockpile = colony.Stockpile;

                    requiredForFix.Add(new InventoryItem(ColonyBuiltIn.ItemTypes.PLANKS.Name, 1));
                    requiredForFix.Add(new InventoryItem(ColonyBuiltIn.ItemTypes.COPPERNAILS.Name, 1));

                    if (state.GetActionEnergy(MachineConstants.REPAIR) < .10f)
                    {
                        requiredForFix.Add(new InventoryItem(ColonyBuiltIn.ItemTypes.IRONWROUGHT.Name, 1));
                        requiredForFix.Add(new InventoryItem(ColonyBuiltIn.ItemTypes.COPPERPARTS.Name, 4));
                        requiredForFix.Add(new InventoryItem(ColonyBuiltIn.ItemTypes.IRONRIVET.Name, 1));
                        requiredForFix.Add(new InventoryItem(ColonyBuiltIn.ItemTypes.COPPERTOOLS.Name, 1));
                    }
                    else if (state.GetActionEnergy(MachineConstants.REPAIR) < .30f)
                    {
                        requiredForFix.Add(new InventoryItem(ColonyBuiltIn.ItemTypes.IRONWROUGHT.Name, 1));
                        requiredForFix.Add(new InventoryItem(ColonyBuiltIn.ItemTypes.COPPERPARTS.Name, 2));
                        requiredForFix.Add(new InventoryItem(ColonyBuiltIn.ItemTypes.COPPERTOOLS.Name, 1));
                    }
                    else if (state.GetActionEnergy(MachineConstants.REPAIR) < .50f)
                    {
                        requiredForFix.Add(new InventoryItem(ColonyBuiltIn.ItemTypes.COPPERPARTS.Name, 1));
                        requiredForFix.Add(new InventoryItem(ColonyBuiltIn.ItemTypes.COPPERTOOLS.Name, 1));
                    }

                    if (stockpile.Contains(requiredForFix))
                    {
                        stockpile.TryRemove(requiredForFix);
                        repaired = true;
                    }
                    else
                    {
                        foreach (var item in requiredForFix)
                            if (!stockpile.Contains(item))
                            {
                                retval = ItemId.GetItemId(item.Type);
                                break;
                            }
                    }

                    if (repaired)
                        state.ResetActionToMaxLoad(MachineConstants.REPAIR);
                }
            }

            return retval;
        }
    }

Full Example

https://github.com/JBurlison/Pandaros.Settlers/blob/master/Pandaros.Settlers/Pandaros.Settlers/Items/Machines/Miner.cs

⚠️ **GitHub.com Fallback** ⚠️