[Цикл статей] Server Perks и его редактирование

Тема в разделе "Кодинг", создана пользователем 2/5, 4 авг 2016.

Статус темы:
Закрыта.
  1. 2/5

    2/5 Соучастник

    Мутатор Server Perks стал очень популярен среди мододелов и админов серверов. Без него уже нельзя представить модифицированный сервер. Тема крайне обширная, и по ней написано много всего. Но всё-таки еще и многое не досказано, и вопросы по редактированию этого мутатора бесконечны.
    Начнём :)

     
    Последнее редактирование модератором: 22 дек 2019
    kolya_abv нравится это.
  2. 2/5

    2/5 Соучастник

    Переименование ServerPerks
    Автор: Flame
    Редактор и публикация: НАТАХА
    ServerPerks есть смысл переименовывать по нескольким причинам:
    1. Чтобы не было конфликтов со стандартным ServerPerks, которые могут возникать при подключении к другому серверу или если у клиента в System уже лежит аналогичный пакет.
    2. Чтобы выделиться или привести название пакетов SP к единому стилю сервера

    Рассматривать будем SP 7.50
    Допустим мы хотим добавить префикс new перед SP, то есть получить пакеты newServerPerks, newServerPerksP, newServerPerksMut.
    Переименовываем все папки с классами и начинаем править сами классы.

    Вначале поправим самый трудоёмкий в плане объёма пакет - ServerPerks.u
    Класс GUIHTMLTextBox
    Код:
    defaultproperties
    {
       ...
       MyScrollBar=GUIVertScrollBar'newServerPerks.GUIHTMLTextBox.TheScrollbar'
       ...
       ToolTip=GUIToolTip'newServerPerks.GUIHTMLTextBox.GUIListBoxBaseToolTip'
       ...
    }
    
    Класс KFPCServ
    Код:
    defaultproperties
    {
       LobbyMenuClassString="newServerPerks.SRLobbyMenu"
       PawnClass=Class'newServerPerks.SRHumanPawn'
    }
    
    Класс SRGUIBuyMenu
    Код:
    defaultproperties
    {
       ...
       QuickPerkSelect=SRKFQuickPerkSelect'newServerPerks.SRGUIBuyMenu.QS'
       ...
       BuyMenuFilter=SRBuyMenuFilter'newServerPerks.SRGUIBuyMenu.SRFilter'
       ...
       WeightBar=SRWeightBar'newServerPerks.SRGUIBuyMenu.WeightB'
       ...
    }
    
    Класс SRInvasionLoginMenu
    Код:
    defaultproperties
    {
       ...
       b_Settings=GUIButton'newServerPerks.SRInvasionLoginMenu.SettingsButton'
       ...
       b_Browser=GUIButton'newServerPerks.SRInvasionLoginMenu.BrowserButton'
       ...
       b_Quit=GUIButton'newServerPerks.SRInvasionLoginMenu.QuitGameButton'
       ...
       b_Favs=GUIButton'newServerPerks.SRInvasionLoginMenu.FavoritesButton'
       ...
       b_Leave=GUIButton'newServerPerks.SRInvasionLoginMenu.LeaveMatchButton'
       ...
       b_MapVote=GUIButton'newServerPerks.SRInvasionLoginMenu.MapVotingButton'
       ...
       b_KickVote=GUIButton'newServerPerks.SRInvasionLoginMenu.KickVotingButton'
       ...
       b_MatchSetup=GUIButton'newServerPerks.SRInvasionLoginMenu.MatchSetupButton'
       ...
       b_Spec=GUIButton'newServerPerks.SRInvasionLoginMenu.SpectateButton'
       ...
       b_Profile=GUIButton'newServerPerks.SRInvasionLoginMenu.ProfileButton'
    }
    
    Класс SRKFTab_BuyMenu
    Код:
    defaultproperties
    {
       ...
       InvSelect=SRKFBuyMenuInvListBox'newServerPerks.SRKFTab_BuyMenu.InventoryBox'
       ...
       ItemInfo=SRGUIBuyWeaponInfoPanel'newServerPerks.SRKFTab_BuyMenu.ItemInf'
       ...
       SaleSelect=SRBuyMenuSaleListBox'newServerPerks.SRKFTab_BuyMenu.SaleBox'
       ...
    }
    
    Класс SRKFTab_Perks
    Код:
    defaultproperties
    {
       ...
       lb_PerkSelect=SRPerkSelectListBox'newServerPerks.SRKFTab_Perks.PerkSelectList'
       ...
       lb_PerkProgress=SRPerkProgressListBox'newServerPerks.SRKFTab_Perks.PerkProgressList'
       ...
    }
    
    Класс SRLobbyFooter
    Код:
    defaultproperties
    {
       ...
       b_Perks=GUIButton'newServerPerks.SRLobbyFooter.PerksButton'
       ...
    }
    
    Класс SRLobbyMenu
    Код:
    defaultproperties
    {
       ...
       tb_ServerMOTD=GUIScrollTextBox'newServerPerks.SRLobbyMenu.MOTDScroll'
       ...
       t_ChatBox=SRLobbyChat'newServerPerks.SRLobbyMenu.ChatBox'
       ...
       t_Footer=SRLobbyFooter'newServerPerks.SRLobbyMenu.BuyFooter'
       ...
    }
    
    Класс SRModelSelect
    Код:
    defaultproperties
    {
       ...
       co_Race=moComboBox'newServerPerks.SRModelSelect.CharRace'
       ...
    }
    
    Класс SRProfilePage
    Код:
    defaultproperties
    {
       ...
       ProfilePanel=SRTab_Profile'newServerPerks.SRProfilePage.Panel'
       ...
    }
    
    Класс SRTab_MidGameHelp
    Код:
    defaultproperties
    {
       ...
       sb_GameDesc=AltSectionBackground'newServerPerks.SRTab_MidGameHelp.sbGameDesc'
       ...
       sb_Hints=AltSectionBackground'newServerPerks.SRTab_MidGameHelp.sbHints'
       ...
       GameDescriptionBox=GUIScrollTextBox'newServerPerks.SRTab_MidGameHelp.InfoText'
       ...
       HintsBox=GUIScrollTextBox'newServerPerks.SRTab_MidGameHelp.HintText'
       ...
       HintCountLabel=GUILabel'newServerPerks.SRTab_MidGameHelp.HintCount'
       ...
       PrevHintButton=GUIButton'newServerPerks.SRTab_MidGameHelp.PrevHint'
       ...
       NextHintButton=GUIButton'newServerPerks.SRTab_MidGameHelp.NextHint'
       ...
    }
    
    Класс SRTab_MidGamePerks
    Код:
    defaultproperties
    {
       ...
       i_BGPerks=GUISectionBackground'newServerPerks.SRTab_MidGamePerks.BGPerks'
       ...
       lb_PerkSelect=SRPerkSelectListBox'newServerPerks.SRTab_MidGamePerks.PerkSelectList'
       ...
       i_BGPerkEffects=GUISectionBackground'newServerPerks.SRTab_MidGamePerks.BGPerkEffects'
       ...
       lb_PerkEffects=GUIScrollTextBox'newServerPerks.SRTab_MidGamePerks.PerkEffectsScroll'
       ...
       i_BGPerkNextLevel=GUISectionBackground'newServerPerks.SRTab_MidGamePerks.BGPerksNextLevel'
       ...
       lb_PerkProgress=SRPerkProgressListBox'newServerPerks.SRTab_MidGamePerks.PerkProgressList'
       ...
       b_Save=GUIButton'newServerPerks.SRTab_MidGamePerks.SaveButton'
       ...
    }
    
    Класс SRTab_MidGameStats
    Код:
    defaultproperties
    {
       ...
       i_BGPerks=GUISectionBackground'newServerPerks.SRTab_MidGameStats.BGPerks'
       ...
       lb_PerkSelect=SRStatListBox'newServerPerks.SRTab_MidGameStats.StatSelectList'
       ...
    }
    
    Класс SRTab_MidGameVoiceChat
    Код:
    defaultproperties
    {
       ...
       sb_Players=AltSectionBackground'newServerPerks.SRTab_MidGameVoiceChat.PlayersBackground'
       ...
       sb_Specs=AltSectionBackground'newServerPerks.SRTab_MidGameVoiceChat.SpecBackground'
       ...
       sb_Options=AltSectionBackground'newServerPerks.SRTab_MidGameVoiceChat.OptionBackground'
       ...
       lb_Players=GUIListBox'newServerPerks.SRTab_MidGameVoiceChat.PlayersList'
       ...
       lb_Specs=GUIListBox'newServerPerks.SRTab_MidGameVoiceChat.SpecList'
       ...
       ch_NoVoiceChat=moCheckBox'newServerPerks.SRTab_MidGameVoiceChat.NoVoiceChat'
       ...
       ch_NoSpeech=moCheckBox'newServerPerks.SRTab_MidGameVoiceChat.NOSPEECH'
       ...
       ch_NoText=moCheckBox'newServerPerks.SRTab_MidGameVoiceChat.NOTEXT'
       ...
       ch_Ban=moCheckBox'newServerPerks.SRTab_MidGameVoiceChat.BanPlayer'
       ...
    }
    
    Класс SRTab_Profile
    Код:
    defaultproperties
    {
       ...
       lb_PerkSelect=SRPerkSelectListBox'newServerPerks.SRTab_Profile.PerkSelectList'
       ...
       lb_PerkProgress=SRPerkProgressListBox'newServerPerks.SRTab_Profile.PerkProgressList'
       ...
    }
    
    Класс SRTab_ServerNews
    Код:
    defaultproperties
    {
       ...
       b_Prev=GUIButton'newServerPerks.SRTab_ServerNews.PreviousButton'
       ...
       b_Next=GUIButton'newServerPerks.SRTab_ServerNews.NextButton'
       ...
       b_Reload=GUIButton'newServerPerks.SRTab_ServerNews.ReloadButton'
       ...
       i_BGSec=GUISectionBackground'newServerPerks.SRTab_ServerNews.BGSec'
       ...
       HTMLText=GUIHTMLTextBox'newServerPerks.SRTab_ServerNews.HTMLInfoText'
       ...
    }
    
    Класс UI_Window
    Код:
    defaultproperties
    {
       ...
       i_FrameBG=FloatingImage'newServerPerks.UI_Window.FloatingFrameBg'
       ...
    }
    

    Замечание 1.
    Надеюсь, что вы всё же догадаетесь делать автозамену по всем файлам в стиле "ServerPerks." на "newServerPerks."
    Хотя можете и руками. Я же вот не поленился и всё сейчас проделал руками)

    Замечание 2.
    Вовсе не обязательно в ссылках такого рода указывать полный путь к объекту.
    Т.е. вместо
    Код:
    i_FrameBG=FloatingImage'newServerPerks.UI_Window.FloatingFrameBg'
    
    Можно написать
    Код:
    i_FrameBG=FloatingFrameBg
    
    и вообще убрать какую-либо зависимость от текущего пакета

    Теперь поправим пакет ServerPerksMut
    Класс ServerPerksMut
    Код:
    Class ServerPerksMut extends Mutator Config(newServerPerks);
    ...
    defaultproperties
    {
        ...
        Perks(0)="newServerPerksP.SRVetSupportSpec"
        Perks(1)="newServerPerksP.SRVetBerserker"
        Perks(2)="newServerPerksP.SRVetCommando"
        Perks(3)="newServerPerksP.SRVetFieldMedic"
        Perks(4)="newServerPerksP.SRVetFirebug"
        Perks(5)="newServerPerksP.SRVetSharpshooter"
        Perks(6)="newServerPerksP.SRVetDemolitions"
        ...
        SmileyTags(0)=(iconTexture="newServerPerks.I_Mad",IconTag=">:(")
        SmileyTags(1)=(iconTexture="newServerPerks.I_Frown",IconTag=":(")
        SmileyTags(2)=(iconTexture="newServerPerks.I_GreenLickB",IconTag=":)")
        SmileyTags(3)=(iconTexture="newServerPerks.I_Tongue",IconTag=":P",bCaseInsensitive=True)
        SmileyTags(4)=(iconTexture="newServerPerks.I_GreenLick",IconTag=":d")
        SmileyTags(5)=(iconTexture="newServerPerks.I_BigGrin",IconTag=":D")
        SmileyTags(6)=(iconTexture="newServerPerks.I_Indiffe",IconTag=":|")
        SmileyTags(7)=(iconTexture="newServerPerks.I_Ohwell",IconTag=":/")
        SmileyTags(8)=(iconTexture="newServerPerks.I_RedFace",IconTag=":*")
        SmileyTags(9)=(iconTexture="newServerPerks.I_RedFace",IconTag=":-*")
        SmileyTags(10)=(iconTexture="newServerPerks.I_Ban",IconTag="Ban?",bCaseInsensitive=True)
        SmileyTags(11)=(iconTexture="newServerPerks.I_Cool",IconTag="B)")
        SmileyTags(12)=(iconTexture="newServerPerks.I_Hmm",IconTag="Hmm")
        SmileyTags(13)=(iconTexture="newServerPerks.I_Scream",IconTag="XD")
        SmileyTags(14)=(iconTexture="newServerPerks.I_Spam",IconTag="SPAM")
        ...
        SRScoreboardType=Class'newServerPerks.SRScoreBoard'
        SRHudType=Class'newServerPerks.SRHUDKillingFloor'
        SRMenuType=Class'newServerPerks.SRInvasionLoginMenu'
        SRControllerType=Class'newServerPerks.KFPCServ'
        ...
    }
    
    Класс StatsObject
    Код:
    Class StatsObject extends Object
       PerObjectConfig
       Config(newServerPerksStat);
    ...
    
    В принципе StatsObject можно было бы и не трогать, но пусть и файл статистики будет по-новому назван

    Замечание 3.
    Не забывайте поправить сам ini файл.
    1. Переименуйте его в newServerPerks.ini
    2. Поправьте заголовок на [newServerPerksMut.ServerPerksMut]
    3. Поправьте содержание ini файла, по аналогии с тем, что правилось в defaultproperties в ServerPerksMut

    Ну и наконец поправим пакет ServerPerksP
    Сюрприз. Тут нечего править)

    Ниже ссылки на получившийся результат
    Ссылка 1 или Ссылка 2

    Замечание 4.
    В принципе, вы могли бы и не мучиться с ServerPerks.u
    Одна скромная команда
    Код:
    UCC.exe DataRip ServerPerks.u newServerPerks.u
    
    И вы получите пакет newServerPerks.u с уже поправленными связями.
    Почему же сразу я не написал про этот способ?
    А всё просто - UCC DataRip правит только ссылки на классы, но ведь многое в коде задано с помощью строк, например в KFPCServ результатом действия этой команды будет:
    Код:
    defaultproperties
    {
        LobbyMenuClassString="ServerPerks.SRLobbyMenu"
        PawnClass=Class'newServerPerks.SRHumanPawn'
    }
    
    И в итоге получится нерабочий (в игровом плане) u пакет. Придётся ползать - искать какие строки надо заменить. Хотя возможно это чуть и менее трудоёмкий процесс, но зато больше шансов запутаться)
     
    Последнее редактирование модератором: 26 дек 2016
    parollpasse, kolya_abv и STaJIKeR нравится это.
  3. 2/5

    2/5 Соучастник

    Скрытый админ
    Автор: Flame
    Редактор и публикация: НАТАХА
    Бывают ситуации, когда админу хочется незаметно находиться на сервере и следить за возможными нарушениями. Опишем несколько изменений в классах ServerPerks, которые в этом помогут.

    1. Незаметное подключение в качестве зрителя.
    Сообщение о подключении зрителя к игре вызывается в функции контроллера JoinedAsSpectatorOnly.
    Нам это сообщение не нужно, по крайней мере, когда к игре подключается админ. Скопируем эту функцию из класса KFPlayerController в класс KFPCServ и слегка её поправим.
    Код:
    function JoinedAsSpectatorOnly()
    {
        if ( Pawn != None )
            Pawn.Died(self, class'DamageType', Pawn.Location);
        if ( PlayerReplicationInfo.Team != None )
            PlayerReplicationInfo.Team.RemoveFromTeam(self);
        PlayerReplicationInfo.Team = None;
        ServerSpectate();
        //BroadcastLocalizedMessage(Level.Game.GameMessageClass, 14, PlayerReplicationInfo);
        ClientBecameSpectator();
    }
    
    Замечание: Если вы хотите, чтобы это сообщение пропало только для заходящих админов, а не для всех игроков, то пропишите руками нужные ID. Например, так
    Код:
    function JoinedAsSpectatorOnly()
    {
        local string Hash;
        if ( Pawn != None )
            Pawn.Died(self, class'DamageType', Pawn.Location);
        if ( PlayerReplicationInfo.Team != None )
            PlayerReplicationInfo.Team.RemoveFromTeam(self);
        PlayerReplicationInfo.Team = None;
        ServerSpectate();
        Hash=GetPlayerIDHash();
        if(Hash!="76561198051378449" && Hash!="76561198051378448")
            BroadcastLocalizedMessage(Level.Game.GameMessageClass, 14, PlayerReplicationInfo);
        ClientBecameSpectator();
    }
    
    Теперь для игроков с ID=76561198051378448, 76561198051378449 сообщение писаться не будет.
    Если кто-то очень хочет вывод списка ID в ini файл - пишите, допишем потом может подпункт к статье.

    2. Прячем надпись про то, что админ залогинился или разлогинился.
    При стандартной попытке залогиниться вылезает вполне однозначное сообщение. Нам необходимо от него избавиться. Если у вас уже используется Admin+ или AdminControl и стандартный класс Admin заменён на них, то правим код этих мутаторов. В противном случае создаём новый класс SRAdmin и наследуем его от Admin.
    Нам надо поправить функции DoLogin и DoLogout, убрать из них рассылку сообщений всем игрокам.
    Код:
    class SRAdmin extends Admin;
    
    function DoLogin( string Username, string Password )
    {
        if (Level.Game.AccessControl.AdminLogin(Outer, Username, Password))
            bAdmin = true;
    }
    
    function DoLogout()
    {
        local bool bWasSilent;
        bWasSilent = Outer.PlayerReplicationInfo.bSilentAdmin;
        if (Level.Game.AccessControl.AdminLogout(Outer))
            bAdmin = false;
    }
    

    Теперь надо заменить класс Admin на наш новый SRAdmin
    Опять же сделаем это в ServerPerksMut
    Код:
    Class ServerPerksMut extends Mutator
    Config(ServerPerks);
    ...
    function PostBeginPlay()
    {
        ...
        KFGT = KFGameType(Level.Game);
        Level.Game.AccessControl.AdminClass = class'SRAdmin';
        ...
    }
    ...
    

    3. Теперь уберём данные о наличии админа в зрителях в информации о сервере.
    Зачем нам ситуация, когда пользователь не поленился и вышел в меню выбора серверов, чтобы поглядеть, кто же сейчас на сервере?
    Поправим функцию GetServerPlayers и в ней не позволим заносить информацию о зрителях-админах в отображаемую информацию. Первоначально эта функция используется в GameType игры, а потом эта функция опрашивает другие мутаторы по поводу каких-то добавлений к базовой информации. Воспользуемся этим - добавим эту функцию в ServerPerksMut и перепишем заполненине массива игроков.
    Код:
    function GetServerPlayers( out GameInfo.ServerResponseLine ServerState )
    {
        local Controller C;
        local PlayerReplicationInfo PRI;
        local int i,j,N;
        local array<string> Ghosts;
        i=0;
        for( C=Level.ControllerList;C!=None;C=C.NextController )
        {
            PRI = C.PlayerReplicationInfo;
            if(PRI != None && PRI.bOnlySpectator && PRI.bAdmin)
            {
                Ghosts[i]=PRI.PlayerName;
                i++;
            }
        }
        for(i=0;i<Ghosts.Length;i++)
        {
            N = ServerState.PlayerInfo.Length;
            for(j=0;j<N;j++)
            {
                if(ServerState.PlayerInfo[j].PlayerName ~= Ghosts[i])
                {
                    if(N==1)
                        ServerState.PlayerInfo.Length = 0;
                    else
                        ServerState.PlayerInfo.Remove(j, 1);
                    break;
                }
            }
        }
    }
    

    4. Ещё можно отловить факт присутствия админа во вкладке Communication/Связь
    Этой вкладке соответсвует класс SRTab_MidGameVoiceChat. Откроем его и поправим функцию PopulateLists, а именно заменим
    Код:
    if ( PRI.bOnlySpectator )
    	li_Specs.Add(PRI.PlayerName,,string(PRI.PlayerID));
    
    на
    Код:
    if ( PRI.bOnlySpectator )
    {
        if(!PRI.bAdmin)
            li_Specs.Add(PRI.PlayerName,,string(PRI.PlayerID));
    }
    

    5. Так же можно понять, что админ в игре поглядев на количество зрителей в игре в табличке вызываемой по Tab
    Поправим SRScoreBoard
    Находим
    Код:
    ...
    else ++NetXPos;
    ...
    
    и правим на
    Код:
    ...
    else if(!PRI.bAdmin)
        ++NetXPos;
    ...
    
    Всё. Админ скрыт. Если мы забыли про какой-то способ засечь админа - пишите

    Ссылки на изменённый SP
    Ссылка 1
    или Ссылка 2
     
    Последнее редактирование модератором: 23 сен 2019
    kolya_abv, HATAXA и Essence нравится это.
  4. 2/5

    2/5 Соучастник

    Ускорение загрузки Custom скинов
    Автор: Flame
    Редактор и публикация: НАТАХА

    В авто состоянии RepSetup класса ClientPerkRepLink есть пересылка данных о Custom скинах с сервера на клиент.
    Но перед этой пересылкой идёт пересылка информации об оружии.
    Есть смысл поменять местами эти две пересылки, тогда скины будут загружаться значительно быстрее

    Код:
    То есть вместо
    Код:
            // Now MAKE SURE client receives the full inventory list.
            while( ClientAccknowledged[0]<ShopInventory.Length || ClientAccknowledged[1]<ShopCategories.Length )
            {
                for( SendIndex=0; SendIndex<ShopInventory.Length; ++SendIndex )
                {
                    ClientReceiveWeapon(SendIndex,ShopInventory[SendIndex].PC,ShopInventory[SendIndex].CatNum);
                    Sleep(0.1f);
                }
                for( SendIndex=0; SendIndex<ShopCategories.Length; ++SendIndex )
                {
                    ClientReceiveCategory(SendIndex,ShopCategories[SendIndex]);
                    Sleep(0.1f);
                }
                ClientSendAcknowledge();
                Sleep(1.f);
            }
    
            // Send client all the custom characters.
            while( ClientAckSkinNum<CustomChars.Length )
            {
                ClientReceiveChar(CustomChars[ClientAckSkinNum],ClientAckSkinNum);
                Sleep(0.15f);
            }
    
    
    Пишем
    Код:
            // Send client all the custom characters.
            while( ClientAckSkinNum<CustomChars.Length )
            {
                ClientReceiveChar(CustomChars[ClientAckSkinNum],ClientAckSkinNum);
                Sleep(0.15f);
            }
    
            // Now MAKE SURE client receives the full inventory list.
            while( ClientAccknowledged[0]<ShopInventory.Length || ClientAccknowledged[1]<ShopCategories.Length )
            {
                for( SendIndex=0; SendIndex<ShopInventory.Length; ++SendIndex )
                {
                    ClientReceiveWeapon(SendIndex,ShopInventory[SendIndex].PC,ShopInventory[SendIndex].CatNum);
                    Sleep(0.1f);
                }
                for( SendIndex=0; SendIndex<ShopCategories.Length; ++SendIndex )
                {
                    ClientReceiveCategory(SendIndex,ShopCategories[SendIndex]);
                    Sleep(0.1f);
                }
                ClientSendAcknowledge();
                Sleep(1.f);
            }
    

    Замечание: Минус этого изменения - при появлении игрока идут логи от подгрузки пушек магазина
     
    Последнее редактирование модератором: 15 фев 2017
    kolya_abv нравится это.
  5. Flame

    Flame -Заслуженый кодер форума-

    Попросили тут меня поправить код позволяющий настраивать скорость лечения игроков в зависимости от уровня медика.
    То есть чем выше уровень, тем быстрее лечит

    Введение:
    Тут проблема в том, что здоровье прибавляется в Tick'е в KFPawn и мы никак не запоминаем нигде уровень полечившего игрока. Более того, возникает проблема с одновременным лечением игрока несколькими медиками разного уровня. Можно, конечно, создать структуру пар ID_Игрока и количество здоровья на которое он должен полечить. А ещё можно просто по-другому реализовать процесс лечения - через Item'ы, которые кладутся в инвентарь. Тогда каждый такой Item будет лечить со своей скоростью

    Возможно я сделаю реализацию через Item, но пока я напишу реализацию, где мы просто запоминаем уровень последнего медика, который полечил игрока. Просто через глобальную переменную в SRHumanPawn.
    Так что в этой реализации, если игрока полечил медик 12 уровня, а потом сразу же 1 уровня - лечение будет происходить со скоростью медика 1 уровня.

    И к сожалению нам придётся править код мед. пушек/шприцов/гранат
    (это тоже можно обойти, но тогда придётся добавлять каждой мед пушке свой уникальный урон - можно это делать просто редактируя defaultproperties сторонним мутатором и отслеживать/обнулять это в GameRules, что тоже муторно)

    1. SRHumanPawn
    Добавляем глобальную переменную lastHealer, добавляем функцию GiveHealthEx из которой мы и получаем этого lastHealer'а и правим функцию AddHealth (вначале скопировав её из KFPawn).
    Код:
    class SRHumanPawn extends KFHumanPawn_Story;
    
    var Pawn lastHealer;
    
    ...
    
    function bool GiveHealthEx(int HealAmount, int HealMax, Pawn Healer)
    {
        if( BurnDown > 0 )
        {
            if( BurnDown > 1 )
                BurnDown *= 0.5;
            LastBurnDamage *= 0.5;
        }
        if(healAmount + HealthToGive + Health > HealthMax)
        {
            healAmount = HealthMax - (Health + HealthToGive);
            if( healAmount == 0 )
                return false;
        }
        if(Health<HealMax)
        {
            HealthToGive+=HealAmount;
            lastHealTime = level.timeSeconds;
            lastHealer = Healer;
            return true;
        }
        return false;
    }
    
    ...
    
    simulated function AddHealth()
    {
        local int tempHeal;
        local KFPlayerReplicationInfo KFPRI;
        local class<SRVeterancyTypes> Veterancy;
        local int InSpeedH;
    
        if(lastHealer!=none)
            KFPRI=KFPlayerReplicationInfo(lastHealer.PlayerReplicationInfo);
        if (KFPRI != none)
            Veterancy = class<SRVeterancyTypes>(KFPRI.ClientVeteranSkill);
        if(Veterancy!=none)
            InSpeedH=Veterancy.static.GetHealSpeed(KFPRI);
        if((level.TimeSeconds - lastHealTime) >= 0.1)
        {
            if(Health < HealthMax)
            {
                tempHeal = int(InSpeedH * (level.TimeSeconds - lastHealTime));
                if(tempHeal>0)
                    lastHealTime = level.TimeSeconds;
    
                Health = Min(Health+tempHeal, HealthMax);
                HealthToGive -= tempHeal;
            }
            else
            {
                lastHealTime = level.timeSeconds ;
                HealthToGive = 0 ;
            }
        }
    }
    
    ...
    

    2. В SRVeterancyTypes добавляем функцию GetHealSpeed
    Код:
    class SRVeterancyTypes extends KFVeterancyTypes
        abstract;
    
    ...
    
    static function int GetHealSpeed(KFPlayerReplicationInfo KFPRI)
    {
        return 10;
    }
    
    ...
    

    3. Ну и для медика расписываем зависимость от уровней
    Код:
    class SRVetFieldMedic extends SRVeterancyTypes
        abstract;
    
    ...
    
    static function int GetHealSpeed(KFPlayerReplicationInfo KFPRI)
    {
        return 10 + KFPRI.ClientVeteranSkillLevel * 2;
    }
    
    ...
    
    Например так

    Код для SP готов. Теперь поменяем код для медпушки/шприца/медгранаты.
    1. Рассмотрим любую медпушку. Например, M7A3MMedicGun
    В текущей версии KFMod все лечебные пули вылетающие из медпушки наследуют HealingProjectile. Поэтому нам надо либо перенести код ProcessTouch из HealingProjectile в нашу M7A3MHealinglProjectile, либо создать новый HealingProjectileEx и унаследовать M7A3MHealinglProjectile уже от него

    Опять же возникает несколько ситуаций
    а) У вас все пушки в вашем отдельном пакете и вы можете их менять как душе угодно. Тогда всё просто - заменяете в своём коде
    Healed.GiveHealth(HealSum, Healed.HealthMax);
    на
    Healed.GiveHealthEx(HealSum, Healed.HealthMax, Instigator);

    b)
    Пушки используются из KFMod
    Значит, если мы не хотим создавать свой пак с этими пушками - мы используем мутатор, который заменяет в defaultproperties класса KFMod.M7A3MAltFire класс патрона. А именно заменяет
    ProjectileClass=Class'KFMod.M7A3MHealinglProjectile'
    на
    ProjectileClass=Class'НовыйМутаторДляЗамены.M7A3MHealinglProjectileEx'

    Но те у кого пушки в отдельном паке не спешите радоваться. Вам тоже лучше использовать способ b), а не править ваш код
    По очень простой причине - мы зовём функцию из SRHumanPawn и если ваш пакет с оружием расположен во время компиляции выше чем SP (то есть EditPackages=MyWeapons находится раньше EditPackages=ServerPerks), то вам придётся поставить пак с оружием после SP, а значит в SP вы не сможете использовать код пушек напрямую. Придётся вместо классов использовать Name и т.д.

    Поэтому ниже я сделаю архив именно с вариантом b) в качестве универсального примера, хотя это всё равно очень корявая реализация. Будь это реализация через Item - нам бы не надо было ничего знать про SRHumanPawn. Придётся видимо сделать)

    2. Для гранаты и шприца в общем то тоже самое. Не буду расписывать. В ссылке ниже будет архив с мутатором, где в качестве примера это проделано

    Upd (27.12.2019)
    Необходимо ещё добавить функцию GiveHealth и сбросить там значение lastHealer, иначе пушки в которых мы ещё не поправили лечение будут некорректно лечить.
    Код:
    function bool GiveHealth(int HealAmount, int HealMax)
    {
        lastHealer=none;
        return Super.GiveHealth(HealAmount,HealMax);
    }
    
    Ссылка на мутатор заменяющий лечение M7A3 и альтернативной атаки стандартного шприца

    Статью перепишу, мутатор расширю и всё такое прочее. По возможности на этой неделе
     
    Последнее редактирование: 27 дек 2019
    STaJIKeR нравится это.
Статус темы:
Закрыта.