Доброго времени суток, камрады. Есть парочка вопросов по коду. Пилю свой скорбоард для кф. Для начала наследуемся от KFScoreBoard или KFScoreBoardNew, добавляем свою механику обсчета, отрисовки и т.д. Предположим, нужно отслеживать и отрисовывать число убитых фп, ск тем или иным игроком с дальнейшей отрисовкой в скорбоарде. Переопределяем ф-ю UpdateScoreBoard, но тут возникает вопрос: по умолчанию доступ к PlayerReplicationInfo всей тимы в этой ф-ии доступен через объект класса GameReplicationInfo, но, если юзать контроллеры игрока, т.е. заполнять массив тимы типа KFPlayerController, и в дальнейшем через него получать доступ к PlayerReplicationInfo, т.е. KFPlayerControllerObject.PlayerReplicationInfo, то каст в ф-ии UpdateScoreBoard Код: KFPRI = KFPlayerReplicationInfo(KFPlayerControllerObject.PlayerReplicationInfo); не робит, по крайней мере ссылка на KFPRI=none, , в то же время как дефолтный код Код: KFPRI = KFPlayerReplicationInfo(TeamPRIArray[i]); робит на ура - первая неясность. Далее, если пытаться обсчитывать механику киллов фп, ск в классе скорбоарда, в том же таймере, например, то не происходит изменения счетчика киллов фп или же ск - вторая неясность, НО, если же обсчитывать это дело в мутаторе (наследнике класса Mutator), то все норм, но опять же, нет отрисовки в самом скорбоарде: через тот же итератор foreach allactors не получается найти объект моего класса скорбоарда - третья неясность. Есть предположение, что дело в репликации, но опять же не до конца понятно, что и куда реплицировать (да, я быдлонубокодер). Вот весь код сей вакханалии: Спойлер Код: class BHScoreboard extends KFScoreBoardNew; var localized string FPText, SCText; var BHScoreboardMut BHSM; //var int FPKills[32], SCKills[32]; /*replication { // Things the server should send to the client. reliable if ( bNetDirty && (Role == Role_Authority) ) FPKills, SCKills; }*/ function SetMutator(BHScoreboardMut M) { BHSM = M; } simulated event UpdateScoreBoard(Canvas Canvas) { local PlayerReplicationInfo PRI, OwnerPRI; local int i,j, FontReduction, NetXPos, PlayerCount, HeaderOffsetY, HeadFoot, MessageFoot, PlayerBoxSizeY, BoxSpaceY, NameXPos, BoxTextOffsetY, OwnerOffset, HealthXPos, BoxXPos,KillsXPos, TitleYPos, BoxWidth, VetXPos, TempVetXPos, VetYPos, FPXPos, SCXPos; local float XL,YL, MaxScaling; local float deathsXL, KillsXL, netXL,HealthXL, MaxNamePos, KillWidthX, HealthWidthX, TimeXL, ScoreXPos, ScoreXL, FPXL, FPWidthX, SCXL, SCWidthX; local bool bNameFontReduction; local Material VeterancyBox, StarMaterial; local int TempLevel; local KFPlayerReplicationInfo KFPRI; local float AssistsXPos,AssistsWidthX; local float CashX; local string CashString,HealthString; local float OutX; local array<PlayerReplicationInfo> TeamPRIArray; //************************************************************************************************ /* local Controller C; local array<KFMonsterController> FPS, SCS; local array<KFPlayerController> PCS;*/ //************************************************************************************************ OwnerPRI = KFPlayerController(Owner).PlayerReplicationInfo; OwnerOffset = -1; for (i = 0; i < GRI.PRIArray.Length; i++) { PRI = GRI.PRIArray[i]; if ( !PRI.bOnlySpectator ) { if ( PRI == OwnerPRI ) OwnerOffset = i; PlayerCount++; TeamPRIArray[ TeamPRIArray.Length ] = PRI; } } PlayerCount = Min(PlayerCount, MAXPLAYERS); //************************************************************************************************ /* PCS.Length = 0; FPS.Length = 0; SCS.Length = 0; for ( C = Level.ControllerList; C != none; C = C.NextController ) { if ( KFPlayerController(C) != none ) PCS[ PCS.Length ] = KFPlayerController(C); if ( KFMonsterController(C) != none ) { if ( KFMonsterController(C).KFM.IsA('ZombieFleshpound') ) FPS[ FPS.Length ] = KFMonsterController(C); if ( KFMonsterController(C).KFM.IsA('ZombieScrake') ) SCS[ SCS.Length ] = KFMonsterController(C); } }*/ //************************************************************************************************ Canvas.Font = class'ROHud'.static.GetSmallMenuFont(Canvas); Canvas.StrLen("Test", XL, YL); BoxSpaceY = 0.25 * YL; PlayerBoxSizeY = 1.2 * YL; HeadFoot = 7 * YL; MessageFoot = 1.5 * HeadFoot; if ( PlayerCount > (Canvas.ClipY - 1.5 * HeadFoot) / (PlayerBoxSizeY + BoxSpaceY) ) { BoxSpaceY = 0.125 * YL; PlayerBoxSizeY = 1.25 * YL; if ( PlayerCount > (Canvas.ClipY - 1.5 * HeadFoot) / (PlayerBoxSizeY + BoxSpaceY) ) { if ( PlayerCount > (Canvas.ClipY - 1.5 * HeadFoot) / (PlayerBoxSizeY + BoxSpaceY) ) { PlayerBoxSizeY = 1.125 * YL; } } } if (Canvas.ClipX < 512) PlayerCount = Min(PlayerCount, 1+(Canvas.ClipY - HeadFoot) / (PlayerBoxSizeY + BoxSpaceY) ); else PlayerCount = Min(PlayerCount, (Canvas.ClipY - HeadFoot) / (PlayerBoxSizeY + BoxSpaceY) ); if (FontReduction > 2) MaxScaling = 3; else MaxScaling = 2.125; PlayerBoxSizeY = FClamp((1.25 + (Canvas.ClipY - 0.67 * MessageFoot)) / PlayerCount - BoxSpaceY, PlayerBoxSizeY, MaxScaling * YL); bDisplayMessages = (PlayerCount <= (Canvas.ClipY - MessageFoot) / (PlayerBoxSizeY + BoxSpaceY)); HeaderOffsetY = 10 * YL; BoxWidth = 0.7 * Canvas.ClipX; BoxXPos = 0.5 * (Canvas.ClipX - BoxWidth); BoxWidth = Canvas.ClipX - 2 * BoxXPos; VetXPos = BoxXPos + 0.00005 * BoxWidth; NameXPos = BoxXPos + 0.075 * BoxWidth; //************************************************************************************************ FPXPos = BoxXPos + 0.30 * BoxWidth; SCXPos = BoxXPos + 0.40 * BoxWidth; //************************************************************************************************ KillsXPos = BoxXPos + 0.50 * BoxWidth; AssistsXPos = BoxXPos + 0.60 * BoxWidth; HealthXpos = BoxXPos + 0.70 * BoxWidth; ScoreXPos = BoxXPos + 0.80 * BoxWidth; NetXPos = BoxXPos + 0.95 * BoxWidth; // Draw background boxes Canvas.Style = ERenderStyle.STY_Alpha; Canvas.DrawColor = HUDClass.default.WhiteColor; Canvas.DrawColor.A = 128; for (i = 0; i < PlayerCount; i++) { Canvas.SetPos(BoxXPos, HeaderOffsetY + (PlayerBoxSizeY + BoxSpaceY) * i); Canvas.DrawTileStretched(BoxMaterial, BoxWidth, PlayerBoxSizeY); } // Draw title Canvas.Style = ERenderStyle.STY_Normal; DrawTitle(Canvas, HeaderOffsetY, (PlayerCount + 1) * (PlayerBoxSizeY + BoxSpaceY), PlayerBoxSizeY); // Draw headers TitleYPos = HeaderOffsetY - 1.1 * YL; Canvas.StrLen(HealthText, HealthXL, YL); Canvas.StrLen(DeathsText, DeathsXL, YL); Canvas.StrLen(KillsText, KillsXL, YL); Canvas.StrLen(PointsText, ScoreXL, YL); Canvas.StrLen(AssistsHeaderText, TimeXL, YL); Canvas.StrLen(FPText, FPXL, YL); Canvas.StrLen(SCText, SCXL, YL); Canvas.DrawColor = HUDClass.default.WhiteColor; Canvas.SetPos(NameXPos, TitleYPos); Canvas.DrawText(PlayerText,true); Canvas.SetPos(KillsXPos - 0.5 * KillsXL, TitleYPos); Canvas.DrawText(KillsText,true); Canvas.SetPos(ScoreXPos - 0.5 * ScoreXL, TitleYPos); Canvas.DrawText(PointsText,true); Canvas.SetPos(AssistsXPos - 0.5 * TimeXL, TitleYPos); Canvas.DrawText(AssistsHeaderText,true); Canvas.SetPos(HealthXPos - 0.5 * HealthXL, TitleYPos); Canvas.DrawText(HealthText,true); //************************************************************************************************ Canvas.SetPos(FPXPos - 0.5 * FPXL, TitleYPos); Canvas.DrawText(FPText,true); Canvas.SetPos(SCXPos - 0.5 * SCXL, TitleYPos); Canvas.DrawText(SCText,true); //************************************************************************************************ // Draw player names MaxNamePos = 0.9 * (KillsXPos - NameXPos); for (i = 0; i < PlayerCount; i++) { Canvas.StrLen(TeamPRIArray[i].PlayerName, XL, YL); if ( XL > MaxNamePos ) { bNameFontReduction = true; break; } } if ( bNameFontReduction ) Canvas.Font = GetSmallerFontFor(Canvas, FontReduction - 1); Canvas.Style = ERenderStyle.STY_Alpha; Canvas.DrawColor = HUDClass.default.WhiteColor; Canvas.SetPos(0.5 * Canvas.ClipX, HeaderOffsetY + 4); BoxTextOffsetY = HeaderOffsetY + 0.5 * (PlayerBoxSizeY - YL); Canvas.DrawColor = HUDClass.default.WhiteColor; MaxNamePos = Canvas.ClipX; Canvas.ClipX = KillsXPos - 4.f; for (i = 0; i < PlayerCount; i++) { Canvas.SetPos(NameXPos, (PlayerBoxSizeY + BoxSpaceY)*i + BoxTextOffsetY); if( i == OwnerOffset ) { Canvas.DrawColor.G = 0; Canvas.DrawColor.B = 0; } else { Canvas.DrawColor.G = 255; Canvas.DrawColor.B = 255; } Canvas.DrawTextClipped(TeamPRIArray[i].PlayerName); } Canvas.ClipX = MaxNamePos; Canvas.DrawColor = HUDClass.default.WhiteColor; if (bNameFontReduction) Canvas.Font = GetSmallerFontFor(Canvas, FontReduction); Canvas.Style = ERenderStyle.STY_Normal; MaxScaling = FMax(PlayerBoxSizeY, 30.f); // Draw each player's information for (i = 0; i < PlayerCount; i++) { //************************************************************************************************ KFPRI = KFPlayerReplicationInfo(TeamPRIArray[i]); //PRI = FindPlayerControllerPRI(PCS[i], TeamPRIArray); //KFPRI = KFPlayerReplicationInfo(PCS[i].PlayerReplicationInfo); //DebugMessage("KFPRI "@string(KFPRI)); //DebugMessage("PCS"@string(i+1)@" "@string(PCS[i])); //FPKills[i] = KillingCounter(PCS[i], FPS); //SCKills[i] = KillingCounter(PCS[i], SCS); //************************************************************************************************ Canvas.DrawColor = HUDClass.default.WhiteColor; // Display perks. if ( KFPRI!=None && KFPRI.ClientVeteranSkill != none ) { if(KFPRI.ClientVeteranSkillLevel == 6) { VeterancyBox = KFPRI.ClientVeteranSkill.default.OnHUDGoldIcon; StarMaterial = class'HUDKillingFloor'.default.VetStarGoldMaterial; TempLevel = KFPRI.ClientVeteranSkillLevel - 5; } else { VeterancyBox = KFPRI.ClientVeteranSkill.default.OnHUDIcon; StarMaterial = class'HUDKillingFloor'.default.VetStarMaterial; TempLevel = KFPRI.ClientVeteranSkillLevel; } if ( VeterancyBox != None ) { TempVetXPos = VetXPos; VetYPos = (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY - PlayerBoxSizeY * 0.22; Canvas.SetPos(TempVetXPos, VetYPos); Canvas.DrawTile(VeterancyBox, PlayerBoxSizeY, PlayerBoxSizeY, 0, 0, VeterancyBox.MaterialUSize(), VeterancyBox.MaterialVSize()); if(StarMaterial != none) { TempVetXPos += PlayerBoxSizeY - ((PlayerBoxSizeY/5) * 0.75); VetYPos += PlayerBoxSizeY - ((PlayerBoxSizeY/5) * 1.5); for ( j = 0; j < TempLevel; j++ ) { Canvas.SetPos(TempVetXPos, VetYPos); Canvas.DrawTile(StarMaterial, (PlayerBoxSizeY/5) * 0.7, (PlayerBoxSizeY/5) * 0.7, 0, 0, StarMaterial.MaterialUSize(), StarMaterial.MaterialVSize()); VetYPos -= (PlayerBoxSizeY/5) * 0.7; } } } } //************************************************************************************************ Canvas.StrLen(BHSM.FPKills[i], FPWidthX, YL); Canvas.SetPos(FPXPos - 0.5 * FPWidthX, (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY); Canvas.DrawText(BHSM.FPKills[i], true); Canvas.StrLen(BHSM.SCKills[i], SCWidthX, YL); Canvas.SetPos(SCXPos - 0.5 * SCWidthX, (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY); Canvas.DrawText(BHSM.SCKills[i], true); //************************************************************************************************ // draw kills if( bDisplayWithKills ) { Canvas.StrLen(KFPRI.Kills, KillWidthX, YL); Canvas.SetPos(KillsXPos - 0.5 * KillWidthX, (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY); Canvas.DrawText(KFPRI.Kills, true); // Draw Kill Assists Canvas.StrLen(KFPRI.KillAssists, AssistsWidthX, YL); Canvas.SetPos(AssistsXPos - 0.5 * AssistsWidthX, (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY); Canvas.DrawText(KFPRI.KillAssists, true); } // draw cash CashString = "£"@string(int(TeamPRIArray[i].Score)) ; if(TeamPRIArray[i].Score >= 1000) { CashString = "£"@string(TeamPRIArray[i].Score/1000.f)$"K" ; } Canvas.StrLen(CashString,CashX,YL); Canvas.SetPos(ScoreXPos - CashX/2 , (PlayerBoxSizeY + BoxSpaceY)*i + BoxTextOffsetY); Canvas.DrawColor = Canvas.MakeColor(255,255,125,255); Canvas.DrawText(CashString); Canvas.DrawColor = HUDClass.default.WhiteColor; // Draw health status HealthString = KFPRI.PlayerHealth$" HP" ; Canvas.StrLen(HealthString,HealthWidthX,YL); Canvas.SetPos(HealthXpos - HealthWidthX/2, (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY); if ( TeamPRIArray[i].bOutOfLives ) { Canvas.StrLen(OutText,OutX,YL); Canvas.DrawColor = HUDClass.default.RedColor; Canvas.SetPos(HealthXpos - OutX/2, (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY); Canvas.DrawText(OutText); } else { if( KFPRI.PlayerHealth>=80 ) { Canvas.DrawColor = HUDClass.default.GreenColor; } else if( KFPRI.PlayerHealth>=50 ) { Canvas.DrawColor = HUDClass.default.GoldColor; } else { Canvas.DrawColor = HUDClass.default.RedColor; } Canvas.DrawText(HealthString); } } if (Level.NetMode == NM_Standalone) return; Canvas.StrLen(NetText, NetXL, YL); Canvas.DrawColor = HUDClass.default.WhiteColor; Canvas.SetPos(NetXPos - 0.5 * NetXL, TitleYPos); Canvas.DrawText(NetText,true); for (i=0; i<GRI.PRIArray.Length; i++) PRIArray[i] = GRI.PRIArray[i]; DrawNetInfo(Canvas, FontReduction, HeaderOffsetY, PlayerBoxSizeY, BoxSpaceY, BoxTextOffsetY, OwnerOffset, PlayerCount, NetXPos); DrawMatchID(Canvas, FontReduction); } defaultproperties { FPText="FP" SCText="SC" } Код: class BHScoreboardMut extends Mutator; var BHScoreboard B; var Controller C; var array<KFMonsterController> FPS, SCS; var array<KFPlayerController> PCS; var int FPKills[32], SCKills[32]; var int i; replication { // Things the server should send to the client. reliable if ( bNetDirty && (Role == Role_Authority) ) FPKills, SCKills; } simulated function PostBeginPlay() { Level.Game.ScoreBoardType = "BHScoreboard.BHScoreboard"; /*foreach AllActors(class'BHScoreboard', B) break; B.SetMutator(self);*/ SetTimer(1.0, true); } function DebugMessage(string Message) { //local Controller C; for ( C = Level.ControllerList; C != none; C = C.NextController ) { if ( PlayerController(C) != none ) PlayerController(C).ClientMessage(Message); } } function bool CheckReplacement(Actor Other, out byte bSuperRelevant) { //local BHScoreboard Board; B = BHScoreboard(Other); if ( B != none ) { B.SetMutator(self); } return true; } function Timer() { PCS.Length = 0; FPS.Length = 0; SCS.Length = 0; for ( C = Level.ControllerList; C != none; C = C.NextController ) { if ( KFPlayerController(C) != none ) PCS[ PCS.Length ] = KFPlayerController(C); if ( KFMonsterController(C) != none ) { if ( KFMonsterController(C).KFM.IsA('ZombieFleshpound') ) FPS[ FPS.Length ] = KFMonsterController(C); if ( KFMonsterController(C).KFM.IsA('ZombieScrake') ) SCS[ SCS.Length ] = KFMonsterController(C); } } for (i = 0; i < PCS.Length; i++) { FPKills[i] += KillingCounter(PCS[i], FPS); SCKills[i] += KillingCounter(PCS[i], SCS); DebugMessage(string(SCKills[i])); } DebugMessage(string(B.Name)); DebugMessage(Level.Game.ScoreBoardType); } function int KillingCounter(KFPlayerController Killer, array<KFMonsterController> Victim) { local int i1, Counter; Counter = 0; for ( i1 = 0; i1 < Victim.Length; i1++) { if ( KFPlayerController(Victim[i1].KFM.LastHitBy) == Killer && Victim[i1].KFM.Health <= 0 ) Counter++; } return Counter; } /*function PlayerReplicationInfo FindPlayerControllerPRI(KFPlayerController KFPC, array<PlayerReplicationInfo> PRI) { local int i; if ( KFPC == none ) return none; else { for ( i = 0; i < PRI.Length; i++) { if ( KFPC.PlayerReplicationInfo.PlayerID == PRI[i].PlayerID ) return PRI[i]; else continue; } } }*/ defaultproperties { bAlwaysRelevant=true RemoteRole=ROLE_SimulatedProxy //bNetNotify=True bAddToServerPackages=true GroupName="BHScoreboard" FriendlyName="Bloody Hell Scoreboard" Description="FP and SC stats were added." } Буду признателен за любой совет и помощь.
Кол-во убитых мобов лучше отлавливать в GameRules в функции ScoreKill Дальше либо передавать это кол-во в кастомный PlayerReplicationInfo, либо хранить в мутаторе И уже тогда тянуть инфу в кастомный ScoreBoard Кстати, ещё можно глянуть эту тему
Ты не против, если я подкину ему кусочек кода, с которым ты мне помог очень давно? Прошу кидай код по спойлер.
Ну основная беда в том, что ты на клиенте работаешь с тем кодом, который надо звать на сервере. Так себе идея тащить мутатор (который должен выполняться на сервере) в ScoreBoard, которая выполняется только на клиенте. Нужен некоторый объект, который принадлежит игроку и существует как на сервере (сбор информации), так и на клиенте (отображение в табличке) Хорошее решение - использование либо SRPlayerReplicationInfo или (если не хочется его захламлять) отдельного ReplicationInfo класса. Кроме того вполне возможно цеплять объекты к контроллеру. То есть цеплять на контроллер новый объект содержащий эти переменные. Но пока не будем усложнять - чуть ниже напишу реализацию с PlayerReplicationInfo + GameRules И вообще есть смысл написать краткую статью по модификации таблички раз такое дело. Но это чуть позже Мутатор: Просто добавим GameRules в котором будем отслеживать убийства, урон и т.д. Кроме того в CheckReplacement добавим новую PlayerReplicationInfo унаследованную от KFPlayerReplicationInfo Ну и пропишем ссылку на новую табличку Спойлер Код: class ScoreBoardMut extends Mutator; var ScoreBoardGameRules sbGameRules; function PostBeginPlay() { Level.Game.ScoreBoardType = "ScoreBoardMut.NewScoreBoard"; sbGameRules=Spawn(Class'ScoreBoardGameRules'); } function bool CheckReplacement(Actor Other, out byte bSuperRelevant) { if(Controller(Other)!=None) Controller(Other).PlayerReplicationInfoClass = Class'SBPlayerReplicationInfo'; return true; } defaultproperties { bAlwaysRelevant=true RemoteRole=ROLE_SimulatedProxy bAddToServerPackages=true GroupName="KF-NewScoreboard" FriendlyName="NewScoreboard" Description="NewScoreboard" } Файл репликации: Просто создаём переменную и реплицируем её на клиент Спойлер Код: class SBPlayerReplicationInfo extends KFPlayerReplicationInfo; var int FPKilled; replication { reliable if (bNetDirty && Role == Role_Authority) FPKilled; } GameRules: Здесь основные функции это PreventDeath (можно так же использовать ScoreKill) и NetDamage Первая отслеживает убийства, вторая урон Спойлер Код: class ScoreBoardGameRules extends GameRules; function PostBeginPlay() { if( Level.Game.GameRulesModifiers==None ) Level.Game.GameRulesModifiers = Self; else Level.Game.GameRulesModifiers.AddGameRules(Self); } function AddGameRules(GameRules GR) { if ( GR!=Self ) Super.AddGameRules(GR); } //Тут информацию по убийствам собираем function bool PreventDeath(Pawn Killed, Controller Killer, class<DamageType> DamageType, vector HitLocation) { if(DamageType==None || Killer==None || Killed==None) { if ( NextGameRules != None) return NextGameRules.PreventDeath(Killed,Killer,DamageType,HitLocation); return false; } if ( Killed.IsA('ZombieFleshpound') && Killer!=none && SBPlayerReplicationInfo(Killer.PlayerReplicationInfo)!=none ) { SBPlayerReplicationInfo(Killer.PlayerReplicationInfo).FPKilled++; } if(NextGameRules != None) return NextGameRules.PreventDeath(Killed,Killer, damageType,HitLocation); return false; } //Тут информацию по урону собираем function int NetDamage(int OriginalDamage, int Damage, Pawn Injured, Pawn InstigatedBy, vector HitLocation, out vector Momentum, class<DamageType> DamageType) { if ( NextGameRules != None) return NextGameRules.NetDamage(OriginalDamage, Damage, injured, instigatedBy, HitLocation, Momentum, DamageType); return Damage; } Ну и сама табличка: Тут в общем просто выводится SBPlayerReplicationInfo(PlayerReplicationInfo).FPKilled переменная Переменную FPsText я сделал глобальной просто аналогично коду от TWI, но в принципе в этом нет особого смысла и можно делать её локальной наряду с другими новыми переменными. Модифицированный код я выделил следующим образом: //Flame Код // Спойлер Код: class NewScoreBoard extends KFScoreBoardNew; var localized string FPsText; simulated event UpdateScoreBoard(Canvas Canvas) { local PlayerReplicationInfo PRI, OwnerPRI; local int i,j, FontReduction, NetXPos, PlayerCount, HeaderOffsetY, HeadFoot, MessageFoot, PlayerBoxSizeY, BoxSpaceY, NameXPos, BoxTextOffsetY, OwnerOffset, HealthXPos, BoxXPos,KillsXPos, TitleYPos, BoxWidth, VetXPos, TempVetXPos, VetYPos; local float XL,YL, MaxScaling; local float deathsXL, KillsXL, netXL,HealthXL, MaxNamePos, KillWidthX, HealthWidthX, TimeXL, TimeWidthX, TimeXPos, ScoreXPos, ScoreXL; local bool bNameFontReduction; local Material VeterancyBox, StarMaterial; local int TempLevel, TempY; local string PlayerTime; local KFPlayerReplicationInfo KFPRI; local float AssistsXPos,AssistsWidthX; local float CashX; local string CashString,HealthString; local float OutX; local array<PlayerReplicationInfo> TeamPRIArray; //Flame local int FPsXPos; local float FPsXL,FPsWidthX; // OwnerPRI = KFPlayerController(Owner).PlayerReplicationInfo; OwnerOffset = -1; for (i = 0; i < GRI.PRIArray.Length; i++) { PRI = GRI.PRIArray[i]; if ( !PRI.bOnlySpectator ) { if ( PRI == OwnerPRI ) OwnerOffset = i; PlayerCount++; TeamPRIArray[ TeamPRIArray.Length ] = PRI; } } PlayerCount = Min(PlayerCount, MAXPLAYERS); Canvas.Font = class'ROHud'.static.GetSmallMenuFont(Canvas); Canvas.StrLen("Test", XL, YL); BoxSpaceY = 0.25 * YL; PlayerBoxSizeY = 1.2 * YL; HeadFoot = 7 * YL; MessageFoot = 1.5 * HeadFoot; if ( PlayerCount > (Canvas.ClipY - 1.5 * HeadFoot) / (PlayerBoxSizeY + BoxSpaceY) ) { BoxSpaceY = 0.125 * YL; PlayerBoxSizeY = 1.25 * YL; if ( PlayerCount > (Canvas.ClipY - 1.5 * HeadFoot) / (PlayerBoxSizeY + BoxSpaceY) ) { if ( PlayerCount > (Canvas.ClipY - 1.5 * HeadFoot) / (PlayerBoxSizeY + BoxSpaceY) ) { PlayerBoxSizeY = 1.125 * YL; } } } if (Canvas.ClipX < 512) PlayerCount = Min(PlayerCount, 1+(Canvas.ClipY - HeadFoot) / (PlayerBoxSizeY + BoxSpaceY) ); else PlayerCount = Min(PlayerCount, (Canvas.ClipY - HeadFoot) / (PlayerBoxSizeY + BoxSpaceY) ); if (FontReduction > 2) MaxScaling = 3; else MaxScaling = 2.125; PlayerBoxSizeY = FClamp((1.25 + (Canvas.ClipY - 0.67 * MessageFoot)) / PlayerCount - BoxSpaceY, PlayerBoxSizeY, MaxScaling * YL); bDisplayMessages = (PlayerCount <= (Canvas.ClipY - MessageFoot) / (PlayerBoxSizeY + BoxSpaceY)); HeaderOffsetY = 10 * YL; BoxWidth = 0.7 * Canvas.ClipX; BoxXPos = 0.5 * (Canvas.ClipX - BoxWidth); BoxWidth = Canvas.ClipX - 2 * BoxXPos; VetXPos = BoxXPos + 0.00005 * BoxWidth; NameXPos = BoxXPos + 0.075 * BoxWidth; //Flame KillsXPos = BoxXPos + 0.30 * BoxWidth; //0.50 * BoxWidth; FPsXPos = BoxXPos + 0.40 * BoxWidth; // AssistsXPos = BoxXPos + 0.60 * BoxWidth; HealthXpos = BoxXPos + 0.70 * BoxWidth; ScoreXPos = BoxXPos + 0.80 * BoxWidth; NetXPos = BoxXPos + 0.95 * BoxWidth; // Draw background boxes Canvas.Style = ERenderStyle.STY_Alpha; Canvas.DrawColor = HUDClass.default.WhiteColor; Canvas.DrawColor.A = 128; for (i = 0; i < PlayerCount; i++) { Canvas.SetPos(BoxXPos, HeaderOffsetY + (PlayerBoxSizeY + BoxSpaceY) * i); Canvas.DrawTileStretched(BoxMaterial, BoxWidth, PlayerBoxSizeY); } // Draw title Canvas.Style = ERenderStyle.STY_Normal; DrawTitle(Canvas, HeaderOffsetY, (PlayerCount + 1) * (PlayerBoxSizeY + BoxSpaceY), PlayerBoxSizeY); // Draw headers TitleYPos = HeaderOffsetY - 1.1 * YL; Canvas.StrLen(HealthText, HealthXL, YL); Canvas.StrLen(DeathsText, DeathsXL, YL); Canvas.StrLen(KillsText, KillsXL, YL); //Flame Canvas.StrLen(FPsText, FPsXL, YL); // Canvas.StrLen(PointsText, ScoreXL, YL); Canvas.StrLen(AssistsHeaderText, TimeXL, YL); Canvas.DrawColor = HUDClass.default.WhiteColor; Canvas.SetPos(NameXPos, TitleYPos); Canvas.DrawText(PlayerText,true); Canvas.SetPos(KillsXPos - 0.5 * KillsXL, TitleYPos); Canvas.DrawText(KillsText,true); //Flame Canvas.SetPos(FPsXPos - 0.5 * FPsXL, TitleYPos); Canvas.DrawText(FPsText,true); // Canvas.SetPos(ScoreXPos - 0.5 * ScoreXL, TitleYPos); Canvas.DrawText(PointsText,true); Canvas.SetPos(AssistsXPos - 0.5 * TimeXL, TitleYPos); Canvas.DrawText(AssistsHeaderText,true); Canvas.SetPos(HealthXPos - 0.5 * HealthXL, TitleYPos); Canvas.DrawText(HealthText,true); // Draw player names MaxNamePos = 0.9 * (KillsXPos - NameXPos); for (i = 0; i < PlayerCount; i++) { Canvas.StrLen(TeamPRIArray[i].PlayerName, XL, YL); if ( XL > MaxNamePos ) { bNameFontReduction = true; break; } } if ( bNameFontReduction ) Canvas.Font = GetSmallerFontFor(Canvas, FontReduction - 1); Canvas.Style = ERenderStyle.STY_Alpha; Canvas.DrawColor = HUDClass.default.WhiteColor; Canvas.SetPos(0.5 * Canvas.ClipX, HeaderOffsetY + 4); BoxTextOffsetY = HeaderOffsetY + 0.5 * (PlayerBoxSizeY - YL); Canvas.DrawColor = HUDClass.default.WhiteColor; MaxNamePos = Canvas.ClipX; Canvas.ClipX = KillsXPos - 4.f; for (i = 0; i < PlayerCount; i++) { Canvas.SetPos(NameXPos, (PlayerBoxSizeY + BoxSpaceY)*i + BoxTextOffsetY); if( i == OwnerOffset ) { Canvas.DrawColor.G = 0; Canvas.DrawColor.B = 0; } else { Canvas.DrawColor.G = 255; Canvas.DrawColor.B = 255; } Canvas.DrawTextClipped(TeamPRIArray[i].PlayerName); } Canvas.ClipX = MaxNamePos; Canvas.DrawColor = HUDClass.default.WhiteColor; if (bNameFontReduction) Canvas.Font = GetSmallerFontFor(Canvas, FontReduction); Canvas.Style = ERenderStyle.STY_Normal; MaxScaling = FMax(PlayerBoxSizeY, 30.f); // Draw each player's information for (i = 0; i < PlayerCount; i++) { KFPRI = KFPlayerReplicationInfo(TeamPRIArray[i]) ; Canvas.DrawColor = HUDClass.default.WhiteColor; // Display perks. if ( KFPRI!=None && KFPRI.ClientVeteranSkill != none ) { if(KFPRI.ClientVeteranSkillLevel == 6) { VeterancyBox = KFPRI.ClientVeteranSkill.default.OnHUDGoldIcon; StarMaterial = class'HUDKillingFloor'.default.VetStarGoldMaterial; TempLevel = KFPRI.ClientVeteranSkillLevel - 5; } else { VeterancyBox = KFPRI.ClientVeteranSkill.default.OnHUDIcon; StarMaterial = class'HUDKillingFloor'.default.VetStarMaterial; TempLevel = KFPRI.ClientVeteranSkillLevel; } if ( VeterancyBox != None ) { TempVetXPos = VetXPos; VetYPos = (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY - PlayerBoxSizeY * 0.22; Canvas.SetPos(TempVetXPos, VetYPos); Canvas.DrawTile(VeterancyBox, PlayerBoxSizeY, PlayerBoxSizeY, 0, 0, VeterancyBox.MaterialUSize(), VeterancyBox.MaterialVSize()); if(StarMaterial != none) { TempVetXPos += PlayerBoxSizeY - ((PlayerBoxSizeY/5) * 0.75); VetYPos += PlayerBoxSizeY - ((PlayerBoxSizeY/5) * 1.5); for ( j = 0; j < TempLevel; j++ ) { Canvas.SetPos(TempVetXPos, VetYPos); Canvas.DrawTile(StarMaterial, (PlayerBoxSizeY/5) * 0.7, (PlayerBoxSizeY/5) * 0.7, 0, 0, StarMaterial.MaterialUSize(), StarMaterial.MaterialVSize()); VetYPos -= (PlayerBoxSizeY/5) * 0.7; } } } } // draw kills if( bDisplayWithKills ) { Canvas.StrLen(KFPRI.Kills, KillWidthX, YL); Canvas.SetPos(KillsXPos - 0.5 * KillWidthX, (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY); Canvas.DrawText(KFPRI.Kills, true); //Flame Canvas.StrLen(SBPlayerReplicationInfo(KFPRI).FPKilled, FPsWidthX, YL); Canvas.SetPos(FPsXPos - 0.5 * FPsWidthX, (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY); Canvas.DrawText(SBPlayerReplicationInfo(KFPRI).FPKilled, true); // // Draw Kill Assists Canvas.StrLen(KFPRI.KillAssists, AssistsWidthX, YL); Canvas.SetPos(AssistsXPos - 0.5 * AssistsWidthX, (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY); Canvas.DrawText(KFPRI.KillAssists, true); } // draw cash CashString = "Ј"@string(int(TeamPRIArray[i].Score)) ; if(TeamPRIArray[i].Score >= 1000) { CashString = "Ј"@string(TeamPRIArray[i].Score/1000.f)$"K" ; } Canvas.StrLen(CashString,CashX,YL); Canvas.SetPos(ScoreXPos - CashX/2 , (PlayerBoxSizeY + BoxSpaceY)*i + BoxTextOffsetY); Canvas.DrawColor = Canvas.MakeColor(255,255,125,255); Canvas.DrawText(CashString); Canvas.DrawColor = HUDClass.default.WhiteColor; // Draw health status HealthString = KFPRI.PlayerHealth$" HP" ; Canvas.StrLen(HealthString,HealthWidthX,YL); Canvas.SetPos(HealthXpos - HealthWidthX/2, (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY); if ( TeamPRIArray[i].bOutOfLives ) { Canvas.StrLen(OutText,OutX,YL); Canvas.DrawColor = HUDClass.default.RedColor; Canvas.SetPos(HealthXpos - OutX/2, (PlayerBoxSizeY + BoxSpaceY) * i + BoxTextOffsetY); Canvas.DrawText(OutText); } else { if( KFPRI.PlayerHealth>=80 ) { Canvas.DrawColor = HUDClass.default.GreenColor; } else if( KFPRI.PlayerHealth>=50 ) { Canvas.DrawColor = HUDClass.default.GoldColor; } else { Canvas.DrawColor = HUDClass.default.RedColor; } Canvas.DrawText(HealthString); } } if (Level.NetMode == NM_Standalone) return; Canvas.StrLen(NetText, NetXL, YL); Canvas.DrawColor = HUDClass.default.WhiteColor; Canvas.SetPos(NetXPos - 0.5 * NetXL, TitleYPos); Canvas.DrawText(NetText,true); for (i=0; i<GRI.PRIArray.Length; i++) PRIArray[i] = GRI.PRIArray[i]; DrawNetInfo(Canvas, FontReduction, HeaderOffsetY, PlayerBoxSizeY, BoxSpaceY, BoxTextOffsetY, OwnerOffset, PlayerCount, NetXPos); DrawMatchID(Canvas, FontReduction); } defaultproperties { FPsText="FPs" } Ссылка ScoreBoardMut.ScoreBoardMut Спойлер Замечание: Не забываем, что GameRules это цепочка последовательно выполняющихся правил, поэтому если есть правило, которое модифицирует урон - убедитесь, что оно выполняется раньше этого правила ведущего статистику. Ибо иначе статистика в табличке будет неверная Замечание 2: Тема создана не в том подфоруме. Перенесу её попозже в кодинг Замечание 3: Мутатор серый + закачивается на клиент, следовательно кача на классических серверах не будет Замечание 4: Тем кто использует ServerPerks нет нужды создавать новый файл репликации - используйте SRPlayerReplicationInfo Табличку опять же стоит использовать стандартную из SP - её и модифицировать То есть всё что нужно тем, кто использует SP - дополнительные переменные в SRPlayerReplicationInfo и их репликация + GameRules, где будет отслеживание убийств и урона
А. Вижу ты хотел подробностей почему твой мутатор не работает 1. CheckReplacement выполняется на сервере, SetMutator нигде не определён, но эт ладно. Он тоже будет выполняться на сервере. И я честно говоря не представляю куда ты будешь привязываться в SetMutator, ибо не существует таблички на сервере. Но так как у тебя мутатор существует и для клиента, то в принципе можешь найти этот мутатор тем же ForEach циклом по мутаторам. То есть внутри кода таблички цикл по всем мутаторам и если это мутатор BHScoreboardMut, то сохраняем его. Ну или просто цикл по классам BHScoreboardMut. 2. Очень сомнительный способ фиксации убийств. Таймер то у тебя раз в секунду работает. А здоровье<0 и как следствие уничтожение тела происходит достаточно быстро. Но это то ладно - можно и поменьше период сделать у таймера. Но всёж лучше использовать GameRules и не изобретать велосипед) 3. PCS - массив составленный циклом по Level.ControllerList из KFPlayerController. И ты выводишь i значение этого массива в табличке: Код: Canvas.DrawText(BHSM.FPKills[i], true); Но почему игрок с контроллером (I) это игрок с PRIArray(I)? (использую круглые скобки, чтобы редактор форума не менял шрифт на курсив)) ) Ты проверял? Они совпадают? Я вот совсем не уверен Заполнение PRIArray идёт в GameReplicationInfo.PostNetBeginPlay Код: ForEach DynamicActors(class'PlayerReplicationInfo',PRI) AddPRI(PRI); В общем основная проблема - передача ссылки на мутатор Добавь поиск Код: ForEach DynamicActors(class'BHScoreboardMut',bhs) в код таблички и можешь потестировать свой вариант. Есть вероятность что он худо бедно заработает. Хотя может будет показывать чужой кач Но лучше используй GameRules и сторонний объект для репликации
Нет, тут не вариант юзать марковский SP. Я прекрасно понимаю тему с согласованием PRI и контроллеров, в коде отдельная для этого ф-я прописана (может я забыл ее скопировать, не суть). Я искал скорбоард, но в муте через данный итератор, ссылка none по дефолту, почему? Из-за того, что нет репликации скорбоарда на серев? Да, мб говнокод будет в этом случае, но все же интересно узнать. А убийства да, они не робят, это пока что черновой вариант) И еще вопрос в таком случае: зачем спавнить класс ScoreBoardGameRules, т.е sbGameRules=Spawn(Class'ScoreBoardGameRules')?
Эт, а причём тут ServerPerks? Я дал решение без использования SP - проверил на чистом серваке Фиг знает как ты искал - ща тож поищу Репликации таблички на сервер нет, конечно, но при чём тут это У тебя мутатор выполняется на каждом из клиентов, ибо у тебя прописано Код: bAlwaysRelevant=true RemoteRole=ROLE_SimulatedProxy Значит на клиенте этот мутатор вполне можно найти В смысле зачем спавнить ScoreBoardGameRules?) Чтобы создать объект класса GameRules и отследить там убийства и урон Можно было не создавать переменную sbGameRules и тем более не делать её глобальной, это я по привычке Но Spawn(Class'ScoreBoardGameRules') то нужен Upd. А. Невнимательно прочёл. Зачем ты ScoreBoard то искал? Ищи в ScoreBoard мутатор, а не наоборот. Ну если очень хочешь искать в мутаторе, то убедись, что функция с префиском simulated Лучше всего в Tick'е, наверное Как-нибудь так: Спойлер Код: var bool bInitiated; ... simulated function Tick(float dt) { local BHScoreboard bhs; if(Role<ROLE_AUTHORITY && !bInitiated) { foreach DynamicActors(class'BHScoreboard',bhs) { bhs.mut=self; bInitiated=true; } } Super.Tick(dt); } ну или можно ещё покрасивее Код: simulated function Tick(float dt) { local BHScoreboard bhs; if(Role<ROLE_AUTHORITY) { foreach DynamicActors(class'BHScoreboard',bhs) { bhs.mut=self; Disable('Tick'); break; } } Super.Tick(dt); }