Adding Custom Zombies - JBurlison/Pandaros.API GitHub Wiki

To add a new type of zombie, there is a 2 step process.

Step 1

The zombie needs to be registered. For this we need to create the monsters settings.

        [ModLoader.ModCallback(ModLoader.EModCallbackType.AfterItemTypesDefined,  GameLoader.NAMESPACE + ".Monsters.Normal.RockThrowerZombie.Register")]
        [ModLoader.ModCallbackDependsOn("pipliz.server.loadnpctypes")]
        [ModLoader.ModCallbackProvidesFor("pipliz.server.registermonstertextures")]
        public static void Register()
        {
            var m = new JSONNode()
                   .SetAs("keyName", Key)
                   .SetAs("printName", "Rock Thrower Zombie")
                   .SetAs("npcType", "monster");

            var ms = new JSONNode()
                    .SetAs("albedo", GameLoader.BLOCKS_NPC_PATH + "FallenRanger.png")
                    .SetAs("normal", GameLoader.BLOCKS_NPC_PATH + "ZombieQueen_normal.png")
                    .SetAs("emissive", GameLoader.BLOCKS_NPC_PATH + "ZombieQueen_emissive.png")
                    .SetAs("initialHealth", 500)
                    .SetAs("movementSpeed", 1.25f)
                    .SetAs("punchCooldownMS", 2000)
                    .SetAs("punchDamage", 50);

            m.SetAs("data", ms);
            _mts = new NPCTypeMonsterSettings(m);
            NPCType.AddSettings(_mts);
        }

Step 2

For step 2, we need to add the new zombie for settlers to find. To do this, implement the IPandaZombie interface and inherit from Zombie.

    [ModLoader.ModManager]
    public class RockThrowerZombie : Zombie, IPandaZombie
    {
        public static string Key = GameLoader.NAMESPACE + ".Monsters.Bosses.RockThrowerZombie";
        private static NPCTypeMonsterSettings _mts;
        private double _cooldown = 2;
        private float _totalHealth = 500;

        public RockThrowerZombie() :
            base(NPCType.GetByKeyNameOrDefault(Key), new Path(), GameLoader.StubColony)
        {
        }

        public RockThrowerZombie(Path path, Colony originalGoal) :
            base(NPCType.GetByKeyNameOrDefault(Key), path, originalGoal)
        {
            TotalHealth = _totalHealth;
            CurrentHealth = _totalHealth;
        }

        // This is required. This is how settlers creates a new instance every time.
        public IPandaZombie GetNewInstance(Path path, Colony p)
        {
            return new RockThrowerZombie(path, p);
        }


        public string name => "RockThrowerZombie";
        public override float TotalHealth => _totalHealth;
        public int MinColonists => 100; // Minimum number of colonists before the zombie will start spawning
        public bool KilledBefore
        {
            get => false;
            set => killedBefore = value;
        }

        public float ZombieHPBonus => 0; // additional HP on top of the base HP
        public float MissChance => 0.05f; // chance to be missed when attacked by guards or the player. in this example its 5%
        public string MosterType => "PandaZombie";  // Used for Loot Table reference
        
        // the types of damage the zombie inflicts. (see the damage matrix on the wiki)
        public Dictionary<DamageType, float> Damage { get; } = new Dictionary<DamageType, float>
        {
            {DamageType.Physical, 50f}
        };

        public DamageType ElementalArmor => DamageType.Physical;

        // FLAT resistance. in this example the zombie will take 15% reduced physical damage.
        public Dictionary<DamageType, float> AdditionalResistance { get; } = new Dictionary<DamageType, float>
        {
            {DamageType.Physical, 0.15f}
        };

        // Logic for the rockthrower to throw rocks
        public override bool Update()
        {
            if (Time.SecondsSinceStartDouble > _cooldown)
            {
                if (Players.FindClosestAlive(Position, out var p, out var dis) &&
                    dis <= 30 &&
                    VoxelPhysics.CanSee(Position, p.Position))
                {
                    Indicator.SendIconIndicatorNear(new Vector3Int(Position), ID,
                                                    new IndicatorState(2, ColonyBuiltIn.ItemTypes.SLINGBULLET.Name));

                    AudioManager.SendAudio(Position, "sling");
                    p.Health -= Damage.Sum(kvp => kvp.Key.CalcDamage(DamageType.Physical, kvp.Value));
                    AudioManager.SendAudio(p.Position, "fleshHit");
                    _cooldown = Time.SecondsSinceStartDouble + 4;
                }
                else if (NPCTracker.TryGetNear(Position, 30, out var npc) &&
                         VoxelPhysics.CanSee(Position, npc.Position.Vector))
                {
                    Indicator.SendIconIndicatorNear(new Vector3Int(Position), ID,
                                                    new IndicatorState(2, ColonyBuiltIn.ItemTypes.SLINGBULLET.Name));

                    AudioManager.SendAudio(Position, "sling");

                    npc.OnHit(Damage.Sum(kvp => kvp.Key.CalcDamage(DamageType.Physical, kvp.Value)), this,
                              ModLoader.OnHitData.EHitSourceType.Monster);

                    AudioManager.SendAudio(npc.Position.Vector, "fleshHit");
                    _cooldown = Time.SecondsSinceStartDouble + 4;
                }
            }

            return base.Update();
        }

See example here: https://github.com/JBurlison/Pandaros.Settlers/blob/master/Pandaros.Settlers/Pandaros.Settlers/Monsters/Normal/RockThrowerZombie.cs