Пример #1
0
    public void ServerPerformInteraction(PositionalHandApply interaction)
    {
        PlayerNetworkActions pna = interaction.Performer.GetComponent <PlayerNetworkActions>();

        if (interaction.HandObject == null)
        {         // No item in hand, so let's TEACH THIS RACK A LESSON
            Chat.AddCombatMsgToChat(interaction.Performer, "You kick the rack. Nice job!",
                                    interaction.Performer.ExpensiveName() + " kicks the rack.");

            integrity.ApplyDamage(Random.Range(4, 8), AttackType.Melee, DamageType.Brute);
            return;
        }

        // If the player is using a wrench on the rack, deconstruct it
        if (Validations.IsTool(interaction.HandObject, ToolType.Wrench) &&
            !interaction.Performer.Player().Script.playerMove.IsHelpIntent)
        {
            SoundManager.PlayNetworkedAtPos("Wrench", interaction.WorldPositionTarget, 1f);
            PoolManager.PoolNetworkInstantiate(rackParts, interaction.WorldPositionTarget.RoundToInt(),
                                               interaction.TargetObject.transform.parent);
            PoolManager.PoolNetworkDestroy(gameObject);

            return;
        }

        // Like a table, but everything is neatly stacked.
        Vector3 targetPosition = interaction.WorldPositionTarget.RoundToInt();

        targetPosition.z = -0.2f;
        pna.CmdPlaceItem(interaction.HandSlot.equipSlot, targetPosition, interaction.Performer, true);
    }
Пример #2
0
    public void ServerPerformInteraction(PositionalHandApply interaction)
    {
        performer                = interaction.Performer;
        target                   = interaction.TargetObject;
        performerName            = interaction.Performer.ExpensiveName();
        targetName               = interaction.TargetObject.ExpensiveName();
        interactionWorldPosition = interaction.WorldPositionTarget;

        var rng = new System.Random();

        if (rng.Next(1, 100) <= KNOCKDOWN_CHANCE)
        {
            KnockDown();
        }

        else if (rng.Next(1, 100) <= DISARM_CHANCE)
        {
            Disarm();
        }
        else
        {
            SoundManager.PlayNetworkedAtPos("PunchMiss", interactionWorldPosition, sourceObj: target);

            Chat.AddCombatMsgToChat(
                performer,
                $"You fail to disarm {targetName}!",
                $"{performerName} attempts to disarm {targetName}!");
            Chat.AddExamineMsgFromServer(target, $"{performerName} attempts to disarm you!");
        }
    }
Пример #3
0
    public void ServerPerformInteraction(PositionalHandApply interaction)
    {
        PlayerNetworkActions pna = interaction.Performer.GetComponent <PlayerNetworkActions>();

        if (interaction.HandObject == null)
        {         // No item in hand, so let's TEACH THIS RACK A LESSON
            Chat.AddCombatMsgToChat(interaction.Performer, "You kick the rack. Nice job!",
                                    interaction.Performer.ExpensiveName() + " kicks the rack.");

            integrity.ApplyDamage(Random.Range(4, 8), AttackType.Melee, DamageType.Brute);
            return;
        }

        // If the player is using a wrench on the rack, deconstruct it
        if (Validations.HasItemTrait(interaction.HandObject, CommonTraits.Instance.Wrench) &&
            interaction.Intent != Intent.Help)
        {
            SoundManager.PlayNetworkedAtPos("Wrench", interaction.WorldPositionTarget, 1f);
            Spawn.ServerPrefab(rackParts, interaction.WorldPositionTarget.RoundToInt(),
                               interaction.TargetObject.transform.parent);
            Despawn.ServerSingle(gameObject);

            return;
        }

        // Like a table, but everything is neatly stacked.
        Vector3 targetPosition = interaction.WorldPositionTarget.RoundToInt();

        targetPosition.z = -0.2f;
        pna.CmdPlaceItem(interaction.HandSlot.NamedSlot.GetValueOrDefault(NamedSlot.none), targetPosition, interaction.Performer, true);
    }
Пример #4
0
        protected override void LethalElectrocution(Electrocution electrocution, float shockPower)
        {
            PlayerMove.allowInput = false;
            // TODO: Add sparks VFX at shockSourcePos.
            SoundManager.PlayNetworkedAtPos(CommonSounds.Instance.Sparks, electrocution.ShockSourcePos);
            StartCoroutine(ElectrocutionSequence());

            string victimChatString, observerChatString;

            if (electrocution.ShockSourceName != null)
            {
                victimChatString   = $"The {electrocution.ShockSourceName} electrocutes you!";
                observerChatString = $"{gameObject.ExpensiveName()} is electrocuted by the {electrocution.ShockSourceName}!";
            }
            else
            {
                victimChatString   = $"Something electrocutes you!";
                observerChatString = $"{gameObject.ExpensiveName()} is electrocuted by something!";
            }
            Chat.AddCombatMsgToChat(gameObject, victimChatString, observerChatString);

            var damage = shockPower / ELECTROCUTION_BURNDAMAGE_MODIFIER;

            if (ELECTROCUTION_MAX_DAMAGE != -1 && damage > ELECTROCUTION_MAX_DAMAGE)
            {
                damage = ELECTROCUTION_MAX_DAMAGE;
            }
            DealElectrocutionDamage(damage * 0.4f, electrocutedHand);
            DealElectrocutionDamage(damage * 0.25f, BodyPartType.Chest);
            DealElectrocutionDamage(damage * 0.175f, BodyPartType.LeftLeg);
            DealElectrocutionDamage(damage * 0.175f, BodyPartType.RightLeg);
        }
Пример #5
0
    public void ServerPerformInteraction(PositionalHandApply interaction)
    {
        performer                = interaction.Performer;
        target                   = interaction.TargetObject;
        performerName            = interaction.Performer.ExpensiveName();
        targetName               = interaction.TargetObject.ExpensiveName();
        interactionWorldPosition = interaction.WorldPositionTarget;

        if (Cooldowns.TryStart(interaction, this, side: NetworkSide.Server) == false)
        {
            return;
        }

        var rng = new System.Random();

        if (rng.Next(1, 100) <= KNOCKDOWN_CHANCE)
        {
            KnockDown();
        }

        else if (rng.Next(1, 100) <= DISARM_CHANCE)
        {
            Disarm();
        }
        else
        {
            SoundManager.PlayNetworkedAtPos(SingletonSOSounds.Instance.PunchMiss, interactionWorldPosition, sourceObj: target);

            Chat.AddCombatMsgToChat(
                performer,
                $"You fail to disarm {targetName}!",
                $"{performerName} attempts to disarm {targetName}!");
            Chat.AddExamineMsgFromServer(target, $"{performerName} attempts to disarm you!");
        }
    }
Пример #6
0
    public void ServerPerformInteraction(PositionalHandApply interaction)
    {
        PlayerNetworkActions pna = interaction.Performer.GetComponent <PlayerNetworkActions>();

        if (interaction.HandObject == null)
        {         // No item in hand, so let's TEACH THIS RACK A LESSON
            Chat.AddCombatMsgToChat(interaction.Performer, "You kick the rack. Nice job!",
                                    interaction.Performer.ExpensiveName() + " kicks the rack.");

            integrity.ApplyDamage(Random.Range(4, 8), AttackType.Melee, DamageType.Brute);
            return;
        }

        // If the player is using a wrench on the rack, deconstruct it
        if (Validations.HasItemTrait(interaction.HandObject, CommonTraits.Instance.Wrench) &&
            interaction.Intent != Intent.Help)
        {
            SoundManager.PlayNetworkedAtPos("Wrench", interaction.WorldPositionTarget, 1f);
            Spawn.ServerPrefab(rackParts, interaction.WorldPositionTarget.RoundToInt(),
                               interaction.TargetObject.transform.parent);
            Despawn.ServerSingle(gameObject);

            return;
        }

        //drop it right in the middle of the rack. IN order to do that we have to calculate
        //that position as an offset from the performer
        //TODO: Make it less awkward by adding a serverdrop method that accepts absolute position instead of vector.
        var targetTileWorldPosition = gameObject.TileWorldPosition();
        var targetTileVector        =
            (Vector3Int)targetTileWorldPosition - interaction.PerformerPlayerScript.registerTile.WorldPositionServer;

        Inventory.ServerDrop(interaction.HandSlot, targetTileVector.To2Int());
    }
    public void CmdRequestHug(string hugger, GameObject huggedPlayer)
    {
        //validate that hug can be done
        if (!Validations.CanApply(playerScript, huggedPlayer, NetworkSide.Server))
        {
            return;
        }
        //hugging is kind of a melee-type action, at least we wouldn't want to spam it quickly
        if (!Cooldowns.TryStartServer(playerScript, CommonCooldowns.Instance.Melee))
        {
            return;
        }

        string hugged   = huggedPlayer.GetComponent <PlayerScript>().playerName;
        var    lhb      = gameObject.GetComponent <LivingHealthBehaviour>();
        var    lhbOther = huggedPlayer.GetComponent <LivingHealthBehaviour>();

        if (lhb != null && lhbOther != null && (lhb.FireStacks > 0 || lhbOther.FireStacks > 0))
        {
            lhb.ApplyDamage(huggedPlayer, 1, AttackType.Fire, DamageType.Burn);
            lhbOther.ApplyDamage(gameObject, 1, AttackType.Fire, DamageType.Burn);
            Chat.AddCombatMsgToChat(gameObject, $"You give {hugged} a fiery hug.", $"{hugger} has given {hugged} a fiery hug.");
        }
        else
        {
            Chat.AddActionMsgToChat(gameObject, $"You hugged {hugged}.", $"{hugger} has hugged {hugged}.");
        }
    }
    public void CmdRequestDisarm(GameObject playerToDisarm)
    {
        if (!Validations.CanApply(playerScript, playerToDisarm, NetworkSide.Server))
        {
            return;
        }
        if (!Cooldowns.TryStartServer(playerScript, CommonCooldowns.Instance.Interaction))
        {
            return;
        }
        var    rng                          = new System.Random();
        string disarmerName                 = playerScript.gameObject.Player()?.Name;
        string playerToDisarmName           = playerToDisarm.Player()?.Name;
        var    disarmStorage                = playerToDisarm.GetComponent <ItemStorage>();
        var    leftHandSlot                 = disarmStorage.GetNamedItemSlot(NamedSlot.leftHand);
        var    rightHandSlot                = disarmStorage.GetNamedItemSlot(NamedSlot.rightHand);
        var    disarmedPlayerRegister       = playerToDisarm.GetComponent <RegisterPlayer>();
        var    disarmedPlayerNetworkActions = playerToDisarm.GetComponent <PlayerNetworkActions>();

        // This is based off the alien/humanoid/attack_hand disarm code of TGStation's codebase.
        // Disarms have 5% chance to knock down, then it has a 50% chance to disarm.
        if (5 >= rng.Next(1, 100))
        {
            disarmedPlayerRegister.ServerStun(6f, false);
            SoundManager.PlayNetworkedAtPos("ThudSwoosh", disarmedPlayerRegister.WorldPositionServer, sourceObj: disarmedPlayerRegister.gameObject);

            Chat.AddCombatMsgToChat(gameObject, $"You have knocked {playerToDisarmName} down!",
                                    $"{disarmerName} has knocked {playerToDisarmName} down!");
        }

        else if (50 >= rng.Next(1, 100))
        {
            // Disarms
            if (leftHandSlot.Item != null)
            {
                Inventory.ServerDrop(leftHandSlot);
            }

            if (rightHandSlot.Item != null)
            {
                Inventory.ServerDrop(rightHandSlot);
            }

            SoundManager.PlayNetworkedAtPos("ThudSwoosh", disarmedPlayerRegister.WorldPositionServer, sourceObj: disarmedPlayerRegister.gameObject);

            Chat.AddCombatMsgToChat(gameObject, $"You have disarmed {playerToDisarmName}!",
                                    $"{disarmerName} has disarmed {playerToDisarmName}!");
        }
        else
        {
            SoundManager.PlayNetworkedAtPos("PunchMiss", disarmedPlayerRegister.WorldPositionServer, sourceObj: disarmedPlayerRegister.gameObject);

            Chat.AddCombatMsgToChat(gameObject, $"You attempted to disarm {playerToDisarmName}!",
                                    $"{disarmerName} has attempted to disarm {playerToDisarmName}!");
        }
    }
Пример #9
0
    private void KnockDown()
    {
        var targetRegister = target.GetComponent <RegisterPlayer>();

        targetRegister.ServerStun(KNOCKDOWN_STUN_TIME, false);

        SoundManager.PlayNetworkedAtPos(SingletonSOSounds.Instance.ThudSwoosh, interactionWorldPosition, sourceObj: target);
        Chat.AddCombatMsgToChat(
            performer,
            $"You knock {targetName} down!",
            $"{performerName} knocks {targetName} down!");
        Chat.AddExamineMsgFromServer(target, $"{performerName} knocks you down!");
    }
Пример #10
0
    public void CmdRequestDisarm(GameObject disarmer, GameObject playerToDisarm)
    {
        var    rng                          = new System.Random();
        string disarmerName                 = disarmer.Player()?.Name;
        string playerToDisarmName           = playerToDisarm.Player()?.Name;
        var    leftHandSlot                 = InventoryManager.GetSlotFromOriginatorHand(playerToDisarm, EquipSlot.leftHand);
        var    rightHandSlot                = InventoryManager.GetSlotFromOriginatorHand(playerToDisarm, EquipSlot.rightHand);
        var    disarmedPlayerRegister       = playerToDisarm.GetComponent <RegisterPlayer>();
        var    disarmedPlayerNetworkActions = playerToDisarm.GetComponent <PlayerNetworkActions>();

        // This is based off the alien/humanoid/attack_hand disarm code of TGStation's codebase.
        // Disarms have 5% chance to knock down, then it has a 50% chance to disarm.
        if (5 >= rng.Next(1, 100))
        {
            disarmedPlayerRegister.Stun(6f, false);
            SoundManager.PlayNetworkedAtPos("ThudSwoosh", disarmedPlayerRegister.WorldPositionServer);

            Chat.AddCombatMsgToChat(gameObject, $"You have knocked {playerToDisarmName} down!",
                                    $"{disarmerName} has knocked {playerToDisarmName} down!");
        }

        else if (50 >= rng.Next(1, 100))
        {
            // Disarms
            if (leftHandSlot.Item != null)
            {
                disarmedPlayerNetworkActions.DropItem(EquipSlot.leftHand);
            }

            if (rightHandSlot.Item != null)
            {
                disarmedPlayerNetworkActions.DropItem(EquipSlot.rightHand);
            }

            SoundManager.PlayNetworkedAtPos("ThudSwoosh", disarmedPlayerRegister.WorldPositionServer);

            Chat.AddCombatMsgToChat(gameObject, $"You have disarmed {playerToDisarmName}!",
                                    $"{disarmerName} has disarmed {playerToDisarmName}!");
        }
        else
        {
            SoundManager.PlayNetworkedAtPos("PunchMiss", disarmedPlayerRegister.WorldPositionServer);

            Chat.AddCombatMsgToChat(gameObject, $"You attempted to disarm {playerToDisarmName}!",
                                    $"{disarmerName} has attempted to disarm {playerToDisarmName}!");
        }
    }
Пример #11
0
        public override void Punish(ConnectedPlayer player)
        {
            Chat.AddCombatMsgToChat(player.GameObject,
                                    "You suddenly feel very solid!",
                                    $"{player.GameObject.ExpensiveName()} goes very still! {player.Script.characterSettings.TheyPronoun()}'s been petrified!");

            player.Script.playerMove.allowInput = false;
            // Piggy-back off IsMiming property to prevent the player from speaking.
            // TODO: convert to player trait when we have that system.
            player.Script.mind.IsMiming = true;

            StartCoroutine(Unpetrify(player.Script));

            Chat.AddCombatMsgToChat(player.GameObject,
                                    "<size=60><b>Your body freezes up! Can't... move... can't... think...</b></size>",
                                    $"{player.GameObject.ExpensiveName()}'s skin rapidly turns to marble!");
        }
Пример #12
0
    public void CmdRequestHug(string hugger, GameObject huggedPlayer)
    {
        string hugged   = huggedPlayer.GetComponent <PlayerScript>().playerName;
        var    lhb      = gameObject.GetComponent <LivingHealthBehaviour>();
        var    lhbOther = huggedPlayer.GetComponent <LivingHealthBehaviour>();

        if (lhb != null && lhbOther != null && (lhb.FireStacks > 0 || lhbOther.FireStacks > 0))
        {
            lhb.ApplyDamage(huggedPlayer, 1, AttackType.Fire, DamageType.Burn);
            lhbOther.ApplyDamage(gameObject, 1, AttackType.Fire, DamageType.Burn);
            Chat.AddCombatMsgToChat(gameObject, $"You give {hugged} a fiery hug.", $"{hugger} has given {hugged} a fiery hug.");
        }
        else
        {
            Chat.AddActionMsgToChat(gameObject, $"You hugged {hugged}.", $"{hugger} has hugged {hugged}.");
        }
    }
Пример #13
0
    public void CmdRequestPunchAttack(GameObject victim, Vector2 punchDirection, BodyPartType damageZone)
    {
        var victimHealth       = victim.GetComponent <LivingHealthBehaviour>();
        var victimRegisterTile = victim.GetComponent <RegisterTile>();
        var rng = new System.Random();

        if (!playerScript.IsInReach(victim, true) || !victimHealth)
        {
            return;
        }

        // If the punch is not self inflicted, do the simple lerp attack animation.
        if (victim != gameObject)
        {
            RpcMeleeAttackLerp(punchDirection, null);
            playerMove.allowInput = false;
        }

        // This is based off the alien/humanoid/attack_hand punch code of TGStation's codebase.
        // Punches have 90% chance to hit, otherwise it is a miss.
        if (90 >= rng.Next(1, 100))
        {
            // The punch hit.
            victimHealth.ApplyDamage(gameObject, (int)fistDamage, AttackType.Melee, DamageType.Brute, damageZone);
            if (fistDamage > 0)
            {
                Chat.AddAttackMsgToChat(gameObject, victim, damageZone);
            }

            // Make a random punch hit sound.
            SoundManager.PlayNetworkedAtPos("Punch#", victimRegisterTile.WorldPosition);

            StartCoroutine(AttackCoolDown());
        }
        else
        {
            // The punch missed.
            string victimName = victim.Player()?.Name;
            SoundManager.PlayNetworkedAtPos("PunchMiss", transform.position);
            Chat.AddCombatMsgToChat(gameObject, $"You attempted to punch {victimName} but missed!",
                                    $"{gameObject.Player()?.Name} has attempted to punch {victimName}!");
        }
    }
Пример #14
0
    private void Disarm()
    {
        var disarmStorage = target.GetComponent <DynamicItemStorage>();

        if (disarmStorage == null)
        {
            return;
        }

        var leftHandSlots  = disarmStorage.GetNamedItemSlots(NamedSlot.leftHand);
        var rightHandSlots = disarmStorage.GetNamedItemSlots(NamedSlot.rightHand);

        foreach (var leftHandSlot in leftHandSlots)
        {
            if (leftHandSlot.IsEmpty)
            {
                continue;
            }

            Inventory.ServerDrop(leftHandSlot);
        }

        foreach (var rightHandSlot in rightHandSlots)
        {
            if (rightHandSlot.IsEmpty)
            {
                continue;
            }

            Inventory.ServerDrop(rightHandSlot);
        }

        SoundManager.PlayNetworkedAtPos(SingletonSOSounds.Instance.ThudSwoosh, interactionWorldPosition, sourceObj: target);
        Chat.AddCombatMsgToChat(
            performer,
            $"You successfully disarm {targetName}!",
            $"{performerName} disarms {targetName}!");
        Chat.AddExamineMsgFromServer(target, $"{performerName} disarms you!");
    }
Пример #15
0
    private void Disarm()
    {
        var disarmStorage = target.GetComponent <ItemStorage>();
        var leftHandSlot  = disarmStorage.GetNamedItemSlot(NamedSlot.leftHand);
        var rightHandSlot = disarmStorage.GetNamedItemSlot(NamedSlot.rightHand);

        if (leftHandSlot.Item != null)
        {
            Inventory.ServerDrop(leftHandSlot);
        }

        if (rightHandSlot.Item != null)
        {
            Inventory.ServerDrop(rightHandSlot);
        }

        SoundManager.PlayNetworkedAtPos("ThudSwoosh", interactionWorldPosition, sourceObj: target);
        Chat.AddCombatMsgToChat(
            performer,
            $"You successfully disarm {targetName}!",
            $"{performerName} disarms {targetName}!");
        Chat.AddExamineMsgFromServer(target, $"{performerName} disarms you!");
    }
Пример #16
0
    public void ServerPerformMeleeAttack(GameObject victim, Vector2 attackDirection,
                                         BodyPartType damageZone, LayerType layerType)
    {
        if (Cooldowns.IsOnServer(playerScript, CommonCooldowns.Instance.Melee))
        {
            return;
        }
        var weapon = playerScript.playerNetworkActions.GetActiveHandItem();

        var tiles = victim.GetComponent <InteractableTiles>();

        if (tiles)
        {
            //validate based on position of target vector
            if (!Validations.CanApply(playerScript, victim, NetworkSide.Server, targetVector: attackDirection))
            {
                return;
            }
        }
        else
        {
            //validate based on position of target object
            if (!Validations.CanApply(playerScript, victim, NetworkSide.Server))
            {
                return;
            }
        }

        if (!playerMove.allowInput ||
            playerScript.IsGhost ||
            !victim ||
            !playerScript.playerHealth.serverPlayerConscious
            )
        {
            return;
        }

        var isWeapon = weapon != null;
        ItemAttributesV2 weaponAttr = isWeapon ? weapon.GetComponent <ItemAttributesV2>() : null;
        var       damage            = isWeapon ? weaponAttr.ServerHitDamage : fistDamage;
        var       damageType        = isWeapon ? weaponAttr.ServerDamageType : DamageType.Brute;
        var       attackSoundName   = isWeapon ? weaponAttr.ServerHitSound : "Punch#";
        LayerTile attackedTile      = null;
        bool      didHit            = false;

        ItemAttributesV2 weaponStats = null;

        if (isWeapon)
        {
            weaponStats = weapon.GetComponent <ItemAttributesV2>();
        }


        // If Tilemap LayerType is not None then it is a tilemap being attacked
        if (layerType != LayerType.None)
        {
            var tileChangeManager = victim.GetComponent <TileChangeManager>();
            if (tileChangeManager == null)
            {
                return;                                        //Make sure its on a matrix that is destructable
            }
            //Tilemap stuff:
            var tileMapDamage = victim.GetComponentInChildren <MetaTileMap>().Layers[layerType].gameObject
                                .GetComponent <TilemapDamage>();
            if (tileMapDamage != null)
            {
                if (isWeapon && weaponStats != null &&
                    weaponStats.hitSoundSettings == SoundItemSettings.OnlyObject)
                {
                    attackSoundName = "";
                }
                var worldPos = (Vector2)transform.position + attackDirection;
                attackedTile = tileChangeManager.InteractableTiles.LayerTileAt(worldPos, true);
                tileMapDamage.DoMeleeDamage(worldPos,
                                            gameObject, (int)damage);
                didHit = true;
            }
        }
        else
        {
            //a regular object being attacked

            LivingHealthBehaviour victimHealth = victim.GetComponent <LivingHealthBehaviour>();

            var integrity = victim.GetComponent <Integrity>();
            if (integrity != null)
            {
                //damaging an object
                if (isWeapon && weaponStats != null &&
                    weaponStats.hitSoundSettings == SoundItemSettings.Both)
                {
                    SoundManager.PlayNetworkedAtPos(integrity.soundOnHit, gameObject.WorldPosServer(), Random.Range(0.9f, 1.1f));
                }
                else if (isWeapon && weaponStats != null &&
                         weaponStats.hitSoundSettings == SoundItemSettings.OnlyObject && integrity.soundOnHit != "")
                {
                    SoundManager.PlayNetworkedAtPos(integrity.soundOnHit, gameObject.WorldPosServer(), Random.Range(0.9f, 1.1f));
                    attackSoundName = "";
                }
                integrity.ApplyDamage((int)damage, AttackType.Melee, damageType);
                didHit = true;
            }
            else
            {
                //damaging a living thing
                var rng = new System.Random();
                // This is based off the alien/humanoid/attack_hand punch code of TGStation's codebase.
                // Punches have 90% chance to hit, otherwise it is a miss.
                if (isWeapon || 90 >= rng.Next(1, 100))
                {
                    // The attack hit.
                    victimHealth.ApplyDamageToBodypart(gameObject, (int)damage, AttackType.Melee, damageType, damageZone);
                    didHit = true;
                }
                else
                {
                    // The punch missed.
                    string victimName = victim.Player()?.Name;
                    SoundManager.PlayNetworkedAtPos("PunchMiss", transform.position);
                    Chat.AddCombatMsgToChat(gameObject, $"You attempted to punch {victimName} but missed!",
                                            $"{gameObject.Player()?.Name} has attempted to punch {victimName}!");
                }
            }
        }

        //common logic to do if we hit something
        if (didHit)
        {
            if (!string.IsNullOrEmpty(attackSoundName))
            {
                SoundManager.PlayNetworkedAtPos(attackSoundName, transform.position);
            }

            if (damage > 0)
            {
                Chat.AddAttackMsgToChat(gameObject, victim, damageZone, weapon, attackedTile: attackedTile);
            }
            if (victim != gameObject)
            {
                RpcMeleeAttackLerp(attackDirection, weapon);
                //playerMove.allowInput = false;
            }
        }

        Cooldowns.TryStartServer(playerScript, CommonCooldowns.Instance.Melee);
    }
    public void CmdRequestMeleeAttack(GameObject victim, Vector2 attackDirection,
                                      BodyPartType damageZone, LayerType layerType)
    {
        var weapon = playerScript.playerNetworkActions.GetActiveHandItem();

        var tiles = victim.GetComponent <InteractableTiles>();

        if (tiles)
        {
            //validate based on position of target vector
            if (!Validations.CanApply(playerScript, victim, NetworkSide.Server, targetVector: attackDirection))
            {
                return;
            }
        }
        else
        {
            //validate based on position of target object
            if (!Validations.CanApply(playerScript, victim, NetworkSide.Server))
            {
                return;
            }
        }

        if (!playerMove.allowInput ||
            playerScript.IsGhost ||
            !victim ||
            !playerScript.playerHealth.serverPlayerConscious
            )
        {
            return;
        }

        if (!allowAttack)
        {
            return;
        }

        var isWeapon = weapon != null;
        ItemAttributesV2 weaponAttr = isWeapon ? weapon.GetComponent <ItemAttributesV2>() : null;
        var       damage            = isWeapon ? weaponAttr.ServerHitDamage : fistDamage;
        var       damageType        = isWeapon ? weaponAttr.ServerDamageType : DamageType.Brute;
        var       attackSoundName   = isWeapon ? weaponAttr.ServerHitSound : "Punch#";
        LayerTile attackedTile      = null;

        bool didHit = false;


        // If Tilemap LayerType is not None then it is a tilemap being attacked
        if (layerType != LayerType.None)
        {
            var tileChangeManager = victim.GetComponent <TileChangeManager>();
            if (tileChangeManager == null)
            {
                return;                                        //Make sure its on a matrix that is destructable
            }
            //Tilemap stuff:
            var tileMapDamage = victim.GetComponentInChildren <MetaTileMap>().Layers[layerType].gameObject
                                .GetComponent <TilemapDamage>();
            if (tileMapDamage != null)
            {
                var worldPos = (Vector2)transform.position + attackDirection;
                attackedTile = tileChangeManager.InteractableTiles.LayerTileAt(worldPos);
                tileMapDamage.DoMeleeDamage(worldPos,
                                            gameObject, (int)damage);
                didHit = true;
            }
        }
        else
        {
            //a regular object being attacked

            //butchering
            //TODO: Move butchering logic to IF2, it should be a progress action done on corpses (make a Corpse component probably)
            LivingHealthBehaviour victimHealth = victim.GetComponent <LivingHealthBehaviour>();
            if (victimHealth != null && victimHealth.IsDead && isWeapon && weaponAttr.HasTrait(KnifeTrait))
            {
                if (victim.GetComponent <SimpleAnimal>())
                {
                    SimpleAnimal attackTarget = victim.GetComponent <SimpleAnimal>();
                    RpcMeleeAttackLerp(attackDirection, weapon);
                    playerMove.allowInput = false;
                    attackTarget.Harvest();
                    SoundManager.PlayNetworkedAtPos("BladeSlice", transform.position);
                }
                else
                {
                    PlayerHealth attackTarget = victim.GetComponent <PlayerHealth>();
                    RpcMeleeAttackLerp(attackDirection, weapon);
                    playerMove.allowInput = false;
                    attackTarget.Harvest();
                    SoundManager.PlayNetworkedAtPos("BladeSlice", transform.position);
                }
            }

            var integrity = victim.GetComponent <Integrity>();
            if (integrity != null)
            {
                //damaging an object
                integrity.ApplyDamage((int)damage, AttackType.Melee, damageType);
                didHit = true;
            }
            else
            {
                //damaging a living thing
                var rng = new System.Random();
                // This is based off the alien/humanoid/attack_hand punch code of TGStation's codebase.
                // Punches have 90% chance to hit, otherwise it is a miss.
                if (isWeapon || 90 >= rng.Next(1, 100))
                {
                    // The attack hit.
                    victimHealth.ApplyDamageToBodypart(gameObject, (int)damage, AttackType.Melee, damageType, damageZone);
                    didHit = true;
                }
                else
                {
                    // The punch missed.
                    string victimName = victim.Player()?.Name;
                    SoundManager.PlayNetworkedAtPos("PunchMiss", transform.position);
                    Chat.AddCombatMsgToChat(gameObject, $"You attempted to punch {victimName} but missed!",
                                            $"{gameObject.Player()?.Name} has attempted to punch {victimName}!");
                }
            }
        }

        //common logic to do if we hit something
        if (didHit)
        {
            SoundManager.PlayNetworkedAtPos(attackSoundName, transform.position);
            if (damage > 0)
            {
                Chat.AddAttackMsgToChat(gameObject, victim, damageZone, weapon, attackedTile: attackedTile);
            }
            if (victim != gameObject)
            {
                RpcMeleeAttackLerp(attackDirection, weapon);
                playerMove.allowInput = false;
            }
        }

        //no matter what, start a cooldown for attacking again so they can't spam attack requests
        StartCoroutine(AttackCoolDown());
    }