Вопросы по ServerPerks и его модификациям.

Тема в разделе "Кодинг", создана пользователем scar, 26 янв 2011.

  1. Essence

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

    У тебя функция возвращает False прежде чем происходит изменение DisableTag.
    То бишь, надо чтобы было так:
    Код:
    if( Pickup == class'SPSniperPickup' || Pickup == class'MK23Pickup')
        {
            if( KFPRI.ClientVeteranSkill.default.PerkIndex == 2 && KFPRI.ClientVeteranSkillLevel >= 9 )
            {
                return true;
            }
            else
            {
                Default.DisableTag="9 Lvl";
                return false;
            }
        }
     
    Mikizverb нравится это.
  2. Mikizverb

    Mikizverb Новенький

    Работает отлично спасибо !
     
  3. Mikizverb

    Mikizverb Новенький

  4. Essence

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

    Можно.
    Вот пример:
    Код:
    Class SRInvasionLoginMenu extends UT2K4PlayerLoginMenu;
    
    var automated b_VisitSite;
    ...
    function bool SiteButtonClicked(GUIComponent Sender)
    {
        PlayerOwner().ConsoleCommand("start"@"https://vk.com/kf_example");
        return true;
    }
    ...
    defaultproperties
    {
        Begin Object Class=GUIButton Name=VisitSite
            Caption="Group VK"
            Hint="Our Group"
            WinTop=0.003
            WinLeft=0.0350
            WinWidth=0.2170
            WinHeight=0.0300
            TabOrder=8
            OnClick=SRInvasionLoginMenu.SiteButtonClicked
            OnKeyEvent=VisitSite.InternalOnKeyEvent
        End Object
        b_VisitSite=VisitSite
    }
     
    Mikizverb нравится это.
  5. Arthur88

    Arthur88 Соучастник

    Доброго времени суток! Появилась необходимость реализовать проверку SteamID (Hash) игроков в функции exec function SetName. Разместил такой код в класс KFPCServ:
    Код:
    var string Hash;
    exec function SetName(coerce string S)
    {
          Hash=GetPlayerIDHash();
          if (Hash=="76561198387409492")
          {
               some code;
          }
    }

    В итоге выполнения функции всегда возвращается Hash равный "20b300195d48c2ccc265188cfea1a2f". Соответственно, проверка не проходит. Но, если этот же код написать в функцию exec function Suicide в том же классе KFPCServ, то Hash возвращается правильно и всё работает:
    Код:
    var string Hash;
    exec function Suicide()
    {
         Hash=GetPlayerIDHash();
         if (Hash=="76561198387409492")
             Pawn.Suicide();
         else
         ClientMessage("Live you moron!");
    }

    Чего здесь не хватает? Сильно не пинайте, занимаюсь этим не столь углублённо.

    По той же причине не могу вставить в меню сервера в табе Stats строчку с отображением Hash игрока, чтобы он сам мог посмотреть свой SteamID. Вернее строчку-то добавил, но туда вообще ничего не выводится. Правил класс SRStatList, функция function InitList( ClientPerkRepLink L ). Может у кого-то уже есть решение? Встречал на некоторых серверах такую фишку.
     
    Последнее редактирование: 22 июн 2019
  6. Essence

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

    Это айди на ждущем сервере.

    Код:
    Class ClientPerkRepLink extends LinkedReplicationInfo
        DependsOn(SRHUDKillingFloor);
    
    ...
    var string StatID;
    ...
    
    replication
    {
        ...
        reliable if(Role==ROLE_Authority && bNetOwner)
            StatID;
        ...
    }
    Код:
    Class ServerStStats extends SRStatsBase;
    
    function PreBeginPlay()
    {
        local LinkedReplicationInfo L;
        PlayerOwner=KFPlayerController(Owner);
        if(Rep==None)
        {
            Rep=Spawn(Class'ClientPerkRepLink', Owner);
            Rep.StatObject=Self;
            Rep.StatID=PlayerOwner.GetPlayerIDHash();
            ...
        }
        Super.PreBeginPlay();
    }
    Код:
    class SRStatList extends GUIVertList;
    
    function InitList( ClientPerkRepLink L )
    {
            ...
            StatProgress[25] = string(L.WinsCount);
            StatProgress[26] = string(L.LostsCount);
            StatProgress[27] = L.StatID;
            ...
    }
    
    defaultproperties
    {
            ...
            ItemCount=28
            ...
            ProgressName(25)="Won games"
            ProgressName(26)="Lost games"
            ProgressName(27)="Steam ID"
            ...
    }
     
    Arthur88 нравится это.
  7. Arthur88

    Arthur88 Соучастник

    Благодарю, теперь Steam ID в таблице Stats прекрасно отображается.
    Но по этой аналогии не смог разобраться как передать этот же StatID в класс KFPCServ, чтобы использовать его там в функции exec function SetName и выполнить проверку айди игрока, который вызывает SetName.

    Такой момент ещё: нужно применить шрифт из кастомной текстуры, которой нет по умолчанию у клиента. Правлю класс SRScoreBoard, функция отрисовки звёзд для перка. Сама функция переделана под отрисовку числового значения Lvl вместо звёздочек по статье Flame и работает исправно. Но не могу применить в ней свой шрифт. Допустим стандартный шрифт G15LCDFonts.LCDSmallFont, который не нуждается в скачивании клиентами, отображается. Но, если я задаю шрифт из пакета UT2003Fonts, которого в кф клиенте по умолчанию нет, то как мне сообщить клиенту, что он должен скачать эту текстуру с шрифтами с сервера?

    Код:
    simulated final function DrawPerkWithDigits(KFPlayerReplicationInfo KFPRI, Canvas C, float X, float Y, float Scale, Material PerkIcon)
    {
        local byte R,G,B,A;
        local Font LvlFont,OrigFont;
        local string S;
        ....
        LvlFont = Font(DynamicLoadObject("G15LCDFonts.LCDSmallFont", class'Font'));
    }
    Здесь, вместо G15LCDFonts.LCDSmallFont ,планируется использовать UT2003Fonts.FontEurostile14 ,который и нужно как-то закачать на клиент.
     
    Последнее редактирование: 23 июн 2019
  8. Essence

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

    Так его и не нужно тащить в PlayerController игрока.
    Достаточно вызвать GetPlayerIDHash() в самом KFPCServ.
    Почему Hash равен "20b300195d48c2ccc265188cfea1a2f" я уже написал.
    На выделенном сервере функция GetPlayerIDHash() вернёт правильный Steam ID.
    Самый простой способ - добавить в KillingFloor.ini в массив ServerPackages:
    Код:
    ServerPackages=KFMod
    ServerPackages=KFChar
    ServerPackages=UT2003Fonts
    
    В SRHUDKillingFloor может понадобится такая строчка:
    Код:
    #exec obj load file="UT2003Fonts"
     
    Arthur88 нравится это.
  9. Arthur88

    Arthur88 Соучастник

    Дело в том, что это выделенный сервер на самом обычном игровом хостинге. Поэтому я подумал, что просто неправильно использую функцию. Но парадокс ещё в том, что в функции Suicide айди возвращается корректно, а в SetName полуается тот самый айди ждущего сервера. Можно как-то костыльным способом получать айди вне функции Suicide и записывать его в переменную, например, которую можно ставить в проверку внутри Suicide? Т.е. чтобы конструкция была такого вида:
    Код:
    class KFPCServ extends KFPlayerController_Story;
    как-то получить айди из, уже имеющегося StatID из ClientPerkRepLink, чтобы можно было выполнить такую проверку:
    exec function Suicide()
    {
        if (StatID=="76561198387409492")
            Pawn.Suicide();
        else
        ClientMessage("Отказано");
    }
    Или может как-то более умнее проверять, не используя StatID.
     
  10. Essence

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

    Странно всё эт.
    Пробуйте через костыли.

    1) В папке Classes пакета ServerPerks создаём файл SRPlayerReplicationInfo с таким содержимым
    Код:
    class SRPlayerReplicationInfo extends KFPlayerReplicationInfo;
    
    var string StringPlayerID;
    
    replication
    {
        reliable if (bNetDirty && Role == Role_Authority)
            StringPlayerID;
    }

    2) Редактируем ServerPerksMut
    Код:
    function bool CheckReplacement(Actor Other, out byte bSuperRelevant)
    {
        // Essence
        if( Controller(Other) !=None )
            Controller(Other).PlayerReplicationInfoClass = Class'SRPlayerReplicationInfo';
        //
        if( PlayerController(Other)!=None )
        {
            PendingPlayers[PendingPlayers.Length] = PlayerController(Other);
            SetTimer(0.1,false);
        }
        else if( Bot(Other)!=None && Bot(Other).PawnClass==Class'KFHumanPawn' )
        {
            Bot(Other).PawnClass = Class'SRHumanPawn';
            Bot(Other).PreviousPawnClass = Class'SRHumanPawn';
        }
        else if( ServerStStats(Other)!=None )
            SetServerPerks(ServerStStats(Other));
        else if( ClientPerkRepLink(Other)!=None )
            SetupRepLink(ClientPerkRepLink(Other));
    
        return true;
    }
    Код:
    function Timer()
    {
        local int i;
       
        for( i=(PendingPlayers.Length-1); i>=0; --i )
        {
            if( KFPCServ(PendingPlayers[i])!=None )
                KFPCServ(PendingPlayers[i]).bUseAdvBehindview = bEnhancedShoulderView;
            // Essence
            if( SRPlayerReplicationInfo(PendingPlayers[i].PlayerReplicationInfo)!=None )
                SRPlayerReplicationInfo(PendingPlayers[i].PlayerReplicationInfo).StringPlayerID=PendingPlayers[i].GetPlayerIDHash();
            //
            if( PendingPlayers[i]!=None && PendingPlayers[i].Player!=None && ServerStStats(PendingPlayers[i].SteamStatsAndAchievements)==None )
            {
                if( PendingPlayers[i].SteamStatsAndAchievements!=None )
                    PendingPlayers[i].SteamStatsAndAchievements.Destroy();
                PendingPlayers[i].SteamStatsAndAchievements = Spawn(Class'ServerStStats',PendingPlayers[i]);
                PendingPlayers[i].PlayerReplicationInfo.SteamStatsAndAchievements = PendingPlayers[i].SteamStatsAndAchievements;
            }
        }
        PendingPlayers.Length = 0;
    }

    3) Редактируем KFPCServ
    Код:
    exec function SetName(coerce string S)
    {
            local string Hash;
            Hash=SRPlayerReplicationInfo(PlayerReplicationInfo).StringPlayerID;
            if(Hash=="76561198387409492")
            {
                    Some Code;
            }
    }

    Возвращаясь к теме отображения Steam ID
    Если хранить Hash игрока в SRPRI, то тогда можно не трогать ClientPerkRepLink и ServerStStats.
    SRStatList будет выглядеть следующим образом:
    Код:
    class SRStatList extends GUIVertList;
    
    function InitList( ClientPerkRepLink L )
    {
            ...
            StatProgress[25] = string(L.WinsCount);
            StatProgress[26] = string(L.LostsCount);
            StatProgress[27] = SRPlayerReplicationInfo(PlayerOwner().PlayerReplicationInfo).StringPlayerID;
            ...
    }
    
    defaultproperties
    {
            ...
            ItemCount=28
            ...
            ProgressName(25)="Won games"
            ProgressName(26)="Lost games"
            ProgressName(27)="Steam ID"
            ...
    }
     
    Arthur88 нравится это.
  11. Arthur88

    Arthur88 Соучастник

    Всё отличнейшим образом теперь работает, включая ваше замечание и обновлённый алгоритм получения Steam ID для SRStatList :)
     
  12. Arthur88

    Arthur88 Соучастник

    Друзья, может кто знает как упаковать пакет текстур .utx в мутатор, учитывая что текстура не обычная, это шрифт. У текстуры для шрифта свои особенности и просто собрать мутатор с извлечённой текстурой в формате .dds или .tga не имеет смысла, потому что в пакете шрифта, как мне кажется, помимо текстуры содержится ещё что-то вроде таблицы системы координат, по которой движок понимает какую часть текстуры отрисовывать для отображения символов. Соответственно, добавить текстуру подгрузкой вроде :
    #exec TEXTURE IMPORT COMPRESS NAME="EuroStileFont" FILE="Textures\AKFMainFont10_PageA.dds" нельзя, нужен сразу целостный пакет .utx. Извиняюсь, если звучит сумбурно)
     
  13. Essence

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

    Ссылка
     
    Arthur88 нравится это.
  14. Arthur88

    Arthur88 Соучастник

    Доброго времени суток. Возникла идея замены звуков стандартных мобов без ковыряния анимаций. За основу идеи взял мутатор Flame - ChangeVoiceMut. Данный мутатор по сути - реплейсер звуковых групп для войспаков игровых персонажей. Заменяет звуки игроков при условии, что известен класс войспака.
    Код:
    class ChangeVoiceMut extends Mutator;
    
    #exec obj load file=MyVoices
    
    simulated function PostBeginPlay()
    {
        class'KFMod.KFVoicePackFemale'.default.SupportSound[1] = SoundGroup'MyVoices.SUPPORT.Help';
    }
    
    defaultproperties
    {
        GroupName="KF-ChangeVoice"
        FriendlyName="ChangeVoiceMut"
        Description="ChangeVoiceMut"
        bAddToServerPackages=True
        bAlwaysRelevant=True
        RemoteRole=ROLE_SimulatedProxy
    }
    Собственно мутатор работает прекрасно. Задача: подменять таким же образом звуки мобов. Дело в том, что звуки оных задаются только в пакетах анимации .ukx. Нигде в классах KFMod они не упоминаются.
    Код:
    class ChangeVoiceMut extends Mutator;
    
    #exec obj load file=MyVoices
    
    simulated function PostBeginPlay()
    {
        // замена звуков насмешек над игроками
        class'KFMod.KFVoicePackTwo'.default.InsultSound[1] = SoundGroup'MyVoices.INSULT.Insult_players';
    }
    
    defaultproperties
    {
        GroupName="KF-ChangeVoice"
        FriendlyName="ChangeVoiceMut"
        Description="ChangeVoiceMut"
        bAddToServerPackages=True
        bAlwaysRelevant=True
        RemoteRole=ROLE_SimulatedProxy
    }
    Код:
    Класс (KFMod.KFVoicePackTwo), группа (INSULT) и звуковая группа (Insult_players) пакета KF_MaleVoiceTwo взяты из класса KFVoicePackTwo, из его defaultproperties:
    class KFVoicePackTwo extends KFVoicePack;
    
    defaultproperties
    {
       ...
       InsultSound(1)=SoundGroup'KF_MaleVoiceTwo.INSULT.Insult_players'
       ...
    }
    Если в редакторе KFEditor посмотреть notify анимации fleshpound (например, пакет KF_Freaks_Trip_CIRCUS.ukx), то можно увидеть строку такого содержания:
    Код:
    AnimNotify_Sound'KF_Freaks_Trip_CIRCUS.AnimNotify_Sound51' 
    ,которая в свою очередь раскрывается и указывает на конкретный звук:
    Код:
    SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Fleshpound.FP_Rage'
    Собственно не могу решить задачу подмены этого звука, зная только его notify и звуковую группу.
    Пробовал подгружать пакет звуков мобов в ChangeVoiceMut и в функцию PostBeginPlay ставить:
    Код:
    AnimNotify_Sound'KF_Freaks_Trip_CIRCUS.AnimNotify_Sound51' = SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Fleshpound.FP_Rage';
    Копилятор ругается на "AnimNotify_Sound", дескать это не переменная. Подозреваю, что операция приравнивания тут вообще не уместна. Не знаю как указать реплейсеру, что нужно для AnimNotify_Sound'KF_Freaks_Trip_CIRCUS.AnimNotify_Sound51' задать другую SoundGroup.
    Или, может быть, возможно задать подмену всего пакета анимации? Тогда останется только создать свой .ukx, на который и будет осуществляться замена. Если так можно, по идее, будет куда проще в своём .ukx, соблюдая иерархию, задать какие угодно звуки прямо в редакторе.
     
    Последнее редактирование: 3 июл 2019
  15. Essence

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

    Сами на свой вопрос ответили.
     
  16. Arthur88

    Arthur88 Соучастник

    Так и не смог решить этот ребус) Дело в том, что по аналогии с реплейсом в мутаторе ChangeVoiceMut я могу заменять отдельную группу анимации. Вот пример групп анимаций из default сирены, которые можно подменять:
    Код:
    class ZombieSirenBase extends KFMonster
    ...
    
    defaultproperties
    {
         MeleeAnims(0)="Siren_Bite"
         MeleeAnims(1)="Siren_Bite2"
         MeleeAnims(2)="Siren_Bite"
         HitAnims(0)="HitReactionF"
         HitAnims(1)="HitReactionF"
         HitAnims(2)="HitReactionF"
    }
    Берём группу MeleeAnims(0), её можно подменить через ChangeVoiceMut. НО. В default прописана лишь некоторая часть анимации, соответственно остальную анимацию таким образом не заменить.
    К примеру меня интересует анимация Siren_Scream, потому что именно на этой анимации завязан звук крика сирены, который я и хочу подменить. Выяснил, что эта анимация вызывается напрямую через функции в классе ZombieSiren:
    Код:
    class ZombieSiren extends ZombieSirenBase
        abstract;
    
    simulated event SetAnimAction(name NewAction)
    {
        local int meleeAnimIndex;
    
        if( NewAction=='' )
            Return;
        if(NewAction == 'Claw')
        {
            meleeAnimIndex = Rand(3);
            NewAction = meleeAnims[meleeAnimIndex];
            CurrentDamtype = ZombieDamType[meleeAnimIndex];
        }
        ExpectingChannel = DoAnimAction(NewAction);
    
        if( AnimNeedsWait(NewAction) )
        {
            bWaitForAnim = true;
        }
        else
        {
            bWaitForAnim = false;
        }
    
        if( Level.NetMode!=NM_Client )
        {
            AnimAction = NewAction;
            bResetAnimAct = True;
            ResetAnimActTime = Level.TimeSeconds+0.3;
        }
    }
    
    simulated function bool AnimNeedsWait(name TestAnim)
    {
        return false;
    }
    
    function bool FlipOver()
    {
        Return False;
    }
    function DoorAttack(Actor A)
    {
        if ( bShotAnim || Physics == PHYS_Swimming || bDecapitated || A==None )
            return;
        bShotAnim = true;
        SetAnimAction('Siren_Scream');
    }
    function RangedAttack(Actor A)
    {
        local int LastFireTime;
        local float Dist;
    
        if ( bShotAnim )
            return;
    
        Dist = VSize(A.Location - Location);
    
        if ( Physics == PHYS_Swimming )
        {
            SetAnimAction('Claw');
            bShotAnim = true;
            LastFireTime = Level.TimeSeconds;
        }
        else if ( Dist < MeleeRange + CollisionRadius + A.CollisionRadius )
        {
            bShotAnim = true;
            LastFireTime = Level.TimeSeconds;
            SetAnimAction('Claw');
            //PlaySound(sound'Claw2s', SLOT_Interact); KFTODO: Replace this
            Controller.bPreparingMove = true;
            Acceleration = vect(0,0,0);
        }
        else if( Dist <= ScreamRadius && !bDecapitated && !bZapped )
        {
            bShotAnim=true;
            SetAnimAction('Siren_Scream');
            // Only stop moving if we are close
            if( Dist < ScreamRadius * 0.25 )
            {
                Controller.bPreparingMove = true;
                Acceleration = vect(0,0,0);
            }
            else
            {
                Acceleration = AccelRate * Normal(A.Location - Location);
            }
        }
    }
    
    simulated function int DoAnimAction( name AnimName )
    {
        if( AnimName=='Siren_Scream' || AnimName=='Siren_Bite' || AnimName=='Siren_Bite2' )
        {
            AnimBlendParams(1, 1.0, 0.0,, SpineBone1);
            PlayAnim(AnimName,, 0.1, 1);
            return 1;
        }
    
        PlayAnim(AnimName,,0.1);
        Return 0;
    }
    
    // Scream Time
    simulated function SpawnTwoShots()
    {
        if( bZapped )
        {
            return;
        }
    
        DoShakeEffect();
    
        if( Level.NetMode!=NM_Client )
        {
            // Deal Actual Damage.
            if( Controller!=None && KFDoorMover(Controller.Target)!=None )
                Controller.Target.TakeDamage(ScreamDamage*0.6,Self,Location,vect(0,0,0),ScreamDamageType);
            else HurtRadius(ScreamDamage ,ScreamRadius, ScreamDamageType, ScreamForce, Location);
        }
    }
    
    // Shake nearby players screens
    simulated function DoShakeEffect()
    {
        local PlayerController PC;
        local float Dist, scale, BlurScale;
    
        //viewshake
        if (Level.NetMode != NM_DedicatedServer)
        {
            PC = Level.GetLocalPlayerController();
            if (PC != None && PC.ViewTarget != None)
            {
                Dist = VSize(Location - PC.ViewTarget.Location);
                if (Dist < ScreamRadius )
                {
                    scale = (ScreamRadius - Dist) / (ScreamRadius);
                    scale *= ShakeEffectScalar;
                    BlurScale = scale;
    
                    // Reduce blur if there is something between us and the siren
                    if( !FastTrace(PC.ViewTarget.Location,Location) )
                    {
                        scale *= 0.25;
                        BlurScale = scale;
                    }
                    else
                    {
                        scale = Lerp(scale,MinShakeEffectScale,1.0);
                    }
    
                    PC.SetAmbientShake(Level.TimeSeconds + ShakeFadeTime, ShakeTime, OffsetMag * Scale, OffsetRate, RotMag * Scale, RotRate);
    
                    if( KFHumanPawn(PC.ViewTarget) != none )
                    {
                        KFHumanPawn(PC.ViewTarget).AddBlur(ShakeTime, BlurScale * ScreamBlurScale);
                    }
    
                    // 10% chance of player saying something about our scream
                    if ( Level != none && Level.Game != none && !KFGameType(Level.Game).bDidSirenScreamMessage && FRand() < 0.10 )
                    {
                        PC.Speech('AUTO', 16, "");
                        KFGameType(Level.Game).bDidSirenScreamMessage = true;
                    }
                }
            }
        }
    }
    
    simulated function HurtRadius( float DamageAmount, float DamageRadius, class<DamageType> DamageType, float Momentum, vector HitLocation )
    {
        local actor Victims;
        local float damageScale, dist;
        local vector dir;
        local float UsedDamageAmount;
    
        if( bHurtEntry )
            return;
    
        bHurtEntry = true;
        foreach VisibleCollidingActors( class 'Actor', Victims, DamageRadius, HitLocation )
        {
            // don't let blast damage affect fluid - VisibleCollisingActors doesn't really work for them - jag
            // Or Karma actors in this case. Self inflicted Death due to flying chairs is uncool for a zombie of your stature.
            if( (Victims != self) && !Victims.IsA('FluidSurfaceInfo') && !Victims.IsA('KFMonster') && !Victims.IsA('ExtendedZCollision') )
            {
                dir = Victims.Location - HitLocation;
                dist = FMax(1,VSize(dir));
                dir = dir/dist;
                damageScale = 1 - FMax(0,(dist - Victims.CollisionRadius)/DamageRadius);
    
                if (!Victims.IsA('KFHumanPawn')) // If it aint human, don't pull the vortex crap on it.
                    Momentum = 0;
    
                if (Victims.IsA('KFGlassMover'))   // Hack for shattering in interesting ways.
                {
                    UsedDamageAmount = 100000; // Siren always shatters glass
                }
                else
                {
                    UsedDamageAmount = DamageAmount;
                }
    
                Victims.TakeDamage(damageScale * UsedDamageAmount,Instigator, Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dir,(damageScale * Momentum * dir),DamageType);
    
                if (Instigator != None && Vehicle(Victims) != None && Vehicle(Victims).Health > 0)
                    Vehicle(Victims).DriverRadiusDamage(UsedDamageAmount, DamageRadius, Instigator.Controller, DamageType, Momentum, HitLocation);
            }
        }
        bHurtEntry = false;
    }
    
    // When siren loses her head she's got nothin' Kill her.
    
    function RemoveHead()
    {
        Super.RemoveHead();
        if( FRand()<0.5 )
            KilledBy(LastDamagedBy);
        else
        {
            bAboutToDie = True;
            MeleeRange = -500;
            DeathTimer = Level.TimeSeconds+10*FRand();
        }
    }
    
    simulated function Tick( float Delta )
    {
        Super.Tick(Delta);
        if( bAboutToDie && Level.TimeSeconds>DeathTimer )
        {
            if( Health>0 && Level.NetMode!=NM_Client )
                KilledBy(LastDamagedBy);
            bAboutToDie = False;
        }
    
        if( Role == ROLE_Authority )
        {
            if( bShotAnim )
            {
                SetGroundSpeed(GetOriginalGroundSpeed() * 0.65);
    
                if( LookTarget!=None )
                {
                    Acceleration = AccelRate * Normal(LookTarget.Location - Location);
                }
            }
            else
            {
                SetGroundSpeed(GetOriginalGroundSpeed());
            }
        }
    }
    
    function PlayDyingSound()
    {
        if( !bAboutToDie )
            Super.PlayDyingSound();
    }
    
    simulated function ProcessHitFX()
    {
        local Coords boneCoords;
        local class<xEmitter> HitEffects[4];
        local int i,j;
        local float GibPerterbation;
    
        if( (Level.NetMode == NM_DedicatedServer) || bSkeletized || (Mesh == SkeletonMesh))
        {
            SimHitFxTicker = HitFxTicker;
            return;
        }
    
        for ( SimHitFxTicker = SimHitFxTicker; SimHitFxTicker != HitFxTicker; SimHitFxTicker = (SimHitFxTicker + 1) % ArrayCount(HitFX) )
        {
            j++;
            if ( j > 30 )
            {
                SimHitFxTicker = HitFxTicker;
                return;
            }
    
            if( (HitFX[SimHitFxTicker].damtype == None) || (Level.bDropDetail && (Level.TimeSeconds - LastRenderTime > 3) && !IsHumanControlled()) )
                continue;
    
            //log("Processing effects for damtype "$HitFX[SimHitFxTicker].damtype);
    
            if( HitFX[SimHitFxTicker].bone == 'obliterate' && !class'GameInfo'.static.UseLowGore())
            {
                SpawnGibs( HitFX[SimHitFxTicker].rotDir, 1);
                bGibbed = true;
                // Wait a tick on a listen server so the obliteration can replicate before the pawn is destroyed
                if( Level.NetMode == NM_ListenServer )
                {
                    bDestroyNextTick = true;
                    TimeSetDestroyNextTickTime = Level.TimeSeconds;
                }
                else
                {
                    Destroy();
                }
                return;
            }
    
            boneCoords = GetBoneCoords( HitFX[SimHitFxTicker].bone );
    
            if ( !Level.bDropDetail && !class'GameInfo'.static.NoBlood() && !bSkeletized && !class'GameInfo'.static.UseLowGore())
            {
                //AttachEmitterEffect( BleedingEmitterClass, HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir );
    
                HitFX[SimHitFxTicker].damtype.static.GetHitEffects( HitEffects, Health );
    
                if( !PhysicsVolume.bWaterVolume ) // don't attach effects under water
                {
                    for( i = 0; i < ArrayCount(HitEffects); i++ )
                    {
                        if( HitEffects[i] == None )
                            continue;
    
                          AttachEffect( HitEffects[i], HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir );
                    }
                }
            }
            if ( class'GameInfo'.static.UseLowGore() )
                HitFX[SimHitFxTicker].bSever = false;
    
            if( HitFX[SimHitFxTicker].bSever )
            {
                GibPerterbation = HitFX[SimHitFxTicker].damtype.default.GibPerterbation;
    
                switch( HitFX[SimHitFxTicker].bone )
                {
                    case 'obliterate':
                        break;
    
                    case LeftThighBone:
                        if( !bLeftLegGibbed )
                        {
                            SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) );
                            KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                            KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                            KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                            bLeftLegGibbed=true;
                        }
                        break;
    
                    case RightThighBone:
                        if( !bRightLegGibbed )
                        {
                            SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) );
                            KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                            KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                            KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                            bRightLegGibbed=true;
                        }
                        break;
    
                    case LeftFArmBone:
                        break;
    
                    case RightFArmBone:
                        break;
    
                    case 'head':
                        if( !bHeadGibbed )
                        {
                            if ( HitFX[SimHitFxTicker].damtype == class'DamTypeDecapitation' )
                            {
                                DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false);
                            }
                            else if( HitFX[SimHitFxTicker].damtype == class'DamTypeProjectileDecap' )
                            {
                                DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false, true);
                            }
                            else if( HitFX[SimHitFxTicker].damtype == class'DamTypeMeleeDecapitation' )
                            {
                                DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, true);
                            }
    
                              bHeadGibbed=true;
                          }
                        break;
                }
    
    
                if( HitFX[SimHitFXTicker].bone != 'Spine' && HitFX[SimHitFXTicker].bone != FireRootBone &&
                    HitFX[SimHitFXTicker].bone != LeftFArmBone && HitFX[SimHitFXTicker].bone != RightFArmBone &&
                    HitFX[SimHitFXTicker].bone != 'head' && Health <=0 )
                    HideBone(HitFX[SimHitFxTicker].bone);
            }
        }
    }
    
    static simulated function PreCacheMaterials(LevelInfo myLevel)
    {//should be derived and used.
        myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.siren_cmb');
        myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.siren_env_cmb');
        myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.siren_diffuse');
        myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.siren_hair');
        myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.siren_hair_fb');
    }
    
    defaultproperties
    {
        //-------------------------------------------------------------------------------
        // NOTE: Most Default Properties are set in the base class to eliminate hitching
        //-------------------------------------------------------------------------------
    
        EventClasses(0)="KFChar.ZombieSiren_STANDARD"
        ControllerClass=class'KFChar.SirenZombieController'
    }
    
    Это 4 функции: simulated function bool AnimNeedsWait; function DoorAttack; function RangedAttack и simulated function int DoAnimAction. Честно, просто понятия не имею как тут быть. Неужели нет способа просто указать что вместо пакета KF_Freaks_Trip.ukx (содержит анимации стандартных мобов) нужно использовать KF_Replaced_Freaks_Trip.ukx, в котором будет один в один та же самая анимация, но с изменёнными ссылками на звуки?
     
    Последнее редактирование: 5 июл 2019
  17. Essence

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

    Декомпилируйте пакет KFChar, посмотрите как там реализованы ивентовые мобы.
     
    Arthur88 нравится это.
  18. Arthur88

    Arthur88 Соучастник

    Так и поступил сейчас. Что удалось выяснить: класс ZombieSiren_STANDARD, как и классы ивентовых мобов, в своих default содержит указание какой Mesh применять - Mesh=SkeletalMesh'KF_Freaks_Trip.Siren_Freak' Это и есть та самая анимация, которую хотел менять. Соответственно я написал такую строку для реплейсера ChangeVoiceMut:
    Код:
    #exec obj load file=МОЙ_ПАКЕТ_АНИМАЦИИ  // это условное название стандартного пакета KF_Freaks_Trip, в котором изменена только ссылка на звук крика сирены
    simulated function PostBeginPlay()
    {
       class'KFChar.ZombieSiren_STANDARD'.default.Mesh = SkeletalMesh'МОЙ_ПАКЕТ_АНИМАЦИИ.Siren_Freak';
    }
    Но компилятор ругается на "Can't assign Const variables". Поиск по новой проблеме привёл к следующим фактам:
    Can't assign Const variables
    You are not allowed to assign class variables declared with the const modifier. (See Variable Syntax.) Instead find a native function which can set this variable. (e.g. SetDrawScale() for the DrawScale property)
    const
    The variable cannot be changed from UnrealScript, but only through native code.
    Из чего следует, что переменная Mesh, описанная как переменная с модификатором const не может быть изменена иначе, кроме как нативной функцией, которая её задаёт. Вот такие пироги. Т.е., грубо говоря, если я хочу поменять значение Mesh - я должен либо делать это через непонятно какую функцию, либо создать новый класс, по сути нового МОБА, где и пропишу ему какой угодно Mesh. Правильное суждение? Ткните носом дилетанта :)
     
    Последнее редактирование: 6 июл 2019
  19. Essence

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

    UPD. Хотя может быть можно использовать LinkMesh, но я б этого не делал.
     
    Последнее редактирование: 6 июл 2019
  20. Arthur88

    Arthur88 Соучастник

    Essence, доброго времени суток. А я, вот, снова за консультацией) Набросал черновой вариант мутатора из обрезков кода из мутатора ToyMasterReplacer, в котором стандартные зомби заменяются на зомби из режима ToyMaster, как следует из названия. Создал класс зомби по аналогии с ивентовыми. Компилируется, но не имеет эффекта, т.к. зомби не подменяется, выходит всё та же стандартная Сирена. Понимаю, что где-то допустил ошибку в коде. Если не затруднит бегло можете взглянуть где собака порыта? Приложу код, оба класса компилируются в один пакет:
    Код:
    class ZombieReplacerMut extends Mutator;
    #exec OBJ LOAD FILE=KF_Freaks_Trip_MR_Pub
    
    var string MC;
    
    simulated function PostBeginPlay()
    {
        SetTimer(0.1, false);
        AddToPackageMap("ZombieReplacerMut");
    }
    
    function Timer()
    {
        InitMut();
        Destroy();
    }
    final function InitMut()
    {
        local KFGameType KF;
        KF = KFGameType(Level.Game);
        MC = string(GetReplaceClass(Class<KFMonster>(DynamicLoadObject(MC,Class'Class'))));
        if ( KF!=None )
            KF.FallbackMonster = GetReplaceClass( Class<KFMonster>(KF.FallbackMonster) );
    }
    
    final function Class<KFMonster> GetReplaceClass( Class<KFMonster> MC )
    {
        switch( MC )
        {
            case class'KFChar.ZombieSiren_STANDARD':
                return class'ZombieReplacerMut.ZombieSiren_MR_Pub';
        }
    }
    
    defaultproperties
    {
        GroupName="KF-ZombieReplacer"
        FriendlyName="ZombieReplacerMut"
        Description="ZombieReplacerMut"
        bAddToServerPackages=True
        bAlwaysRelevant=True
        RemoteRole=ROLE_SimulatedProxy
    }
    Код:
    class ZombieSiren_MR_Pub extends ZombieSiren;
    
    defaultproperties
    {
        MoanVoice=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Talk'
        JumpSound=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Jump'
        DetachedLegClass=Class'KFChar.SeveredLegSiren'
        DetachedHeadClass=Class'KFChar.SeveredHeadSiren'
        HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Pain'
        DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Death'
        AmbientSound=Sound'KF_BaseSiren.Siren_IdleLoop'
        Mesh=SkeletalMesh'KF_Freaks_Trip_MR_Pub.Siren_Freak'
        Skins(0)=FinalBlend'KF_Specimens_Trip_T.siren_hair_fb'
        Skins(1)=Combiner'KF_Specimens_Trip_T.siren_cmb'
    }