Dear forum users! In compliance with the new European GDPR regulations, we'd just like to inform you that if you have an account, your email address is stored in our database. We do not share your information with third parties, and your email address and password are encrypted for security reasons.

New to the forum? Say hello in this topic! Also make sure to read the rules.

Public Event! Bullets Deluxe

Custom scripts
Forum rules
By using the forum you agree to the following rules.
Post Reply
User avatar
Danger Ross
Superfighter
Superfighter
Posts: 123
Joined: Thu Mar 31, 2016 12:56 am
Title: Competition
SFD Alias: Danger Ross
Started SFD: 14 june 2012 (launch day)
Location: California
Gender:
Age: 19

Public Event! Bullets Deluxe

Post by Danger Ross » Sat Aug 10, 2019 8:40 pm

ImageImage


What is it:
We are organizing a big event where anyone interested in scripting can submit their own script to be combined into the final creation. This is the first public script collaboration event in SFD and we urge you to create your own submissions as long as they follow the guidelines.
The final script will make every submission into ammo types that will alter what players shoot similarly to bouncing ammo and fire ammo. This event is a response to the new possibilities added by the last update which allowed us to manipulate bullets as they are flying.



How to contribute:
Guidelines for submission:
1. Keep it "reasonable", no bullets that can destroy the entire map very quickly.
2. No overpowered damage: 4 pistol bullets should not be enough to kill you
3. the ammo must affect all different ammo types evenly. You are not allowed to have specific guns do specific things.
4. Make it unique. Don't submit scripts that are similar to other submissions and be creative.

We have a discord server for map makers and scripters that was recently opened to the public here:
https://discord.gg/B4Grnw3

You can submit your scripts following the format there.
or you can submit your script here on the forum post comments.
make sure to write a description about your script in either case. We want to know what name we should give credit to in the final script and how to implement it.

You can also just write your suggestions or ideas for scripts by commenting on this forum post or by chatting on the discord server.
have fun!

Current Contributions:
Author Name: Danger Ross
Submission Name: circles
Short Description: makes bullets go in circles clockwise

Author Name: Danger Ross
Submission Name: floaties
Short Description: makes bullets stay in one spot

Author Name: Danger Ross
Submission Name: Homing
Short Description: makes bullets home in on targets. Homes in faster when closer. Can hit people around corners.

Author Name: Danger Ross
Submission Name: Virus Bullet
Short Description: Makes bullets split into two when hitting a wall

Author Name: Danger Ross
Submission Name: Wavy
Short Description: Makes bullets have a random pattern in flight.

Author Name: exsceses
Submission Name: Explosive Bullets
Short Description: Makes bullets deal AoE (area of effect) damage.

Author Name: exsceses
Submission Name: Hollow Points
Short Description: Makes bullets deal (stacking) bleed damage over time.

Author: Ebomb09
Submission Name: Chaining Shots
Short Description: Makes bullets bounce off players, and towards others once.

Author Name: motto
Submission Name: Heavy slugs
Short Description: All bullets are slower, and do twice the damage. They are also affected by gravity. (Broken in slomo since the gravity value relies on framerate, so yeah pretty much useless)

Author Name: Near
Submission Name: Infected Bullet
Short Description: Bullets have a small chance to infect player. Infected players turn into zombie after dying for a while

Author Name: Near
Submission Name: PresentDay
Short Description: Make bullet become present which spawns weapon/powerup when opened. Sometimes it spawns something else...
Last edited by Danger Ross on Tue Aug 13, 2019 4:39 pm, edited 5 times in total.
9 x
sorry bucko, you can't punch with swords 8-)

User avatar
KliPeH
Moderator
Moderator
Posts: 834
Joined: Sat Mar 19, 2016 3:03 pm
Title: [happy moth noises]
SFD Account: KliPeH
Started SFD: Pre-Alpha 1.4.2
Gender:
Contact:

Post by KliPeH » Sat Aug 10, 2019 9:32 pm

Danger Ross wrote:
Sat Aug 10, 2019 8:40 pm
Author Name: exsceses
Submission Name: Explosive Bullets
Short Description: Makes bullets deal AoE (area of effect) damage.

Author Name: motto
Submission Name: Heavy slugs
Short Description: All bullets are slower, and do twice the damage. They are also affected by gravity. (Broken in slomo since the gravity value relies on framerate, so yeah pretty much useless)
I would love to have these two in my games. The first one is also a planned feature.

Is it possible to have the utility supply crates randomly drop one (custom) object instead of a real item, that will act as one of the ammo pickups from your list? If so, is it possible to design your own item (gluing 1-pixel tiles to one another?) to create a functional, custom powerup that will alter your bullets whenever you pick it up?

Great suggestions otherwise.
---  
Here's an idea (I'm assuming this is an idea contest rather than a script submission list, I don't actually know any scripting).

Author Name:        KliPeH
Submission Name:  Healing Darts
Short Description:  Affect only regular bullets and arrows. Reduce mag size to 3-5 rounds and remove spare ammo.
Each bullet that connects with a player gives them health that is equal to x2 of the weapon's damage (crits included).
1 x
Image

User avatar
Danger Ross
Superfighter
Superfighter
Posts: 123
Joined: Thu Mar 31, 2016 12:56 am
Title: Competition
SFD Alias: Danger Ross
Started SFD: 14 june 2012 (launch day)
Location: California
Gender:
Age: 19

Post by Danger Ross » Sat Aug 10, 2019 9:57 pm

KliPeH wrote:
Sat Aug 10, 2019 9:32 pm
Is it possible to have the utility supply crates randomly drop one (custom) object instead of a real item, that will act as one of the ammo pickups from your list? If so, is it possible to design your own item (gluing 1-pixel tiles to one another?) to create a functional, custom powerup that will alter your bullets whenever you pick it up?
That is literally what we have planned! You just read my mind. Be expecting a system like this that seamlessly implements these ammo types into gameplay.
As well as a gamemode option where every weapon has a special ammo mod for some crazy gameplay.

Also that's a nice idea you got there. I'm going to get a scripter on that one asap! :P
1 x
sorry bucko, you can't punch with swords 8-)

NearHuscarl
Fighter
Fighter
Posts: 20
Joined: Thu Feb 07, 2019 4:36 am

Post by NearHuscarl » Mon Aug 12, 2019 4:47 am

Author Name: Near
Submission Name: PresentDay
Short Description: Make bullet become present which spawns weapon/powerup when opened. Sometimes it spawns something else...

Code: Select all

        public void OnStartup()
        {
            Game.GetPlayers()[0].GiveWeaponItem(WeaponItem.M60);
            Game.RunCommand("ia 1");

            Events.ProjectileCreatedCallback.Start(OnProjectileCreated);
            Events.ObjectTerminatedCallback.Start(OnObjectTerminated);
        }

        private static Random rnd = new Random();
        private static T GetItem<T>(List<T> list)
        {
            var rndIndex = rnd.Next(list.Count);
            return list[rndIndex];
        }

        private Dictionary<int, IObject> m_customBullets = new Dictionary<int, IObject>();
        private readonly List<string> m_presents = new List<string>()
        {
            "XmasPresent00",
            "WpnPistol",
            "WpnPistol45",
            "WpnSilencedPistol",
            "WpnMachinePistol",
            "WpnMagnum",
            "WpnRevolver",
            "WpnPumpShotgun",
            "WpnDarkShotgun",
            "WpnTommygun",
            "WpnSMG",
            "WpnM60",
            "WpnPipeWrench",
            "WpnChain",
            "WpnWhip",
            "WpnHammer",
            "WpnKatana",
            "WpnMachete",
            "WpnChainsaw",
            "WpnKnife",
            "WpnSawedoff",
            "WpnBat",
            "WpnBaton",
            "WpnShockBaton",
            "WpnLeadPipe",
            "WpnUzi",
            "WpnSilencedUzi",
            "WpnBazooka",
            "WpnAxe",
            "WpnAssaultRifle",
            "WpnMP50",
            "WpnSniperRifle",
            "WpnCarbine",
            "WpnFlamethrower",
            "ItemPills",
            "ItemMedkit",
            "ItemSlomo5",
            "ItemSlomo10",
            "ItemStrengthBoost",
            "ItemSpeedBoost",
            "ItemLaserSight",
            "ItemBouncingAmmo",
            "ItemFireAmmo",
            "WpnGrenades",
            "WpnMolotovs",
            "WpnMines",
            "WpnShuriken",
            "WpnBow",
            "WpnFlareGun",
            "WpnGrenadeLauncher",
        };
        private readonly List<string> m_oofs = new List<string>()
        {
            "WpnGrenadesThrown",
            "WpnMolotovsThrown",
            "WpnMineThrown",
        };
        private void OnObjectTerminated(IObject[] objs)
        {
            foreach (var obj in objs)
            {
                if (m_customBullets.ContainsKey(obj.UniqueID))
                {
                    var customBullet = m_customBullets[obj.UniqueID];
                    var position = customBullet.GetWorldPosition();

                    // normally, the present spawn some random shits upon destroyed. make the present disappeared
                    // and spawn something else as a workaround
                    customBullet.SetWorldPosition(new Vector2(-5000, 5000));

                    var rndNum = rnd.Next(0, 100);
                    if (rndNum < 1) // big oof
                    {
                        SpawnBadSanta(position);
                    }
                    if (1 <= rndNum && rndNum < 5)
                    {
                        Game.CreateObject(GetItem(m_oofs), position);
                    }
                    if (5 <= rndNum && rndNum < 30)
                    {
                        Game.CreateObject(GetItem(m_presents), position);
                    }

                    m_customBullets.Remove(obj.UniqueID);
                }
            }
        }

        private void SpawnBadSanta(Vector2 position)
        {
            var player = Game.CreatePlayer(position);

            player.SetModifiers(new PlayerModifiers()
            {
                MaxHealth = 200,
                CurrentHealth = 200,
                ExplosionDamageTakenModifier = 0.5f,
                MeleeForceModifier = 1.5f,
                SizeModifier = 1.1f,
                InfiniteAmmo = 1,
            });
            player.SetProfile(new IProfile()
            {
                Name = "Bad Santa",
                Accesory = new IProfileClothingItem("SantaMask", "ClothingLightGray", "ClothingLightGray", ""),
                ChestOver = new IProfileClothingItem("Coat", "ClothingRed", "ClothingLightGray", ""),
                ChestUnder = new IProfileClothingItem("SleevelessShirt", "ClothingLightGray", "ClothingLightGray", ""),
                Feet = new IProfileClothingItem("BootsBlack", "ClothingBrown", "ClothingLightGray", ""),
                Gender = Gender.Male,
                Hands = new IProfileClothingItem("SafetyGlovesBlack", "ClothingGray", "ClothingLightGray", ""),
                Head = new IProfileClothingItem("SantaHat", "ClothingRed", "ClothingLightGray", ""),
                Legs = new IProfileClothingItem("Pants", "ClothingRed", "ClothingLightGray", ""),
                Skin = new IProfileClothingItem("Tattoos", "Skin3", "ClothingPink", ""),
                Waist = new IProfileClothingItem("Belt", "ClothingDarkRed", "ClothingLightYellow", ""),
            });
            player.SetBotName("Bad Santa");

            player.SetBotBehaviorSet(BotBehaviorSet.GetBotBehaviorPredefinedSet(PredefinedAIType.ChallengeA));
            player.SetBotBehaviorActive(true);

            player.SetTeam(PlayerTeam.Independent);

            player.GiveWeaponItem(WeaponItem.KNIFE);
            player.GiveWeaponItem(WeaponItem.M60);
            player.GiveWeaponItem(WeaponItem.UZI);

            Game.CreateDialogue("Ho ho ho!", new Color(128, 32, 32), player);
        }

        private void OnProjectileCreated(IProjectile[] projectiles)
        {
            foreach (var projectile in projectiles)
            {
                switch (projectile.ProjectileItem)
                {
                    case ProjectileItem.BAZOOKA:
                    case ProjectileItem.GRENADE_LAUNCHER:
                        break;

                    default:
                        ToPresentBullet(projectile);
                        break;
                }
            }
        }

        private void ToPresentBullet(IProjectile projectile)
        {
            var customBullet = Game.CreateObject("XmasPresent00",
                worldPosition: projectile.Position,
                angle: (float)Math.Atan2(projectile.Direction.X, projectile.Direction.Y),
                linearVelocity: projectile.Velocity / 50 + new Vector2(0, 3),
                angularVelocity: 50f * (int)(projectile.Direction.X % 1),
                faceDirection: (int)(projectile.Direction.X % 1)
                );

            customBullet.TrackAsMissile(true);
            m_customBullets.Add(customBullet.UniqueID, customBullet);
            projectile.FlagForRemoval();
        }
Author Name: Near
Submission Name: Infected Bullet
Short Description: Bullets have a small chance to infect player. Infected players turn into zombie after dying for a while

Code: Select all

        private class InfectedCorpse
        {
            public static int TimeToTurnIntoZombie = 5000;
            public IPlayer Body { get; set; }
            public float DeathTime { get; private set; }
            public bool IsTurningIntoZombie { get; private set; }
            public bool CanTurnIntoZombie { get; private set; }
            public bool IsZombie { get; private set; }

            private bool TurnIntoZombie()
            {
                if (Body.IsRemoved || Body.IsBurnedCorpse) return false;

                var zombieBody = Game.CreatePlayer(Body.GetWorldPosition());
                if (rnd.Next(0, 100) < 10)
                {
                    Game.CreateDialogue("Brainzz", zombieBody);
                }
                zombieBody.SetBotBehaviorSet(BotBehaviorSet.GetBotBehaviorPredefinedSet(GetZombieType(Body)));
                zombieBody.SetTeam(ZombieTeam);

                var modifiers = Body.GetModifiers();
                modifiers.RunSpeedModifier -= 0.15f;
                modifiers.SprintSpeedModifier -= 0.15f;
                modifiers.CurrentHealth = modifiers.MaxHealth * 0.75f;
                zombieBody.SetModifiers(modifiers);

                var profile = Body.GetProfile();
                zombieBody.SetProfile(ToZombieProfile(profile));
                zombieBody.SetBotName(Body.Name);

                Body.Remove();
                Body = zombieBody;
                Body.SetBotBehaivorActive(false);
                Body.AddCommand(new PlayerCommand(PlayerCommandType.StartCrouch));
                IsTurningIntoZombie = true;
                return true;
            }

            public InfectedCorpse(IPlayer player)
            {
                Body = player;
                IsTurningIntoZombie = false;
                IsZombie = false;
                CanTurnIntoZombie = true;
                DeathTime = Game.TotalElapsedGameTime;
            }

            public void Update()
            {
                if (IsElapsed(DeathTime, TimeToTurnIntoZombie))
                {
                    if (!IsTurningIntoZombie)
                    {
                        CanTurnIntoZombie = TurnIntoZombie();
                    }
                    if (!IsZombie)
                    {
                        UpdateTurningIntoZombieAnimation();
                    }
                }
            }

            private bool isKneeling;
            private float kneelingTime;
            private void UpdateTurningIntoZombieAnimation()
            {
                if (!isKneeling)
                {
                    kneelingTime = Game.TotalElapsedGameTime;
                    isKneeling = true;
                }
                else
                {
                    if (IsElapsed(kneelingTime, 700))
                    {
                        Body.AddCommand(new PlayerCommand(PlayerCommandType.StopCrouch));
                        Body.SetBotBehaivorActive(true);
                        IsZombie = true;
                    }
                }
            }
        }

        private class TheInfected
        {
            private IPlayer m_player;

            public TheInfected(IPlayer player)
            {
                m_player = player;
            }

            private float m_damageTimer = 0;
            public void Update(float dt)
            {
                if (!m_player.IsRemoved && !m_player.IsBurnedCorpse)
                {
                    m_damageTimer += dt;
                    if (m_damageTimer > 2000)
                    {
                        var health = m_player.GetHealth();
                        health -= 0.5f;
                        m_player.SetHealth(health);
                        Game.PlayEffect(EffectName.BloodTrail, m_player.GetWorldPosition());
                        m_damageTimer = 0;
                    }
                }
            }
        }

        /// <summary>
        /// Placeholder constructor that's not to be included in the ScriptWindow!
        /// </summary>

        private static Random rnd = new Random();
        private static readonly PlayerTeam ZombieTeam = PlayerTeam.Team4;
        private static Dictionary<int, TheInfected> m_infectedPlayers = new Dictionary<int, TheInfected>();
        private static List<InfectedCorpse> m_infectedCorpses = new List<InfectedCorpse>();
        private Dictionary<int, IProjectile> m_customBullets = new Dictionary<int, IProjectile>();

        public void OnStartup()
        {
            Game.GetPlayers()[0].GiveWeaponItem(WeaponItem.M60);
            Game.RunCommand("ia 1");

            Events.UpdateCallback.Start(OnUpdate);
            Events.ProjectileCreatedCallback.Start(OnProjectileCreated);
            Events.ProjectileHitCallback.Start(OnProjectileHit);
            Events.PlayerDamageCallback.Start(OnPlayerDamage);
            Events.PlayerDeathCallback.Start(OnPlayerDeath);
        }

        private void OnProjectileCreated(IProjectile[] projectiles)
        {
            foreach (var projectile in projectiles)
            {
                switch (projectile.ProjectileItem)
                {
                    case ProjectileItem.BAZOOKA:
                    case ProjectileItem.GRENADE_LAUNCHER:
                        break;

                    default:
                        ToInfectedBullet(projectile);
                        break;
                }
            }
        }

        private void OnProjectileHit(IProjectile projectile, ProjectileHitArgs args)
        {
            if (args.RemoveFlag)
            {
                m_customBullets.Remove(projectile.InstanceID);
            }
        }

        private void OnPlayerDamage(IPlayer player, PlayerDamageArgs args)
        {
            if (args.DamageType == PlayerDamageEventType.Projectile)
            {
                if (m_customBullets.ContainsKey(args.SourceID))
                {
                    if (rnd.Next(0, 100) < 15 && !CanInfectFrom(player) && !player.IsBurnedCorpse)
                    {
                        Infect(player);
                    }
                }
            }
            if (args.DamageType == PlayerDamageEventType.Melee)
            {
                var attacker = Game.GetPlayer(args.SourceID);

                UpdateInfectedStatus(player, attacker, args);
            }
        }

        private void UpdateInfectedStatus(IPlayer player, IPlayer attacker, PlayerDamageArgs args)
        {
            if (player == null) return;

            if (!CanInfectFrom(player) && !player.IsBurnedCorpse && attacker != null)
            {
                var attackerPunching = args.DamageType == PlayerDamageEventType.Melee
                    && attacker.CurrentWeaponDrawn == WeaponItemType.NONE
                    && !attacker.IsKicking && !attacker.IsJumpKicking;

                if (CanInfectFrom(attacker) && attackerPunching)
                {
                    Infect(player);
                }
            }
        }

        private static void OnPlayerDeath(IPlayer player, PlayerDeathArgs args)
        {
            if (player == null) return;

            if (m_infectedPlayers.ContainsKey(player.UniqueID))
            {
                m_infectedCorpses.Add(new InfectedCorpse(player));
            }
        }

        private bool CanInfectFrom(IPlayer player)
        {
            return m_infectedPlayers.ContainsKey(player.UniqueID)
                || HasZombieSkin(player);
        }

        private void Infect(IPlayer player)
        {
            Game.PlayEffect(EffectName.CustomFloatText, player.GetWorldPosition(), "infected");
            m_infectedPlayers.Add(player.UniqueID, new TheInfected(player));

            if (player.IsDead)
            {
                m_infectedCorpses.Add(new InfectedCorpse(player));
            }
        }

        private void OnUpdate(float dt)
        {
            UpdateTheInfected(dt);
        }

        private void UpdateTheInfected(float dt)
        {
            foreach (var player in m_infectedPlayers.Values)
            {
                player.Update(dt);
            }

            foreach (var corpse in m_infectedCorpses.ToList())
            {
                corpse.Update();

                if (corpse.IsZombie || !corpse.CanTurnIntoZombie)
                {
                    m_infectedCorpses.Remove(corpse);
                }
            }
        }

        private void ToInfectedBullet(IProjectile projectile)
        {
            if (!m_customBullets.ContainsKey(projectile.InstanceID))
            {
                m_customBullets.Add(projectile.InstanceID, projectile);
            }
        }

        public static bool IsElapsed(float timeStarted, float timeToElapse)
        {
            return Game.TotalElapsedGameTime - timeStarted >= timeToElapse;
        }

        #region Zombie helper methods
        public static PredefinedAIType GetZombieType(IPlayer player)
        {
            if (player == null)
            {
                throw new Exception("Player cannot be null");
            }

            if (player.IsUser)
            {
                return PredefinedAIType.ZombieB;
            }
            else
            {
                var playerAI = player.GetBotBehavior().PredefinedAI;

                switch (playerAI)
                {
                    // Expert, Hard
                    case PredefinedAIType.BotA:
                    case PredefinedAIType.BotB:
                    case PredefinedAIType.ChallengeA:
                    case PredefinedAIType.MeleeB:
                        return PredefinedAIType.ZombieB;

                    default:
                        return PredefinedAIType.ZombieA;
                }
            }
        }

        private static IProfile ToZombieProfile(IProfile profile)
        {
            switch (profile.Skin.Name)
            {
                case "Normal":
                case "Tattoos":
                    profile.Skin = new IProfileClothingItem("Zombie", "Skin1", "ClothingLightGray", "");
                    break;

                case "Normal_fem":
                case "Tattoos_fem":
                    profile.Skin = new IProfileClothingItem("Zombie_fem", "Skin1", "ClothingLightGray", "");
                    break;

                case "BearSkin":
                    profile.Skin = new IProfileClothingItem("FrankenbearSkin", "ClothingDarkGray", "ClothingLightBlue", "");
                    break;
            }

            return profile;
        }

        private bool HasZombieSkin(IPlayer player)
        {
            return player.GetProfile().Skin.Name == "Zombie"
                || player.GetProfile().Skin.Name == "Zombie_fem"
                || player.GetProfile().Skin.Name == "FrankenbearSkin";
        }
        #endregion
Author Name: Near
Submission Name: Electric Bullet
Short Description: EMP bullets that have a chance to stunned player for a while (like shock baton). Have a very small chance to make a blast (stun multiple players in a small area)

Code: Select all

        private class StunnedPlayer
        {
            public static readonly float StunnedTime = 3000f;
            private IPlayer m_player;

            public int UniqueID
            {
                get { return m_player.UniqueID; }
            }

            private bool isStunned;
            public bool IsStunned
            {
                get
                {
                    return isStunned;
                }
                private set
                {
                    isStunned = value;
                    if (isStunned)
                    {
                        m_player.SetInputEnabled(false);
                        m_player.AddCommand(new PlayerCommand(PlayerCommandType.DeathKneelInfinite));
                    }
                    else
                    {
                        m_player.AddCommand(new PlayerCommand(PlayerCommandType.StopDeathKneel));
                        m_player.SetInputEnabled(true);
                    }
                }
            }

            public StunnedPlayer(IPlayer player) : this(player, player.GetWorldPosition())
            {
            }

            public StunnedPlayer(IPlayer player, Vector2 hitPosition)
            {
                m_player = player;
                IsStunned = true;

                Game.PlayEffect(EffectName.Electric, hitPosition);

                if (rnd.Next(0, 100) < 30)
                {
                    Game.SpawnFireNode(hitPosition, Vector2.Zero);
                    Game.PlayEffect(EffectName.FireTrail, hitPosition);
                }
            }

            private float m_stunnedTimer = 0;
            private float m_stunnedEffectTimer = 0;
            public void Update(float dt)
            {
                if (IsStunned)
                {
                    m_stunnedTimer += dt;
                    m_stunnedEffectTimer += dt;

                    if (m_stunnedEffectTimer >= 400)
                    {
                        var position = m_player.GetWorldPosition();
                        position.X += rnd.Next(-10, 10);
                        position.Y += rnd.Next(-10, 10);

                        Game.PlayEffect(EffectName.Electric, position);
                        m_stunnedEffectTimer = 0;
                    }

                    if (m_stunnedTimer >= StunnedTime)
                    {
                        IsStunned = false;
                    }
                }
            }
        }

        /// <summary>
        /// Placeholder constructor that's not to be included in the ScriptWindow!
        /// </summary>

        private static Random rnd = new Random();
        private Dictionary<int, IProjectile> m_customBullets = new Dictionary<int, IProjectile>();
        private static Dictionary<int, StunnedPlayer> m_stunnedPlayers = new Dictionary<int, StunnedPlayer>();

        public void OnStartup()
        {
            var me = Game.GetPlayers()[0];
            me.GiveWeaponItem(WeaponItem.PISTOL);
            me.GiveWeaponItem(WeaponItem.M60);
            var mod = me.GetModifiers();
            mod.MeleeStunImmunity = 1;
            me.SetModifiers(mod);

            Game.RunCommand("ia 1");
            Game.RunCommand("ih 1");

            foreach (var player in Game.GetPlayers())
            {
                if (player == me) continue;

                var bot = Game.CreatePlayer(player.GetWorldPosition());
                bot.SetBotName("bot " + rnd.Next(0, 1000));
                bot.SetBotBehaviorSet(BotBehaviorSet.GetBotBehaviorPredefinedSet(PredefinedAIType.BotD));
                bot.SetBotBehaviorActive(true);
                player.Remove();
            }


            Events.UpdateCallback.Start(OnUpdate);
            Events.ProjectileCreatedCallback.Start(OnProjectileCreated);
            Events.ProjectileHitCallback.Start(OnProjectileHit);
        }

        private void OnProjectileCreated(IProjectile[] projectiles)
        {
            foreach (var projectile in projectiles)
            {
                switch (projectile.ProjectileItem)
                {
                    case ProjectileItem.BAZOOKA:
                    case ProjectileItem.GRENADE_LAUNCHER:
                        break;

                    default:
                        ToEMPBullet(projectile);
                        break;
                }
            }
        }

        private void OnProjectileHit(IProjectile projectile, ProjectileHitArgs args)
        {
            if (m_customBullets.ContainsKey(projectile.InstanceID))
            {
                var rndNum = rnd.Next(0, 100);
                if (rndNum < 1)
                {
                    ElectrocuteRange(args.HitPosition);
                }
                if (1 <= rndNum && rndNum < 21)
                {
                    var obj = Game.GetObject(args.HitObjectID);
                    Electrocute(args);
                }

                m_customBullets.Remove(projectile.InstanceID);
            }
        }

        private Vector2? m_empBlastCenter = null;
        public static readonly float EMPBlastRadius = 15f;
        private float m_stunnedRangeTimer = 0f;
        private void ElectrocuteRange(Vector2 position)
        {
            foreach (var player in Game.GetPlayers())
            {
                if (IsTouchingCircle(player.GetAABB(), position, EMPBlastRadius) && CanBeStunned(player))
                {
                    Game.PlayEffect(EffectName.CustomFloatText, player.GetWorldPosition(), "stunned");
                    m_stunnedPlayers.Add(player.UniqueID, new StunnedPlayer(player));
                }
            }

            for (var i = 0; i < 360; i += 72) // Play electric effect 5 times in circle (360 / 5 = 72)
            {
                var degree = i;
                var radianAngle = degree * Math.PI / 180.0f;

                var direction = new Vector2()
                {
                    X = (float)Math.Cos(radianAngle),
                    Y = (float)Math.Sin(radianAngle),
                };

                Game.PlayEffect(EffectName.Electric, position + direction * EMPBlastRadius);
            }

            m_empBlastCenter = position;
        }

        private void Electrocute(ProjectileHitArgs args)
        {
            var position = args.HitPosition;

            if (args.IsPlayer)
            {
                var player = (IPlayer)Game.GetObject(args.HitObjectID);

                if (CanBeStunned(player))
                {
                    Game.PlayEffect(EffectName.CustomFloatText, position, "stunned");
                    m_stunnedPlayers.Add(player.UniqueID, new StunnedPlayer(player, position));
                }
            }
        }

        private bool CanBeStunned(IPlayer player)
        {
            return !player.IsRemoved && !player.IsDead && !m_stunnedPlayers.ContainsKey(player.UniqueID);
        }

        private void OnUpdate(float dt)
        {
            UpdatePlayers(dt);

            m_stunnedRangeTimer += dt;
            if (m_stunnedRangeTimer >= StunnedPlayer.StunnedTime)
            {
                m_stunnedRangeTimer = 0;
                m_empBlastCenter = null;
            }
            if (m_empBlastCenter != null)
                Game.DrawCircle((Vector2)m_empBlastCenter, EMPBlastRadius, Color.Red);
        }

        private void UpdatePlayers(float dt)
        {
            foreach (var player in m_stunnedPlayers.Values.ToList())
            {
                player.Update(dt);

                if (!player.IsStunned)
                {
                    m_stunnedPlayers.Remove(player.UniqueID);
                }
            }
        }

        private void ToEMPBullet(IProjectile projectile)
        {
            if (!m_customBullets.ContainsKey(projectile.InstanceID))
            {
                m_customBullets.Add(projectile.InstanceID, projectile);
            }
        }

        private bool IsTouchingCircle(Area area, Vector2 center, float radius)
        {
            var lines = new List<Vector2[]>()
            {
                new Vector2[] { area.BottomRight, area.BottomLeft },
                new Vector2[] { area.BottomLeft, area.TopLeft },
                new Vector2[] { area.TopLeft, area.TopRight },
                new Vector2[] { area.TopRight, area.BottomRight },
            };

            var minDistanceToCenter = float.MaxValue;

            foreach (var line in lines)
            {
                var distanceToCenter = FindDistanceToSegment(center, line[0], line[1]);
                if (distanceToCenter < minDistanceToCenter) minDistanceToCenter = distanceToCenter;
            }

            return minDistanceToCenter <= radius;
        }

        // https://stackoverflow.com/a/1501725/9449426
        private float FindDistanceToSegment(Vector2 point, Vector2 p1, Vector2 p2)
        {
            // Return minimum distance between line segment vw and point point
            var lengthSquare = (float)(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));  // i.e. |p2-p1|^2 -  avoid a sqrt
            if (lengthSquare == 0.0) return Vector2.Distance(point, p1);   // p1 == p2 case
            // Consider the line extending the segment, parameterized as p1 + t (p2 - p1).
            // We find projection of point point onto the line. 
            // It falls where t = [(point-p1) . (p2-p1)] / |p2-p1|^2
            // We clamp t from [0,1] to handle points outside the segment vw.
            var t = MathHelper.Clamp(Vector2.Dot(point - p1, p2 - p1) / lengthSquare, 0, 1);
            var projection = p1 + t * (p2 - p1);  // Projection falls on the segment
            return Vector2.Distance(point, projection);
        }
EDIT: Update the scripts to 1.3.0b. Add Electric bullet
2 x

Post Reply