Создание бонусов для оружия в ServerPerks

Тема в разделе "Кодинг", создана пользователем Essence, 13 июн 2019 в 02:16.

  1. Essence

    Essence Moderator Команда форума

    Думаю многие задавались вопросом как разнообразить перки не только оружием, но и бонусами, отличными от стандартных.

    В данной статье я распишу как создать кастомный бонус на оружие для любого перка.

    Возьмем к примеру оружие ближнего боя. Как известно, при ударе в спину мобы получают двойной урон. Мы же хотим настроить повышенный урон по своему усмотрению.

    1. Переопределим функцию Timer в Fire классе нашего оружия.
    Для этого нужно скопировать целиком функцию Timer из KFMeleeFire.
    Обычно всё используемое оружие на сервере лежит в пакете с ServerPerks, так что с этим проблем не должно возникнуть.
    В противном случае (например, для стандартного оружия) можно использовать следующий мутатор:
    Код:
    class WeaponsConfigMut extends Mutator;
    
    simulated function PostBeginPlay()
    {
        class'ClaymoreSword'.default.FireModeClass[0]=class'ClaymoreSwordFireNew';
        class'ClaymoreSword'.default.FireModeClass[1]=class'ClaymoreSwordFireBNew';
    }
    
    defaultproperties
    {
        bAddToServerPackages=True
        GroupName="KF-WeaponsConfigMut"
        FriendlyName="WeaponsConfigMut"
        Description="WeaponsConfigMut"
        bAlwaysRelevant=True
        RemoteRole=ROLE_SimulatedProxy
    }
    Сами Fire классы будут выглядеть следующим образом:
    Код:
    class ClaymoreSwordFireNew extends ClaymoreSwordFire;
    
    simulated function Timer()
    {
        local Actor HitActor;
        local vector StartTrace, EndTrace, HitLocation, HitNormal;
        local rotator PointRot;
        local int MyDamage;
        local bool bBackStabbed;
        local Pawn Victims;
        local vector dir, lookdir;
        local float DiffAngle, VictimDist;
    
        MyDamage = MeleeDamage;
    
        If( !KFWeapon(Weapon).bNoHit )
        {
            MyDamage = MeleeDamage;
            StartTrace = Instigator.Location + Instigator.EyePosition();
    
            if( Instigator.Controller!=None && PlayerController(Instigator.Controller)==None && Instigator.Controller.Enemy!=None )
            {
                PointRot = rotator(Instigator.Controller.Enemy.Location-StartTrace);
            }
            else
            {
                PointRot = Instigator.GetViewRotation();
            }
    
            EndTrace = StartTrace + vector(PointRot)*weaponRange;
            HitActor = Instigator.Trace( HitLocation, HitNormal, EndTrace, StartTrace, true);
    
            if (HitActor!=None)
            {
                ImpactShakeView();
    
                if( HitActor.IsA('ExtendedZCollision') && HitActor.Base != none &&
                    HitActor.Base.IsA('KFMonster') )
                {
                    HitActor = HitActor.Base;
                }
    
                if ( (HitActor.IsA('KFMonster') || HitActor.IsA('KFHumanPawn')) && KFMeleeGun(Weapon).BloodyMaterial!=none )
                {
                    Weapon.Skins[KFMeleeGun(Weapon).BloodSkinSwitchArray] = KFMeleeGun(Weapon).BloodyMaterial;
                    Weapon.texture = Weapon.default.Texture;
                }
                if( Level.NetMode==NM_Client )
                {
                    Return;
                }
    
                if( HitActor.IsA('Pawn') && !HitActor.IsA('Vehicle')
                 && (Normal(HitActor.Location-Instigator.Location) dot vector(HitActor.Rotation))>0 ) // Fixed in Balance Round 2
                {
                    bBackStabbed = true;
                    MyDamage*=2; // Backstab >:P
                }
    
                if( (KFMonster(HitActor)!=none) )
                {
                    KFMonster(HitActor).bBackstabbed = bBackStabbed;
    
                    HitActor.TakeDamage(MyDamage, Instigator, HitLocation, vector(PointRot), hitDamageClass) ;
    
                    if(MeleeHitSounds.Length > 0)
                    {
                        Weapon.PlaySound(MeleeHitSounds[Rand(MeleeHitSounds.length)],SLOT_None,MeleeHitVolume,,,,false);
                    }
    
                    if(VSize(Instigator.Velocity) > 300 && KFMonster(HitActor).Mass <= Instigator.Mass)
                    {
                        KFMonster(HitActor).FlipOver();
                    }
                }
                else
                {
                    HitActor.TakeDamage(MyDamage, Instigator, HitLocation, vector(PointRot), hitDamageClass) ;
                    Spawn(HitEffectClass,,, HitLocation, rotator(HitLocation - StartTrace));
                }
            }
    
            if( WideDamageMinHitAngle > 0 )
            {
                foreach Weapon.VisibleCollidingActors( class 'Pawn', Victims, (weaponRange * 2), StartTrace ) //, RadiusHitLocation
                {
                    if( (HitActor != none && Victims == HitActor) || Victims.Health <= 0 )
                    {
                        continue;
                    }
    
                    if( Victims != Instigator )
                    {
                        VictimDist = VSizeSquared(Instigator.Location - Victims.Location);
    
                        if( VictimDist > (((weaponRange * 1.1) * (weaponRange * 1.1)) + (Victims.CollisionRadius * Victims.CollisionRadius)) )
                        {
                            continue;
                        }
    
                          lookdir = Normal(Vector(Instigator.GetViewRotation()));
                        dir = Normal(Victims.Location - Instigator.Location);
    
                           DiffAngle = lookdir dot dir;
    
                           if( DiffAngle > WideDamageMinHitAngle )
                           {
                               Victims.TakeDamage(MyDamage*DiffAngle, Instigator, (Victims.Location + Victims.CollisionHeight * vect(0,0,0.7)), vector(PointRot), hitDamageClass) ;
                            if(MeleeHitSounds.Length > 0)
                            {
                                Victims.PlaySound(MeleeHitSounds[Rand(MeleeHitSounds.length)],SLOT_None,MeleeHitVolume,,,,false);
                            }
                           }
                    }
                }
            }
        }
    }
    Код:
    class ClaymoreSwordFireBNew extends ClaymoreSwordFireB;
    
    simulated function Timer()
    {
        local Actor HitActor;
        local vector StartTrace, EndTrace, HitLocation, HitNormal;
        local rotator PointRot;
        local int MyDamage;
        local bool bBackStabbed;
        local Pawn Victims;
        local vector dir, lookdir;
        local float DiffAngle, VictimDist;
    
        MyDamage = MeleeDamage;
    
        If( !KFWeapon(Weapon).bNoHit )
        {
            MyDamage = MeleeDamage;
            StartTrace = Instigator.Location + Instigator.EyePosition();
    
            if( Instigator.Controller!=None && PlayerController(Instigator.Controller)==None && Instigator.Controller.Enemy!=None )
            {
                PointRot = rotator(Instigator.Controller.Enemy.Location-StartTrace);
            }
            else
            {
                PointRot = Instigator.GetViewRotation();
            }
    
            EndTrace = StartTrace + vector(PointRot)*weaponRange;
            HitActor = Instigator.Trace( HitLocation, HitNormal, EndTrace, StartTrace, true);
    
            if (HitActor!=None)
            {
                ImpactShakeView();
    
                if( HitActor.IsA('ExtendedZCollision') && HitActor.Base != none &&
                    HitActor.Base.IsA('KFMonster') )
                {
                    HitActor = HitActor.Base;
                }
    
                if ( (HitActor.IsA('KFMonster') || HitActor.IsA('KFHumanPawn')) && KFMeleeGun(Weapon).BloodyMaterial!=none )
                {
                    Weapon.Skins[KFMeleeGun(Weapon).BloodSkinSwitchArray] = KFMeleeGun(Weapon).BloodyMaterial;
                    Weapon.texture = Weapon.default.Texture;
                }
                if( Level.NetMode==NM_Client )
                {
                    Return;
                }
    
                if( HitActor.IsA('Pawn') && !HitActor.IsA('Vehicle')
                 && (Normal(HitActor.Location-Instigator.Location) dot vector(HitActor.Rotation))>0 ) // Fixed in Balance Round 2
                {
                    bBackStabbed = true;
                    MyDamage*=2; // Backstab >:P
                }
    
                if( (KFMonster(HitActor)!=none) )
                {
                    KFMonster(HitActor).bBackstabbed = bBackStabbed;
    
                    HitActor.TakeDamage(MyDamage, Instigator, HitLocation, vector(PointRot), hitDamageClass) ;
    
                    if(MeleeHitSounds.Length > 0)
                    {
                        Weapon.PlaySound(MeleeHitSounds[Rand(MeleeHitSounds.length)],SLOT_None,MeleeHitVolume,,,,false);
                    }
    
                    if(VSize(Instigator.Velocity) > 300 && KFMonster(HitActor).Mass <= Instigator.Mass)
                    {
                        KFMonster(HitActor).FlipOver();
                    }
                }
                else
                {
                    HitActor.TakeDamage(MyDamage, Instigator, HitLocation, vector(PointRot), hitDamageClass) ;
                    Spawn(HitEffectClass,,, HitLocation, rotator(HitLocation - StartTrace));
                }
            }
    
            if( WideDamageMinHitAngle > 0 )
            {
                foreach Weapon.VisibleCollidingActors( class 'Pawn', Victims, (weaponRange * 2), StartTrace ) //, RadiusHitLocation
                {
                    if( (HitActor != none && Victims == HitActor) || Victims.Health <= 0 )
                    {
                        continue;
                    }
    
                    if( Victims != Instigator )
                    {
                        VictimDist = VSizeSquared(Instigator.Location - Victims.Location);
    
                        if( VictimDist > (((weaponRange * 1.1) * (weaponRange * 1.1)) + (Victims.CollisionRadius * Victims.CollisionRadius)) )
                        {
                            continue;
                        }
    
                          lookdir = Normal(Vector(Instigator.GetViewRotation()));
                        dir = Normal(Victims.Location - Instigator.Location);
    
                           DiffAngle = lookdir dot dir;
    
                           if( DiffAngle > WideDamageMinHitAngle )
                           {
                               Victims.TakeDamage(MyDamage*DiffAngle, Instigator, (Victims.Location + Victims.CollisionHeight * vect(0,0,0.7)), vector(PointRot), hitDamageClass) ;
                            if(MeleeHitSounds.Length > 0)
                            {
                                Victims.PlaySound(MeleeHitSounds[Rand(MeleeHitSounds.length)],SLOT_None,MeleeHitVolume,,,,false);
                            }
                           }
                    }
                }
            }
        }
    }

    2. Ссылаемся на наш бонус, который будет хранится в SRVeterancyTypes.
    Для этого изменим этот кусок кода:
    Код:
                if( HitActor.IsA('Pawn') && !HitActor.IsA('Vehicle')
                 && (Normal(HitActor.Location-Instigator.Location) dot vector(HitActor.Rotation))>0 ) // Fixed in Balance Round 2
                {
                    bBackStabbed = true;
                    MyDamage*=2; // Backstab >:P
                }
    Приведем его в такой вид:
    Код:
                if( HitActor.IsA('Pawn') && !HitActor.IsA('Vehicle')
                 && (Normal(HitActor.Location-Instigator.Location) dot vector(HitActor.Rotation))>0 ) // Fixed in Balance Round 2
                {
                    bBackStabbed = true;
                    if ( KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo) != none
                    && KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill != none )
                    {
                        MyDamage *= class<SRVeterancyTypes>(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill).Static.GetBackStabDamageMod(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo), KFMonster(HitActor), hitDamageClass); // Backstab >:P
                    }
                }

    3. Создаем бонус.
    Вставляем функцию в SRVeterancyTypes
    Код:
    static function float GetBackStabDamageMod(KFPlayerReplicationInfo KFPRI, KFMonster KFM, class<DamageType> DmgType)
    {
        Return 1.0;
    }
    Разберем подробнее эту функцию.
    Она является статической, а значит, мы можем работать в ней только с дефолтными или входными параметрами.
    В качестве входных параметров у нас выступают KFPlayerReplicationInfo игрока (зачастую используется для создания условий, связанных с уровнем игрока), KFMonster - моб, по которому мы ударили со спины, и DamageType оружия с которого был нанесён урон.
    Я думаю этих трёх параметров вполне достаточно для гибкой настройки бонуса.
    Выходной параметр у неё float, по умолчанию возвращает 1.0

    4. Редактируем бонус для Берсеркера.
    Для этого в SRVetBerserker переопределяем нашу функцию и редактируем её как нам угодно.
    Код:
    static function float GetBackStabDamageMod(KFPlayerReplicationInfo KFPRI, KFMonster KFM, class<DamageType> DmgType)
    {
        if ( KFM.IsA('ZombieBoss_STANDARD') ) return 1.0; // Бонус не распространяется на патриарха
        if ( KFM.IsA('ZombieScrake_STANDARD') && DmgType == class'DamTypeClaymoreSword' ) // Ударили мясника мечом в спину, увеличиваем урон в завимости от уровня игрока
        {
            if ( KFPRI.ClientVeteranSkillLevel >= 0 )
                return 1.5 + (0.1 * FMin(KFPRI.ClientVeteranSkillLevel, 5)); // Изначально даётся +50% к урону, +10% за уровень. Итого на 5 уровне будет +100% к урону.
        }
        return 1.0;
    }

    В общем-то, на этом всё.

    Замечание.
    Для того чтобы бонус работал и для другого оружия, в его Fire классе также должна быть переопределена функция Timer и должен происходить вызов функции GetBackStabDamageMod из SRVeterancyTypes.

    Чистый SP версии 7.5 с внесёнными изменениями:
    Ссылка
     
    2/5 и HATAXA нравится это.