tAPI [Tutorial] Generics and Custom JSON Properties

MiraiMai

Terrarian
%-_- MiraiMai's Tutorial: Generics and Custom JSON Properties %-_-
This is a tutorial for using the "code" property in your mods, and how to simplify mod making and reduce the amount of code you need. This tutorial is more advanced as it uses code, however it also makes a large amount of the advanced features people want to add very simple. This post is split into two major parts. The first half is the actual tutorial, explaining how generic code works and how you should use it. The second part is a library of generic code you could add to your mod, made by myself or others in the community.​

2015/01/24
- Initial release
- Note: Minion's item code will change once R15 comes out.​

This is an intermediate tutorial, and requires programming knowledge. You have been warned.

- - - The Tutorial - - -
Introduction
Let's start from the beginning. Normally, when making content for tAPI, you would need 2 or 3 files.
  • The Sprite file
  • The JSON file
  • The CS code file (optional)
Most people can easily do the first two (the first may be hideous, but it can be done) but the third is more complicated. More than that though, a lot of the time, you will have multiple items using identical CS files, aside from the name. A good example of this is boomerangs, which all require code to limit how many times you can throw the item.

Not only is this code repetitive, but it's usually public knowledge. Almost everyone knows or can find the code for a boomerang. Almost everyone knows or can find the code for adding dust to a weapon. A lot of people request that features like this get added to tAPI, but seeing as how simple it is to do with code, there's no point.

The purpose of this tutorial is to make some of the more advanced features people want in their content easier to add, and cleaner to add. At the end of this tutorial, you will be able to make new items that inflict debuffs, new boomerangs, or almost anything you want, without needing a new CS file every time.

Starting Out: Generic Files
In order to use generic code, you need to make a few .cs files to store them. I prefer to keep them all in large files based on content type.
  • GenericBuff.cs
  • GenericItem.cs
  • GenericNPC.cs
  • GenericProjectile.cs
Splitting the code up like this allows for having few files that are well sorted.

Each file would need the usual C# code around it, the using statements and namespace. After that, you would just add all your generic code classes into the namespace.

Code:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

using LitJson;
using Terraria;
using TAPI;

namespace ModName.Items {
    // Generic Classes Here
}

Of course, don't forget to replace ModName with your own mod's name.

Basics: GenericBoomerang
For your first generic class, let's go with something simple. Many mods will have boomerangs in them at some point, and there is no point giving every single one it's own code if they do the same thing.

To start with, a boomerang needs a CanUse hook. This is used to prevent throwing a boomerang you've already thrown, or in the case of stacking boomerangs, from throwing more than you have. This hook can be found in multiple places, so I won't go explaining every detail, but this is all that is needed for a boomerang.

First we need to make the GenericItem.cs class, and add the GenericBoomerang code.

Code:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

using LitJson;
using Terraria;
using TAPI;

namespace ModName.Items {
    public class GenericBoomerang : TAPI.ModItem {

        public override bool CanUse(Player player) {
            for(int l = 0; l < Main.projectile.Length; l++) {
                Projectile proj = Main.projectile[l];
                if(proj.active && proj.owner == player.whoAmI && proj.type == item.shoot) {
                    return false;
                }
            }
            return true;
        }

    }
}

Now that we have the code, all we need to do is use it in our boomerang item. This is ridiculously simple, just use the "code" property.

Code:
{
    "displayName": "Simple Boomerang",
    "size": [32,32],
    "maxStack": 1,
    "value": [0,1,0,0],
    "rare": 1,
    "tooltip": ["A simple boomerang!"],
    "useStyle": 1,
    "useAnimation": 7,
    "useTime": 7,
    "damage": 12,
    "knockback": 1,
    "useSound": 1,
    "melee": true,
    "shoot": "SimpleBoomerang",
    "shootSpeed": 8,
    "noUseGraphic": true,
    "noMelee": true,

    "code": "Items.GenericBoomerang"
}

There you have it, a boomerang that uses generic code! I like to keep the "code" separated at the bottom of the JSON file, for reasons you will see in the next section.

Custom JSON Properties: GenericDebuff
So you've made basic piece of generic code, that allows you to do the same thing with multiple items, easily. But what if you want to be able to customize that functionality, such as having your item inflict different debuffs?

This is similarly simple. For inflicting a debuff, you need to call AddBuff during the Dealt hooks. However, you need to be able to choose what debuff and for how long to inflict it. This can be done by adding custom JSON options.

That is a bit of a misnomer, though. You aren't actually adding an option, as much as reading one you assume is there. You can put any custom JSON option into a file without an error, you just need to use code to read it. Thankfully, every JSON loaded by tAPI is kept, so it is simple to get at.

Code:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

using LitJson;
using Terraria;
using TAPI;

namespace ModName.Items {
    public class GenericDebuff : TAPI.ModItem {

        private int buff;
        private int time;

        public override void Initialize() {
            buff = (int) item.def.json["debuff"];
            time = (int) item.def.json["debuffTime"];
        }

        public override void DealtPVP(Player owner, Player p, int hitDir, int dmgDealt, bool crit) {
            p.AddBuff(buff, time, false);
        }

        public override void DealtNPC(Player owner, NPC npc, int hitDir, int dmgDealt, float knockback, bool crit) {
            npc.AddBuff(buff, time, false);
        }

    }
}

As you can see, getting custom options from JSON files is not hard at all. You add variables to store the data, load them in Initialize, the do the usual you would do in more specific code.

Note that it is required to cast to the type you want, to get data from a JsonData class. This one will also crash if the option is missing, which is okay because there's no point having defaults for a debuff. If you needed something optional, you could surround each option's loading line with an if statement checking Has("property").

And again, now that we have the code all done, here's a JSON for an item using this code.

Code:
{
    "displayName": "Poison Stick",
    "size": [32,32],
    "maxStack": 1,
    "value": [0,1,0,0],
    "rare": 1,
    "tooltip": ["A poisonous stick!"],
    "useStyle": 1,
    "useAnimation": 40,
    "useTime": 40,
    "damage": 12,
    "knockback": 1,
    "useSound": 1,
    "melee": true,
    "noUseGraphic": true,
    "noMelee": true,

    "code": "Items.GenericDebuff",
    "debuff": "Poisoned",
    "debuffTime": 300
}

Congratulations! You now have a poisonous stick. Not only that, but you can make flaming sticks, or venomous sticks, or any other debuff stick, and not need a single bit more code. Oh, you can also make non-stick weapons, but what's the fun in that?

This also shows why I like to keep the code separate near the bottom. This allows you to easily see what code you are using, and the options for that code.

Extending Functionality: GenericGlowingDebuff
Now we have a weapon that inflicts debuffs, and you could easily use the same idea to make one that glows, or emits dust. The problem is, what if you want multiple of these things? You can't have multiple "code" properties per JSON, so unfortunately you're limited to making larger generic classes that do multiple things.

One way to make this less tedious is to use inheritance, that way you're still writing the main function only once. GenericDebuff adds a rather major feature, so you can easily extend it multiple times with a few variations. In this, we'll be making GenericGlowingDebuff, which emites light as well as inflicts a debuff.

There are two ways you could do this, either by defining them separately, or by defining the variations within the original GenericDebuff. I'll show both below.

Code:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

using LitJson;
using Terraria;
using TAPI;

namespace ModName.Items {
    public class GenericDebuff : TAPI.ModItem {

        private int buff;
        private int time;

        public override void Initialize() {
            buff = (int) item.def.json["debuff"];
            time = (int) item.def.json["debuffTime"];
        }

        public override void DealtPVP(Player owner, Player p, int hitDir, int dmgDealt, bool crit) {
            p.AddBuff(buff, time, false);
        }

        public override void DealtNPC(Player owner, NPC npc, int hitDir, int dmgDealt, float knockback, bool crit) {
            npc.AddBuff(buff, time, false);
        }

    }

    public class GenericGlowingDebuff : GenericDebuff {

        private float R, G, B;

        public override void Initialize() {
            base.Initialize();
            JsonData glow = item.def.json["glow"];
            R = (float) glow[0];
            G = (float) glow[1];
            B = (float) glow[2];
        }

        public override void UseItemEffects(Player p, Rectangle rect) {
            Lighting.AddLight((int)((p.itemLocation.X + 6f + p.velocity.X) / 16f), (int)((p.itemLocation.Y - 14f) / 16f), R, G, B);
        }

    }
}
Code:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

using LitJson;
using Terraria;
using TAPI;

namespace ModName.Items {
    public class GenericDebuff : TAPI.ModItem {

        public class Glowing : GenericDebuff {

            private float R, G, B;

            public override void Initialize() {
                base.Initialize();
                JsonData glow = item.def.json["glow"];
                R = (float) glow[0];
                G = (float) glow[1];
                B = (float) glow[2];
            }

            public override void UseItemEffects(Player p, Rectangle rect) {
                Lighting.AddLight((int)((p.itemLocation.X + 6f + p.velocity.X) / 16f), (int)((p.itemLocation.Y - 14f) / 16f), R, G, B);
            }

        }

        private int buff;
        private int time;

        public override void Initialize() {
            buff = (int) item.def.json["debuff"];
            time = (int) item.def.json["debuffTime"];
        }

        public override void DealtPVP(Player owner, Player p, int hitDir, int dmgDealt, bool crit) {
            p.AddBuff(buff, time, false);
        }

        public override void DealtNPC(Player owner, NPC npc, int hitDir, int dmgDealt, float knockback, bool crit) {
            npc.AddBuff(buff, time, false);
        }

    }
}

Do not forget to call the base methods if they were already used in the original. Also, note that in order to access a custom array property, you must first get the JsonData and use that as an array. You cannot cast it to a float[].

Both variations work identically, the only change is how you use them in the JSON. I find the first way looks cleaner in the code, but the second way is more logical for using in JSONs.

Code:
{
    "displayName": "Fire Stick",
    "size": [32,32],
    "maxStack": 1,
    "value": [0,1,0,0],
    "rare": 1,
    "tooltip": ["A firey stick!"],
    "useStyle": 1,
    "useAnimation": 40,
    "useTime": 40,
    "damage": 12,
    "knockback": 1,
    "useSound": 1,
    "melee": true,
    "noUseGraphic": true,
    "noMelee": true,

    "code": "Items.GenericGlowingDebuff",
    "debuff": "On Fire!",
    "debuffTime": 300,
    "glow": [0.9, 0.8, 0.7]
}
Code:
{
    "displayName": "Fire Stick",
    "size": [32,32],
    "maxStack": 1,
    "value": [0,1,0,0],
    "rare": 1,
    "tooltip": ["A firey stick!"],
    "useStyle": 1,
    "useAnimation": 40,
    "useTime": 40,
    "damage": 12,
    "knockback": 1,
    "useSound": 1,
    "melee": true,
    "noUseGraphic": true,
    "noMelee": true,

    "code": "Items.GenericDebuff.Glowing",
    "debuff": "On Fire!",
    "debuffTime": 300,
    "glow": [0.9, 0.8, 0.7]
}

And with that, you've learned basically all it takes to use generic code properly in your mods. Hopefully you found this useful, and it helps clean up your code.


- - - The Generics Library - - -
None yet. :dryadsad:

Common
Code:
    public class GenericDusty : TAPI.ModItem {
        
        private int dust;
        private int dustAlpha = 0;
        private float dustScale = 1f;
        
        public override void Initialize() {
            dust = (int) item.def.json["dust"];
            if (item.def.json.Has("dustAlpha")) {
                dustAlpha = (int) item.def.json["dustAlpha"];
            }
            if (item.def.json.Has("dustScale")) {
                dustScale = (float) item.def.json["dustScale"];
            }
        }
        
        public override void UseItemEffects(Player p, Rectangle rect) {
            float speedX = p.velocity.X * (float)Main.rand.Next(5) * 0.2f;
            float speedY = p.velocity.Y * (float)Main.rand.Next(5) * 0.2f;
            int i = Dust.NewDust(new Vector2(rect.X, rect.Y),rect.Width,rect.Height,dust,speedX,speedY,dustAlpha,default(Color),dustScale);
            Main.dust[i].noGravity = true;
        }
        
    }
    
    public class GenericGlowing : TAPI.ModItem {
        
        private float R, G, B;
        
        public override void Initialize() {
            JsonData glow = item.def.json["glow"];
            R = (float) glow[0];
            G = (float) glow[1];
            B = (float) glow[2];
        }
        
        public override void UseItemEffects(Player p, Rectangle rect) {
            Lighting.AddLight((int)((p.itemLocation.X + 6f + p.velocity.X) / 16f), (int)((p.itemLocation.Y - 14f) / 16f), R, G, B);
        }
        
    }

GenericBoomerang
Code:
    public class GenericBoomerang : TAPI.ModItem {
        
        public override bool CanUse(Player player) {
            for(int l = 0; l < Main.projectile.Length; l++) {
                Projectile proj = Main.projectile[l];
                if(proj.active && proj.owner == player.whoAmI && proj.type == item.shoot) {
                    return false;
                }
            }
            return true;
        }
        
    }
    
    public class GenericStackableBoomerang : ModItem {
       
        public override bool CanUse(Player player) {
            int count = 0;
            for(int l = 0; l < Main.projectile.Length; l++) {
                Projectile proj = Main.projectile[l];
                if(proj.active && proj.owner == player.whoAmI && proj.type == item.shoot) {
                    count++;
                    if (count == item.stack) {
                        return false;
                    }
                }
            }
            return true;
        }
       
    }
Code:
    public class GenericBoomerang : TAPI.ModItem {
        
        public class Stackable : ModItem {
           
            public override bool CanUse(Player player) {
                int count = 0;
                for(int l = 0; l < Main.projectile.Length; l++) {
                    Projectile proj = Main.projectile[l];
                    if(proj.active && proj.owner == player.whoAmI && proj.type == item.shoot) {
                        count++;
                        if (count == item.stack) {
                            return false;
                        }
                    }
                }
                return true;
            }
           
        }
        
        public override bool CanUse(Player player) {
            for(int l = 0; l < Main.projectile.Length; l++) {
                Projectile proj = Main.projectile[l];
                if(proj.active && proj.owner == player.whoAmI && proj.type == item.shoot) {
                    return false;
                }
            }
            return true;
        }
        
    }

Debuff
Code:
    public class GenericDebuff : TAPI.ModItem {
        
        private int buff;
        private int time;
        
        public override void Initialize() {
            buff = (int) item.def.json["debuff"];
            time = (int) item.def.json["debuffTime"];
        }
        
        public override void DealtPVP(Player owner, Player p, int hitDir, int dmgDealt, bool crit) {
            p.AddBuff(buff, time, false);
        }
        
        public override void DealtNPC(Player owner, NPC npc, int hitDir, int dmgDealt, float knockback, bool crit) {
            npc.AddBuff(buff, time, false);
        }
        
    }
    
    public class GenericDustyDebuff : GenericDebuff {
        
        private int dust;
        private int dustAlpha = 0;
        private float dustScale = 1f;
        
        public override void Initialize() {
            base.Initialize();
            dust = (int) item.def.json["dust"];
            if (item.def.json.Has("dustAlpha")) {
                dustAlpha = (int) item.def.json["dustAlpha"];
            }
            if (item.def.json.Has("dustScale")) {
                dustScale = (float) item.def.json["dustScale"];
            }
        }
        
        public override void UseItemEffects(Player p, Rectangle rect) {
            float speedX = p.velocity.X * (float)Main.rand.Next(5) * 0.2f;
            float speedY = p.velocity.Y * (float)Main.rand.Next(5) * 0.2f;
            int i = Dust.NewDust(new Vector2(rect.X, rect.Y),rect.Width,rect.Height,dust,speedX,speedY,dustAlpha,default(Color),dustScale);
            Main.dust[i].noGravity = true;
        }
        
    }
    
    public class GenericGlowingDebuff : GenericDebuff {
        
        private float R, G, B;
        
        public override void Initialize() {
            base.Initialize();
            JsonData glow = item.def.json["glow"];
            R = (float) glow[0];
            G = (float) glow[1];
            B = (float) glow[2];
        }
        
        public override void UseItemEffects(Player p, Rectangle rect) {
            Lighting.AddLight((int)((p.itemLocation.X + 6f + p.velocity.X) / 16f), (int)((p.itemLocation.Y - 14f) / 16f), R, G, B);
        }
        
    }
Code:
    public class GenericDebuff : TAPI.ModItem {
        
        public class Dusty : GenericDebuff {
            
            private int dust;
            private int dustAlpha = 0;
            private float dustScale = 1f;
            
            public override void Initialize() {
                base.Initialize();
                dust = (int) item.def.json["dust"];
                if (item.def.json.Has("dustAlpha")) {
                    dustAlpha = (int) item.def.json["dustAlpha"];
                }
                if (item.def.json.Has("dustScale")) {
                    dustScale = (float) item.def.json["dustScale"];
                }
            }
            
            public override void UseItemEffects(Player p, Rectangle rect) {
                float speedX = p.velocity.X * (float)Main.rand.Next(5) * 0.2f;
                float speedY = p.velocity.Y * (float)Main.rand.Next(5) * 0.2f;
                int i = Dust.NewDust(new Vector2(rect.X, rect.Y),rect.Width,rect.Height,dust,speedX,speedY,dustAlpha,default(Color),dustScale);
                Main.dust[i].noGravity = true;
            }
            
        }
        
        public class Glowing : GenericDebuff {
            
            private float R, G, B;
            
            public override void Initialize() {
                base.Initialize();
                JsonData glow = item.def.json["glow"];
                R = (float) glow[0];
                G = (float) glow[1];
                B = (float) glow[2];
            }
            
            public override void UseItemEffects(Player p, Rectangle rect) {
                Lighting.AddLight((int)((p.itemLocation.X + 6f + p.velocity.X) / 16f), (int)((p.itemLocation.Y - 14f) / 16f), R, G, B);
            }
            
        }
        
        private int buff;
        private int time;
        
        public override void Initialize() {
            buff = (int) item.def.json["debuff"];
            time = (int) item.def.json["debuffTime"];
        }
        
        public override void DealtPVP(Player owner, Player p, int hitDir, int dmgDealt, bool crit) {
            p.AddBuff(buff, time, false);
        }
        
        public override void DealtNPC(Player owner, NPC npc, int hitDir, int dmgDealt, float knockback, bool crit) {
            npc.AddBuff(buff, time, false);
        }
        
    }

None yet. :dryadsad:

Common
Code:
    public class GenericDusty : TAPI.ModProjectile {
        
        private int dust;
        private int dustAlpha = 0;
        private float dustScale = 1f;
        
        public override void Initialize() {
            dust = (int) projectile.def.json["dust"];
            if (projectile.def.json.Has("dustAlpha")) {
                dustAlpha = (int) projectile.def.json["dustAlpha"];
            }
            if (projectile.def.json.Has("dustScale")) {
                dustScale = (float) projectile.def.json["dustScale"];
            }
        }
        
        public override void PostAI() {
            float speedX = projectile.velocity.X * (float)Main.rand.Next(5) * 0.2f;
            float speedY = projectile.velocity.Y * (float)Main.rand.Next(5) * 0.2f;
            int i = Dust.NewDust(projectile.position,projectile.width,projectile.height,dust,speedX,speedY,dustAlpha,default(Color),dustScale);
            Main.dust[i].noGravity = true;
        }
        
    }
    
    public class GenericGlowing : TAPI.ModProjectile {
        
        private float R, G, B;
        
        public override void Initialize() {
            JsonData glow = projectile.def.json["glow"];
            R = (float) glow[0];
            G = (float) glow[1];
            B = (float) glow[2];
        }
        
        public override void PostAI() {
            Vector2 pos = p.Centre;
            Lighting.AddLight((int)(pos.X / 16), (int)(pos.Y / 16), R, G, B);
        }
        
    }

Debuff
Code:
    public class GenericDebuff : TAPI.ModProjectile {
        
        private int buff;
        private int time;
        
        public override void Initialize() {
            buff = (int) projectile.def.json["debuff"];
            time = (int) projectile.def.json["debuffTime"];
        }
        
        public override void DealtPVP(Player p, int hitDir, int dmgDealt, bool crit) {
            p.AddBuff(buff, time, false);
        }
        
        public override void DealtNPC(NPC npc, int hitDir, int dmgDealt, float knockback, bool crit) {
            npc.AddBuff(buff, time, false);
        }
        
    }
    
    public class GenericDustyDebuff : GenericDebuff {
        
        private int dust;
        private int dustAlpha = 0;
        private float dustScale = 1f;
        
        public override void Initialize() {
            base.Initialize();
            dust = (int) projectile.def.json["dust"];
            if (projectile.def.json.Has("dustAlpha")) {
                dustAlpha = (int) projectile.def.json["dustAlpha"];
            }
            if (projectile.def.json.Has("dustScale")) {
                dustScale = (float) projectile.def.json["dustScale"];
            }
        }
        
        public override void PostAI() {
            float speedX = projectile.velocity.X * (float)Main.rand.Next(5) * 0.2f;
            float speedY = projectile.velocity.Y * (float)Main.rand.Next(5) * 0.2f;
            int i = Dust.NewDust(projectile.position,projectile.width,projectile.height,dust,speedX,speedY,dustAlpha,default(Color),dustScale);
            Main.dust[i].noGravity = true;
        }
        
    }
    
    public class GenericGlowingDebuff : GenericDebuff {
        
        private float R, G, B;
        
        public override void Initialize() {
            base.Initialize();
            JsonData glow = projectile.def.json["glow"];
            R = (float) glow[0];
            G = (float) glow[1];
            B = (float) glow[2];
        }
        
        public override void PostAI() {
            Vector2 pos = p.Centre;
            Lighting.AddLight((int)(pos.X / 16), (int)(pos.Y / 16), R, G, B);
        }
        
    }
Code:
    public class GenericDebuff : TAPI.ModProjectile {
        
        public class Dusty : GenericDebuff {
            
            private int dust;
            private int dustAlpha = 0;
            private float dustScale = 1f;
            
            public override void Initialize() {
                base.Initialize();
                dust = (int) projectile.def.json["dust"];
                if (projectile.def.json.Has("dustAlpha")) {
                    dustAlpha = (int) projectile.def.json["dustAlpha"];
                }
                if (projectile.def.json.Has("dustScale")) {
                    dustScale = (float) projectile.def.json["dustScale"];
                }
            }
            
            public override void PostAI() {
                float speedX = projectile.velocity.X * (float)Main.rand.Next(5) * 0.2f;
                float speedY = projectile.velocity.Y * (float)Main.rand.Next(5) * 0.2f;
                int i = Dust.NewDust(projectile.position,projectile.width,projectile.height,dust,speedX,speedY,dustAlpha,default(Color),dustScale);
                Main.dust[i].noGravity = true;
            }
            
        }
        
        public class Glowing : GenericDebuff {
            
            private float R, G, B;
            
            public override void Initialize() {
                base.Initialize();
                JsonData glow = projectile.def.json["glow"];
                R = (float) glow[0];
                G = (float) glow[1];
                B = (float) glow[2];
            }
            
            public override void PostAI() {
                Vector2 pos = p.Centre;
                Lighting.AddLight((int)(pos.X / 16), (int)(pos.Y / 16), R, G, B);
            }
            
        }
        
        private int buff;
        private int time;
        
        public override void Initialize() {
            buff = (int) projectile.def.json["debuff"];
            time = (int) projectile.def.json["debuffTime"];
        }
        
        public override void DealtPVP(Player p, int hitDir, int dmgDealt, bool crit) {
            p.AddBuff(buff, time, false);
        }
        
        public override void DealtNPC(NPC npc, int hitDir, int dmgDealt, float knockback, bool crit) {
            npc.AddBuff(buff, time, false);
        }
        
    }

None yet. :dryadsad:

Minions
This is a collection of generic files for making minions easier. Note that since minions require custom AI, you will need to still make your own class for the minion projectile, but if you inherit from GenericMinion then you will not have to deal with the player/buff. The projectile GenericMinion is also a very good place for putting your own custom functions for AI that all your minions share, such as finding a target.

This set also requires you to add an array of booleans named "minion" to your ModPlayer, which is used throughout the code. Make it as big as you need, and give each minion a unique index in the JSONs. If your ModPlayer class is not named ModPlayer, you will have to rename that in the code.

Code:
public class GenericMinion : TAPI.ModBuff {
        
        private int buffType;
        
        private int minionIndex;
        private int minionType;
        
        public override void Start(Player player, int index) {
            player.buffTime[index] = 22;
            
            buffType = player.buffType[index];
            
            JsonData data = BuffDef.buffs[buffType].json;
            minionIndex = (int) data["minionIndex"];
            minionType = ProjDef.byName[modBase.mod.InternalName + ":" + (string)data["minion"]].type;
        }
        
        public override void MidUpdate(Player player) {
            ModPlayer mp = player.GetSubClass<ModPlayer>();
            
            // Check the minion exists
            for (int l=0; l<1000; l++) {
                if(Main.projectile[l].active && Main.projectile[l].owner == player.whoAmI && Main.projectile[l].type == minionType) {
                    mp.minion[minionIndex] = true;
                    break;
                }
            }
            
            // Update the buff
            int index = Array.IndexOf(player.buffType, buffType);
            if (index >= 0) {
                if(mp.minion[minionIndex]) {
                    player.buffTime[index] = 22;
                } else {
                    player.DelBuff(index);
                }
            }
        }
        
    }

Code:
    public class GenericMinion : ModItem {
        
        public override bool PreShoot(Player p, Vector2 pos, Vector2 speed, int type, int damage, float knockback) {
            // Spawn the projectile at the cursor not at the player
            Projectile.NewProjectile(Main.mouseX + Main.screenPosition.X, Main.mouseY + Main.screenPosition.Y, speed.X, speed.Y, type, damage, knockback, p.whoAmI);
            
            return false;
        }
        
        public override void PreItemCheck(Player player) {
            // Only necessary before R15
            if (API.MyVersion().StartsWith("R15")) {
                return;
            }
            
            // If you can't use the item, no point doing anything
            if (!player.controlUseItem || !player.releaseUseItem || player.itemAnimation != 0 || player.noItems || player.frozen || player.silence || player.statMana < item.mana) {
                return;
            }
            
            // Find all minions
            List<int> minions = new List<int>();
            float minionSlots = 0f;
            for (int l=0; l<1000; l++) {
                if (Main.projectile[l].active && Main.projectile[l].owner == player.whoAmI && Main.projectile[l].minion) {
                    int index;
                    for (index=0; index<minions.Count; index++) {
                        if (Main.projectile[minions[index]].minionSlots > Main.projectile[l].minionSlots) {
                            minions.Insert(index, l);
                            break;
                        }
                    }
                    if (index == minions.Count) {
                        minions.Add(l);
                    }
                    minionSlots += Main.projectile[l].minionSlots;
                }
            }
            
            // Make room for minion
            float requiredSlots = ProjDef.byType[player.inventory[player.selectedItem].shoot].minionSlots;
            float freedSlots = 0f;
            int twin = 388;
            for (int l=0; l<minions.Count; l++) {
                if (minionSlots - freedSlots <= (float)player.maxMinions - requiredSlots) {
                    break;
                }
                int type = Main.projectile[minions[l]].type;
                if (type != twin) {
                    freedSlots += Main.projectile[minions[l]].minionSlots;
                    if (type == 388 && twin == 387) {
                        twin = 388;
                    }
                    if (type == 387 && twin == 388) {
                        twin = 387;
                    }
                    Main.projectile[minions[l]].Kill();
                }
            }
            minions.Clear();
        }
        
    }

Code:
    public class GenericMinion : TAPI.ModProjectile {
        
        private int minionIndex;
        
        public override void Initialize() {
            projectile.netImportant = true;
            
            minionIndex = (int) projectile.def.json["minionIndex"];
        }
        
        public override void PostAI() {
            // Minion can only be active while owner is alive and buff is active
            Player p = Main.player[projectile.owner];
            ModPlayer mp = p.GetSubClass<ModPlayer>();
            if (!p.active) {
                projectile.active = false;
                return;
            }
            if (p.dead) {
                mp.minion[minionIndex] = false;
            }
            if (mp.minion[minionIndex]) {
                projectile.timeLeft = 22;
            }
        }
        
    }


- - - Permissions - - -
All code included in this tutorial is public domain. Feel free to use and abuse it however you want.

Please note that this is also true of any code submitted by the community to be included in the library, so consider that if you wish to share code.​
 
Last edited:
Back
Top Bottom