[TUTORIAL] Livestats on HUD

serthy
Hier geht es darum, wie man auf dem HUD seine aktuellen stats angezeigt bekommt.



Als erstes erarbeiten wir uns jetzt einmal die Datei, die das ganze letztendlich auch bewerkstelligen soll.
Erstellt eine Datei mit der Endung '.gsc', ich nenne sie hier stats.gsc.
In diese schreiben wir nun als erstes unsere Initialisierungsfunktion, welche sobald eine Map geladen wird als allererstes aufgerufen wird.
Alle Texte/Bilder die als HUD auf dem Bildschirm zu sehen sind, müssen in erstem Serverframe geladen werden, ansonsten wird es zu Serverabstürzen kommen.
Also machen wir das gleich mal:

php:
1:
2:
3:
4:
5:
init()
{
        precacheString( &"^5~>^7Score: &&1" );        //&&1 wird dann mit einer variablen ersetzt
        precacheString( &"^5~>^7Deaths: &&1" );
}
Als nächstes müssen wir jetzt alle Spieler die auf den Server connecten erfassen, sowohl ls auch wenn sie spawnen, um das HUD auch anzeigen zu lassen.
Das passiert hiermit:

php:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
init()
{
        precacheString( &"^5~>^7Score: &&1" );
        precacheString( &"^5~>^7Deaths: &&1" );

        level thread onPlayerConnected(); //Funktionsaufruf
}

onPlayerConnected()
{
        while( true )
        {
                level waittill"connected" player );        //warten bis Spieler connected

                if( !isDefinedplayer.score ) )        //stats initialisieren, falls nocht nicht geschehen
                        player.score 0;
                if( !isDefinedplayer.deaths ) )
                        player.deaths 0;

                player thread onPlayerSpawned();        //Spieler führt jetzt vers. Funktionen aus
        }
}

onPlayerSpawned() //überwacht Spawns des Spielers (self = Spieler)
{
        self endon"disconnect" ); //wenn Spieler das Spiel verlässt, beendet sich die Funktion

        while( isDefinedself ) )
        {
                self waittill"spawned" );

                wait0.05 );

                if( self.pers["team"] == "spectator" )
                {
                        //kein StatsHUD für Zuschauer
                        self thread destroyStatsboard();
                }
                else
                {
                        //hier nun HUD anzeigen lassen
                        self thread drawStatsboard();
                        self thread monitorPlayer();
                }
        }
}
Jetzt schreiben wir die Funktion, welche die HUD's erzeugt. Diese wird an jedem Spawn neu aufgerufen:


php:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
drawStatsboard()
{
        self endon"disconnect" );

        //HUDs von Funktion erstellen lassen
        self.hud_stats_score self createStatsboardLine, &"^5~>^7Score: &&1" );
        self.hud_stats_deaths self createStatsboardLine, &"^5~>^7Deaths: &&1" );
}

createStatsboardLinelineCount label //erstellt das HUD
{
        = -100;
        0;
        vertAlign "middle";
        horzAlign "right";
        lineHeight 10;

        hud newClientHudElemself );                //erstellt HUD das nur für 1 Spieler zu sehen ist (self = Spieler)
        hud.x;
        hud.+ ( lineCount lineHeight );        //legt Reihe fest
        hud.vertAlign vertAlign;                        //Alignment auf dem Bildschirm
        hud.horzAlign horzAlign;                        //Alignment auf dem Bildschirm
        hud.archived true;                                //.archived legt fest, ob das HUD in der Killcam zusehen ist
        hud.label label;                                //.label beinhaltet unseren String, somit können noch mit setText() oder setValue() andere Werte mit dem selben HUD angezeigt werden
        hud setValue);

        return hud;        //und HUD wieder zurückgeben
}
Okay, nun wird das HUD am Spawn erzeugt, nun kommt der Teil, wo das HUD aktualisiert wird. Diese Funktion wird überall da aufgerufen, wo die Werte sich verändertn (Kill...)
Zudem werde ich hier nur 2 Werte (score/deaths) betrachten, für headshots etc müsst ihr in die jeweiligen gametypes (tdm.gsc/sd.gsc...) gehen und in der Funktion 'Callback_PlayerKilled' einen headshotcounter einführen (if( sMeansOfDeath == "MOD_HEAD_SHOT" ) eAttacker.headshots++Augenzwinkern , und noch einige Dinge mehr, aber das würde jetzt zu viel Aufwand auf einmal sein
Zugegeben die monitorPlayer()-Funktion ist nicht besonders schön, dafür aber einfach. Besser wäre es wirklich direkt beim Kill in die oben genannte Funktion zu gehen und eAttacker thread mod\stats::updateLivestats(); sowie das selbe für den Toten zu machen, das spart uns einen Loop für jeden Spieler.
php:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
updateStatsboard()
{
        if( isDefinedself.hud_stats_score ) )
                self.hud_stats_score setValueself.score );
        if( isDefinedself.hud_stats_deaths ) )
                self.hud_stats_deaths setValueself.deaths );
}

monitorPlayer()
{
        self endon"disconnect" );

        score 0;
        deaths 0;

        while( isDefinedself ) )
        {
                update false;

                if( score != self.score )
                        update true;
                else if( deaths != self.deaths )
                        update true;

                if( update )
                        self thread updateStatsboard();

                if( self.sessionstate != "playing" )
                        break;

                wait1.0 );
        }
}
nun fehlt noch eine letzte Kleinigkeit. Irgendwie muss der Bildschirm wieder freigemacht werden, dazu müssen wir die HUDS wieder beseitigen:

php:
1:
2:
3:
4:
5:
6:
7:
destroyStatsboard()
{
        if( isDefinedself.hud_stats_score ) )
                self.hud_stats_score destroy();
        if( isDefinedself.hud_stats_deaths ) )
                self.hud_stats_deaths destroy();
}
Alles zusammen sieht dann so aus (ich habe noch einige Kleinigkeiten hinzugefügt, die sollten aber verständlich sein):

Code einblendenCode angehängt. Klicke hier zum Ein-/Ausblenden

code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
init()
{
        precacheString( &"^5~>^7Score: &&1" );
        precacheString( &"^5~>^7Deaths: &&1" );

        level thread onPlayerConnected(); //Funktionsaufruf
}

onPlayerConnected()
{
        while( true )
        {
                level waittill( "connected" , player );        //warten bis Spieler connected

                if( !isDefined( player.score ) )        //stats initialisieren, falls nocht nicht geschehen
                        player.score = 0;
                if( !isDefined( player.deaths ) )
                        player.deaths = 0;

                player thread onPlayerSpawned();        //Spieler führt jetzt vers. Funktionen aus
        }
}

onPlayerSpawned() //überwacht Spawns des Spielers (self = Spieler)
{
        self endon( "disconnect" ); //wenn Spieler das Spiel verlässt, beendet sich die Funktion

        while( isDefined( self ) )
        {
                self waittill( "spawned" );

                wait( 0.05 );

                if( self.pers["team"] == "spectator" )
                {
                        //kein StatsHUD für Zuschauer
                        self thread destroyStatsboard();
                }
                else
                {
                        //hier nun HUD anzeigen lassen
                        self thread drawStatsboard();
                        self thread monitorPlayer();
                }
        }
}

monitorPlayer()
{
        self endon( "disconnect" );

        score = 0;
        deaths = 0;

        while( isDefined( self ) )
        {
                update = false;

                if( score != self.score )
                        update = true;
                else if( deaths != self.deaths )
                        update = true;

                if( update )
                        self thread updateStatsboard();

                if( self.sessionstate != "playing" )
                        break;

                wait( 1.0 );
        }
}

drawStatsboard()
{
        self endon( "disconnect" );

        self thread destroyStatsboard();        //falls schon vorhanden, erst entfernen!

        //HUDs von Funktion erstellen lassen
        self.hud_stats_score = self createStatsboardLine( 0 , &"^5~>^7Score: &&1" );
        self.hud_stats_deaths = self createStatsboardLine( 1 , &"^5~>^7Deaths: &&1" );
}

createStatsboardLine( lineCount , label ) //erstellt das HUD
{
        x = -100;
        y = 0;
        vertAlign = "middle";
        horzAlign = "right";
        lineHeight = 10;

        hud = newClientHudElem( self );                //erstellt HUD das nur für 1 Spieler zu sehen ist (self = Spieler)
        hud.x = x;
        hud.y = y + ( lineCount * lineHeight );        //legt Reihe fest
        hud.vertAlign = vertAlign;                        //Alignment auf dem Bildschirm
        hud.horzAlign = horzAlign;                        //Alignment auf dem Bildschirm
        hud.archived = true;                                //.archived legt fest, ob das HUD in der Killcam zusehen ist
        hud.label = label;                                //.label beinhaltet unseren String, somit können noch mit setText() oder setValue() andere Werte mit dem selben HUD angezeigt werden
        hud setValue( 0 );

        return hud;        //und HUD wieder zurückgeben
}

updateStatsboard()
{
        if( isDefined( self.hud_stats_score ) )
                self.hud_stats_score setValue( self.score );
        if( isDefined( self.hud_stats_deaths ) )
                self.hud_stats_deaths setValue( self.deaths );
}

destroyStatsboard()
{
        if( isDefined( self.hud_stats_score ) )
                self.hud_stats_score destroy();
        if( isDefined( self.hud_stats_deaths ) )
                self.hud_stats_deaths destroy();
}


So, das spiechert ihr in eurer stats.gsc, meine liegt im Verzeichnis cod2/fs_game/mod/stats.gsc
Als letztes geht in in die maps/mp/gametypes/_callbacksetup.gsc, diese wird von allen Spieltypen verwendet, so braucht man die folgende Zeilen nicht in jede sd.gsc etc neu einfügen.
In der ersten Funktion, Callback_StartGameType ruft ihr eure stats.gsc nun auf:
php:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
Callback_StartGameType()
{
        // If the gametype has not beed started, run the startup

        level thread mod\stats::init();

        if(!isDefined(level.gametypestarted) || !level.gametypestarted)
        {
                [[level.callbackStartGameType]]();

                level.gametypestarted true// so we know that the gametype has been started up
        }
}


Soweit sollte alles klappen, viel Spaß =)

cheers Serthy
bangingbernie
Dankeschön, alter Kot...äääh Codemeister.

Immer wieder klasse, das Du Dich um ein altes Spiel noch bemühst. smile