목요일, 10월 19, 2006

Purpose: Counter-Strike(CS) 처럼 Team System 만들기

-= Counter-Strike 처럼 Team System 만들기 =-

Purpose: Counter-Strike(CS) 처럼 Team System 만들기
Author: godmode2k (godmode2k@hotmail.com | MSN IM)
Date: August. 11. 2004
Destination: 모드나라 (http://mod.zoa.to)
Source:
http://users.pandora.be/dekoffie/teamvgui.html


SDK: HL1 SDK 2.3
Code: Multyplayer Source
Tested: Steam based

(*)Special thanks to:
- Chip aka DarkKnight


Description:
지난번 제가 작성한 tutorials:
0. "자신이 만든 MOD를 STEAM 으로 돌려보기 입니다."
1. 외국 Tutorial 번역 "Creating a New VGUI Menu - Part 1"
2. "게임내에서 wave 파일을 재생하자!"
3. "Radio Command Menu 만들기(CS 처럼)"
4. "MOD 게임 바탕화면 Logo 바꾸기"
5. "HLDS로 나의 MOD Server 구성하기"

주소는: 0. http://cocowest.javasarang.net/mod/bbs/view.php?&bbs_id=hlsdk&page=&doc_num=39
1. http://cocowest.javasarang.net/mod/vgui_p1/VGUIMenuPart1.htm
2. http://cocowest.javasarang.net/mod/bbs/view.php?&bbs_id=hlsdk&page=&doc_num=49
3. http://cocowest.javasarang.net/mod/bbs/view.php?&bbs_id=hlsdk&page=&doc_num=51
4. http://cocowest.javasarang.net/mod/bbs/view.php?&bbs_id=hlsdk&page=&doc_num=53
5. http://cocowest.javasarang.net/mod/bbs/view.php?&bbs_id=hlsdk&page=&doc_num=59

[ 추가사항 ]
* Observer 코드: 이 부분은 나중에 다루기로 하겠습니다.
* Team Spawn Points: 게임 시작시 각 팀들이 map 상의 어디에서 부터 시작하는지를 설정한다.
* ScoreBoard에 Team name 출력
* 기존에 wave 재생시 자신의 PC에서만 재생이 되고 다른 사람들에겐 소리가 들리지 않았지만,
이번엔 Radio Message가 게임에 참여하고 있는 모든 client들에게 전달 될 수 있게 코딩이 되었습니다.


[ 주의 ]
code를 추가해 줄 때,

// 018killer: ... [
// 018killer: ... ]

사이 부분의 code를 추가해 주시면 됩니다

간혹 코드중에 사용하지는 않지만 주석처리된 것이 의외로 많습니다.
귀찮아서 하지 않은건 아니구요,,, ^^;
먼저 작성한 코드지만, 후에 이용할 수 있을것 같아서 그냥 놔두었습니다.
불편하시면 (***모든 주석처리(원본은 제외)***)는 삭제하셔도 무방합니다.


*** 분량이 조금 많습니다. 커피나 시원한 음료수 하나 놓고 시작하시기 바랍니다.
*** 중간에 키보드를 던지는 ,,, ㅎㅎ; 그런일이 발생하지 않았으면 하는 바람입니다.

그럼, 준비가 되셨다면 시작해 볼까요?



첫번째로 먼저 설정되어야 되는 코드
--------------------------------------------------------------
파일명: gamerules.cpp
작업위치:
CGameRules *InstallGameRules( void )
{
SERVER_COMMAND( "exec game.cfg\n" );
SERVER_EXECUTE( );
...
--------------------------------------------------------------
SERVER_COMMAND( "exec game.cfg\n" );
SERVER_EXECUTE( );
여기 바로 아래에 있는 if - else 문을 모두 주석 처리한 후 아래의 코드를 추가해 준다.

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
if ( !gpGlobals->deathmatch )
{
// No deathmatch defined but we want it anyways.
gpGlobals->deathmatch = TRUE;
return new CHalfLifeTeamplay;
}

else
{
// Deathmatch, so let's start Teamplay
return new CHalfLifeTeamplay;
}
// 018killer: Choose a Team Menu ]
==============================================================



=== 실제 Teamplay 코드를 작성하기에 앞서 Observer 코드를 추가해 준다. ===
***> 이 부분은 나중에 다루기로 하겠습니다.

파일명:
작업위치:

--------------------------------------------------------------


==============================================================



// Avobe: Observer codes
// ===================================================================================== //
// Below: Teamplay codes



--------------------------------------------------------------
파일명: player.h
작업위치: 전역변수로 선언
class CBasePlayer : public CBaseMonster { }; 윗줄에 추가해 준다.
--------------------------------------------------------------
// 018killer: HUD Command Menu [
// VGUIMenu Stuff
#define MENU_DEFAULT 1
#define MENU_TEAM 2
#define MENU_CLASS 3
#define MENU_MAPBRIEFING 4
#define MENU_INTRO 5
#define MENU_CLASSHELP 6
#define MENU_CLASSHELP2 7
#define MENU_REPEATHELP 8
// 018killer: HUD Command Menu ]
==============================================================



--------------------------------------------------------------
파일명: player.h
작업위치:
class CBasePlayer : public CBaseMonster
{
public:
int random_seed;
...
--------------------------------------------------------------
int random_seed; 위에 추가해 준다.

// 018killer: VGUI [
void ShowVGUIMenu(int iMenuID);
// 018killer: VGUI ]
==============================================================



--------------------------------------------------------------
파일명: player.cpp
작업위치: 전역변수로 선언
--------------------------------------------------------------
void LinkUserMessages( void ) {} 함수 위에 추가해 준다.

// 018killer: VGUI [
int gmsgVGUIMenu = 0;
// 018killer: VGUI ]
==============================================================



--------------------------------------------------------------
파일명: player.cpp
작업위치: void LinkUserMessages( void ) {} 함수에서 마지막 부분에 추가해 준다.
--------------------------------------------------------------
// 018killer: VGUI [
gmsgVGUIMenu = REG_USER_MSG("VGUIMenu", 1);
// 018killer: VGUI ]
==============================================================



--------------------------------------------------------------
파일명: player.cpp
작업위치: void LinkUserMessages( void ) {} 함수 다음에 추가해 준다.
--------------------------------------------------------------
// 018killer: VGUI [
// define: ShowVGUIMenu -> player.h
// nfo: void TeamFortressViewport::ShowVGUIMenu( int iMenu ) { } -> vgui_TeamFortressViewport.cpp
void CBasePlayer :: ShowVGUIMenu(int iMenuID)
{
MESSAGE_BEGIN(MSG_ONE, gmsgVGUIMenu, NULL, pev);
WRITE_BYTE( iMenuID );
MESSAGE_END();
}
// 018killer: VGUI ]
==============================================================



--------------------------------------------------------------
파일명: client.cpp
작업위치: void ClientCommand( edict_t *pEntity ) {}
--------------------------------------------------------------
// 018killer: ChooseClassMenu [
// nfo: ShowVGUIMenu -> player.cpp
else if( FStrEq(pcmd, "chooseteam") )
{
GetClassPtr( (CBasePlayer *)pev )->ShowVGUIMenu(MENU_TEAM);
}
// 018killer: ChooseClassMenu ]
==============================================================



--------------------------------------------------------------
파일명: teamplay_gamerules.h
작업위치: 전역변수로 선언
--------------------------------------------------------------
// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
#define TEAM1_NAME "Blue"
#define TEAM2_NAME "Red"
//#define TEAM1_NAME "Counter-Terrorist"
//#define TEAM2_NAME "Terrorist Force"
// 018killer: Choose a Team Menu ]
==============================================================



--------------------------------------------------------------
파일명: teamplay_gamerules.cpp
작업위치:
BOOL CHalfLifeTeamplay :: ClientCommand( CBasePlayer *pPlayer, const char *pcmd )
{

if(g_VoiceGameMgr.ClientCommand(pPlayer, pcmd))
return TRUE;

--------------------------------------------------------------
위의 if문 다음의 else if 문으로, 아래의 코드를 추가해 준다.

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
else if ( FStrEq( pcmd, "jointeam" ) )
{
int slot = atoi( CMD_ARGV(1) );

//switch( atoi( CMD_ARGV(1) ) )
switch( slot )
{
case 1: // Team Blue
JoinTeam( pPlayer, TEAM1_NAME );
break;

case 2: // Team Red
JoinTeam( pPlayer, TEAM2_NAME );
break;

case 5: // Auto-Select
// random
switch( RANDOM_LONG(1, 2) )
{
case 1:
JoinTeam( pPlayer, TEAM1_NAME );
break;

case 2:
JoinTeam( pPlayer, TEAM2_NAME );
break;
}
break;
}
}
// 018killer: Choose a Team Menu ]
==============================================================



--------------------------------------------------------------
파일명: teamplay_gamerules.cpp
작업위치:
const char *CHalfLifeTeamplay::SetDefaultPlayerTeam( CBasePlayer *pPlayer ) {}
--------------------------------------------------------------
함수안에 있는 모든 코드를 주석 처리하고, 아래 코드를 추가해 준다.

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
return NULL;
// 018killer: Choose a Team Menu ]
==============================================================



--------------------------------------------------------------
파일명: multiplay_gamerules.cpp
작업위치:
void CHalfLifeMultiplay::InitHUD( CBasePlayer *pl ) { }
--------------------------------------------------------------
이 함수 윗 line에 추가를 해준다.

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
#define TEAM1_NAME "Blue"
#define TEAM2_NAME "Red"
//#define TEAM1_NAME "Counter-Terrorist"
//#define TEAM2_NAME "Terrorist Force"
extern int gmsgTeamNames;
// 018killer: Choose a Team Menu ]
==============================================================



--------------------------------------------------------------
파일명: multiplay_gamerules.cpp
작업위치:
void CHalfLifeMultiplay::InitHUD( CBasePlayer *pl ) {
...
}
--------------------------------------------------------------
이 함수내의 끝 부분에 추가를 해준다.

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
MESSAGE_BEGIN( MSG_ONE, gmsgTeamNames, NULL, pl->edict() );
WRITE_BYTE(2);
WRITE_STRING(TEAM1_NAME);
WRITE_STRING(TEAM2_NAME);
MESSAGE_END();
// 018killer: Choose a Team Menu ]

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
pl->ShowVGUIMenu( MENU_TEAM );
// 018killer: Choose a Team Menu ]
==============================================================



--------------------------------------------------------------
파일명: teamplay_gamerules.cpp
작업위치:
void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer )
{
CHalfLifeMultiplay::InitHUD( pPlayer ); 아래에 추가
--------------------------------------------------------------
CHalfLifeMultiplay::InitHUD( pPlayer ); 윗 line 에 있는 다음의 코드는 주석처리한다.
//SetDefaultPlayerTeam( pPlayer );


아래 원본은 주석 처리해 준다.
/*
// 원본
MESSAGE_BEGIN( MSG_ONE, gmsgTeamNames, NULL, pPlayer->edict() );
WRITE_BYTE( num_teams );
for ( i = 0; i < num_teams; i++ )
{
WRITE_STRING( team_names[ i ] );
}
MESSAGE_END();
*/

Recount ... 이 코드는 그냥 놔둡니다.
char *mdls ... 이 코드는 그냥 놔둡니다.

/*
// 원본
/ /아마도 HL1 SDK 2.3 에서는 원래 있는 코드일겁니다.
// 간혹 아래의 코드를 그대로 작성하는 경우가 있는데요 주석처리하세요.

// update the current player of the team he is joining
char text[1024];
if ( !strcmp( mdls, pPlayer->m_szTeamName ) )
{
sprintf( text, "* you are on team \'%s\'\n", pPlayer->m_szTeamName );
}
else
{
sprintf( text, "* assigned to team %s\n", pPlayer->m_szTeamName );
}

ChangePlayerTeam( pPlayer, pPlayer->m_szTeamName, FALSE, FALSE );
UTIL_SayText( text, pPlayer );
int clientIndex = pPlayer->entindex();
RecountTeams();
*/
// update this player with all the other players team info
==============================================================



******************************
삭제예정 [
삭제예정 ] 은 작성하지 마세요.
******************************
///////////////////////////////////////////////////////////////////////////////////////////
// 삭제예정 [
--------------------------------------------------------------
파일명: teamplay_gamerules.cpp
작업위치:
void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer )
{
int i;
--------------------------------------------------------------
int i; 다음부터 추가를 해준다.
// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
pPlayer->ShowVGUIMenu( MENU_INTRO );
// 018killer: Choose a Team Menu ]

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
pPlayer->ShowVGUIMenu( MENU_TEAM );
// 018killer: Choose a Team Menu ]


//SetDefaultPlayerTeam( pPlayer );
CHalfLifeMultiplay::InitHUD( pPlayer );
==============================================================



--------------------------------------------------------------
파일명: teamplay_gamerules.cpp
작업위치:
void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer )
{
InitHUD( pPlayer ); 아래에 추가
--------------------------------------------------------------
작업위치 바로 아래에 추가해 준다.

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
MESSAGE_BEGIN( MSG_ONE, gmsgTeamNames, NULL, pPlayer->edict() );
WRITE_BYTE(2); // have up to 4
WRITE_STRING(TEAM1_NAME); // TEAM1_NAME
WRITE_STRING(TEAM2_NAME);
MESSAGE_END();
// 018killer: Choose a Team Menu ]



아래 원본은 주석 처리해 준다.
/*
// 원본
MESSAGE_BEGIN( MSG_ONE, gmsgTeamNames, NULL, pPlayer->edict() );
WRITE_BYTE( num_teams );
for ( i = 0; i < num_teams; i++ )
{
WRITE_STRING( team_names[ i ] );
}
MESSAGE_END();
*/

Recount ...
char *mdls ...

/*
// update the current player of the team he is joining
char text[1024];
if ( !strcmp( mdls, pPlayer->m_szTeamName ) )
{
sprintf( text, "* you are on team \'%s\'\n", pPlayer->m_szTeamName );
}
else
{
sprintf( text, "* assigned to team %s\n", pPlayer->m_szTeamName );
}

ChangePlayerTeam( pPlayer, pPlayer->m_szTeamName, FALSE, FALSE );
UTIL_SayText( text, pPlayer );
int clientIndex = pPlayer->entindex();
RecountTeams();
*/
// update this player with all the other players team info
==============================================================



--------------------------------------------------------------
파일명: teamplay_gamerules.cpp
작업위치:
void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer )
{
...
MESSAGE_END();
--------------------------------------------------------------
작업위치 바로 아래에 추가해 준다.

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
pPlayer->ShowVGUIMenu( MENU_CLASS );
// 018killer: Choose a Team Menu ]

RecountTeams();
==============================================================
// 삭제예정 ]
///////////////////////////////////////////////////////////////////////////////////////////





--------------------------------------------------------------
파일명: teamplay_gamerules.cpp
작업위치:
void CHalfLifeTeamplay::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer )
{
char text[1024];
--------------------------------------------------------------
작업위치 바로 아래에 추가해 준다.

// 018killer: Choose a Team Menu [
// Purpose: observer는 몇몇 변경이 가능 하지만, 보이지는 않기 때문에 여기에서 막아야 한다.
if( pPlayer->m_afPhysicsFlags & PFLAG_OBSERVER )
return;
// 018killer: Choose a Team Menu ]

다음 아래쪽 코드 위치에서,
* if ( !stricmp( mdls, pPlayer->m_szTeamName ) )
* return;

바로 아래쪽에 다음의 코드를 추가해 준다.

// 018killer: Choose a Team Menu [
// Purpose: observer는 몇몇 변경이 가능 하지만, 보이지는 않기 때문에 여기에서 막아야 한다.
g_engfuncs.pfnSetClientKeyValue( pPlayer->entindex(), g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName );
// 018killer: Choose a Team Menu ]
==============================================================



--------------------------------------------------------------
파일명: teamplay_gamerules.cpp
작업위치:
const char *CHalfLifeTeamplay::TeamWithFewestPlayers( void )
{
...
memset( teamCount, 0, MAX_TEAMS * sizeof(int) );
...
--------------------------------------------------------------
memset( teamCount, 0, MAX_TEAMS * sizeof(int) );
이 코드 바로 윗줄에 아래의 코드를 추가해 준다.

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
if( GetTeamIndex(TEAM1_NAME) == -1 )
return TEAM1_NAME; // Team Blue

else if( GetTeamIndex(TEAM2_NAME) == -1 )
return TEAM2_NAME; // Team Red
// 018killer: Choose a Team Menu ]
==============================================================



--------------------------------------------------------------
파일명: teamplay_gamerules.cpp
작업위치: 제일 마지막 부분에 function을 추가해 준다.
--------------------------------------------------------------
// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
// define: JoinTeam -> teamplay_gamerules.h
void CHalfLifeTeamplay :: JoinTeam(CBasePlayer *pPlayer, const char *pTeamName)
{
char text[1024];

if( FStrEq(pPlayer->m_szTeamName, pTeamName) )
{
return;
}

if( pPlayer->m_afPhysicsFlags &PFLAG_OBSERVER )
{
sprintf( text, "* %s has joined %s\n", STRING( pPlayer->pev->netname ), pTeamName );
UTIL_SayText( text, pPlayer );
UTIL_LogPrintf( "\"%s<%i>\" has joined %s\n", STRING( pPlayer->pev->netname ), GETPLAYERUSERID( pPlayer->edict() ), pTeamName );

pPlayer->pev->deadflag = DEAD_RESPAWNABLE;
ChangePlayerTeam( pPlayer, pTeamName, FALSE, FALSE );

RecountTeams();
pPlayer->Spawn();
}

else
{
sprintf( text, "* %s has joined %s\n", STRING( pPlayer->pev->netname ), pTeamName );
UTIL_SayText( text, pPlayer );
UTIL_LogPrintf( "\"%s<%i>\" has joined %s\n", STRING( pPlayer->pev->netname ), GETPLAYERUSERID( pPlayer->edict() ), pTeamName );

ChangePlayerTeam( pPlayer, pTeamName, TRUE, TRUE );

RecountTeams();
}
}
// 018killer: Choose a Team Menu ]
==============================================================



--------------------------------------------------------------
파일명: teamplay_gamerules.h
작업위치:
class CHalfLifeTeamplay : public CHalfLifeMultiplay
{
public:
--------------------------------------------------------------
public: 제일 마지막 줄에 아래의 코드를 추가해 준다.

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
virtual void JoinTeam(CBasePlayer *pPlayer, const char *pTeamName);
// 018killer: Choose a Team Menu ]
==============================================================



--------------------------------------------------------------
파일명: vgui_teammenu.cpp
작업위치:
CTeamMenuPanel::CTeamMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) :
CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall)
{
--------------------------------------------------------------
// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
pLabel->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Title_SelectYourTeam"));
"#Title_SelectYourTeam" 이 부분을 "SELECT TEAM" 이렇게 변경
// 018killer: Choose a Team Menu ]

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
m_pButtons[5]->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("#Team_AutoAssign") );
"#Team_AutoAssign" 이 부분을 "Auto" 이렇게 변경
// 018killer: Choose a Team Menu ]
==============================================================



--------------------------------------------------------------
파일명: vgui_teammenu.cpp
작업위치:
CTeamMenuPanel::CTeamMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) :
CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall)
{
...
// Team Menu buttons
for (int i = 1; i <= 5; i++)
{
...
if (i == 5)
{
--------------------------------------------------------------
if (i == 5) { } 이 코드의 윗줄에 아래의 코드를 추가해 준다.

// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
if (i == 1) // Team Blue
{
m_pButtons[1]->setBoundKey( '1' );
// void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer ) 에서
// WRITE_STRING(TEAM1_NAME); WRITE_STRING(TEAM2_NAME); 을 했기 때문에 바꿀려면
// WRITE_STRING(TEAM1_NAME); WRITE_STRING(TEAM2_NAME); 을 주석 처리하면 된다.
m_pButtons[1]->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("Blue") ); // Team2 Name
//m_pButtons[1]->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("Terrorist Force") );
m_pButtons[1]->setVisible( true );
}

if (i == 2) // Team Red
{
m_pButtons[2]->setBoundKey( '2' );
m_pButtons[2]->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("Red") ); // Team2 Name
//m_pButtons[2]->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("Counter-Terrorist Force") );
m_pButtons[2]->setVisible( true );
}
// 018killer: Choose a Team Menu ]

if( i == 5 ) { } 이 부분은 있기 때문에 skip
==============================================================



--------------------------------------------------------------
파일명: vgui_ClassMenu.cpp
작업위치:
CClassMenuPanel::CClassMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) :
CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall)
{
--------------------------------------------------------------
// 018killer: Choose a Team Menu [
// Purpose: JoinTeam
pLabel->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Title_SelectYourClass"));
"#Title_SelectYourClass" 이 부분을 "CHOOSE A CLASS" 이렇게 변경
// 018killer: Choose a Team Menu ]
==============================================================



이제 STEAM 을 통해 MOD를 실행한 후 게임상에서 'm'으로 bind된 ChooseTeam을 실행한다.
즉, 'm' key를 누른다.

그러면 화면에 VGUIMenu로 team을 선택할 수 있는 메뉴가 나온다.



=== Team Spawn Points ===
게임 시작시 각 팀들이 map 상의 어디에서 부터 시작하는지를 설정한다.
(각 팀들이 나타날(진영) 위치를 설정한다.)

--------------------------------------------------------------
파일명: subs.cpp
작업위치:
// These are the new entry points to entities.
LINK_ENTITY_TO_CLASS(info_player_deathmatch,CBaseDMStart);
LINK_ENTITY_TO_CLASS(info_player_start,CPointEntity);
LINK_ENTITY_TO_CLASS(info_landmark,CPointEntity);
아래...
--------------------------------------------------------------
// 018killer: Team Spawn Points [
// Purpose: 각 팀들이 나타날(진영) 위치를 설정한다.
// define: -> .h
LINK_ENTITY_TO_CLASS( info_player_observer, CBaseDMStart );
//LINK_ENTITY_TO_CLASS( info_player_team1, CBaseDMStart );
//LINK_ENTITY_TO_CLASS( info_player_team2, CBaseDMStart );
// 018killer: Team Spawn Points ]
==============================================================



--------------------------------------------------------------
파일명: player.cpp
작업위치:
extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer );
아래에 전역변수로 선언
--------------------------------------------------------------
// 018killer: Team Spawn Points [
// Purpose: 각 팀들이 나타날(진영) 위치를 설정한다.
// define: -> .h
extern edict_t *EntSelectTeamSpawnPoint( CBaseEntity *pPlayer );
#define TEAM1_NAME "Blue"
#define TEAM2_NAME "Red"
//#define TEAM1_NAME "Counter-Terrorist"
//#define TEAM2_NAME "Terrorist Force"
// 018killer: Team Spawn Points ]
==============================================================



--------------------------------------------------------------
파일명: gamerules.cpp
작업위치:
extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer );
아래에 전역변수로 선언
--------------------------------------------------------------
// 018killer: Team Spawn Points [
// Purpose: 각 팀들이 나타날(진영) 위치를 설정한다.
// define: -> .h
extern edict_t *EntSelectTeamSpawnPoint( CBaseEntity *pPlayer );
// 018killer: Team Spawn Points ]
==============================================================



--------------------------------------------------------------
파일명: gamerules.cpp
작업위치:
edict_t *CGameRules :: GetPlayerSpawnSpot( CBasePlayer *pPlayer ) { }
이 함수의 전체 내용은 주석 처리하고 아래의 코드를 추가한다.
--------------------------------------------------------------
// 018killer: Team Spawn Points [
// Purpose: 각 팀들이 나타날(진영) 위치를 설정한다.
// define: -> .h
edict_t *pentSpawnSpot;

if( g_pGameRules->IsTeamplay() )
//edict_t *pentSpawnSpot = EntSelectTeamSpawnPoint( pPlayer );
pentSpawnSpot = EntSelectTeamSpawnPoint( pPlayer );
else
//edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer );
pentSpawnSpot = EntSelectSpawnPoint( pPlayer );

pPlayer->pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1);
pPlayer->pev->v_angle = g_vecZero;
pPlayer->pev->velocity = g_vecZero;
pPlayer->pev->angles = VARS(pentSpawnSpot)->angles;
pPlayer->pev->punchangle = g_vecZero;
pPlayer->pev->fixangle = TRUE;

return pentSpawnSpot;
// 018killer: Team Spawn Points ]
==============================================================



--------------------------------------------------------------
파일명: player.cpp
작업위치:
edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ) { }
이 함수 위에 작성을 해 준다.
--------------------------------------------------------------
// 018killer: Team Spawn Points [
// Purpose: 각 팀들이 나타날(진영) 위치를 설정한다.
// define: -> .h
edict_t *EntSelectTeamSpawnPoint( CBaseEntity *pPlayer )
{
CBaseEntity *pSpot;
edict_t *player;

player = pPlayer->edict();
CBasePlayer *cbPlayer = (CBasePlayer *)pPlayer; // Get a CBasePlayer

pSpot = g_pLastSpawn;
// Randomize the start spot
for( int i=RANDOM_LONG(1, 5); i>0; i-- )
{
if( FStrEq(cbPlayer->m_szTeamName, "Blue") ) // Team1 Name
// 혹은 이렇게; if( FStrEq(cbPlayer->m_szTeamName, TEAM1_NAME) ) // Team1 Name
// 아래 반복되는곳에: Blue-> TEAM1_NAME, Red-> TEAM2_NAME 으로 바꾸면 된다.
//pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_team1" );
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_start" );

else if( FStrEq(cbPlayer->m_szTeamName, "Red") ) // Team2 Name
//pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_team2" );
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" );

else // not team observer
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_observer" );
}

if( FNullEnt( pSpot ) ) // skip over the null point
{
if( FStrEq(cbPlayer->m_szTeamName, "Blue") ) // Team1 Name
//pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_team1" );
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_start" );

else if( FStrEq(cbPlayer->m_szTeamName, "Red") ) // Team2 Name
//pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_team2" );
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" );

else // not team observer
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_observer" );
}

CBaseEntity *pFirstSpot = pSpot;

do
{
if( pSpot )
{
// check if pSpot is valid
if( IsSpawnPointValid( pPlayer, pSpot ) )
{
if( pSpot->pev->origin == Vector( 0, 0, 0 ) )
{
if( FStrEq(cbPlayer->m_szTeamName, "Blue") ) // Team1 Name
//pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_team1" );
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_start" );

else if( FStrEq(cbPlayer->m_szTeamName, "Red") ) // Team2 Name
//pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_team2" );
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" );

else // not team observer
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_observer" );

continue;
}

// valid pSpot, so it can be returned
goto ReturnSpot;
}
} // if

// increment pSpot

if( FStrEq(cbPlayer->m_szTeamName, "Blue") ) // Team1 Name
//pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_team1" );
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_start" );

else if( FStrEq(cbPlayer->m_szTeamName, "Red") ) // Team2 Name
//pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_team2" );
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" );

else // not team observer
pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_observer" );

} while( pSpot != pFirstSpot ); // loop if we're not back to the start

if( !FNullEnt( pSpot ) )
{
CBaseEntity *ent = NULL;

while( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL )
{
// if ent is a client, kill em (unless they are ourselves)
//if( ent->IsPlayer() && !(ent->edict() == player) )
if( ent->IsPlayer() && (ent->edict() != player) )
ent->TakeDamage( VARS(INDEXENT(0)), VARS(INDEXENT(0)), 300, DMG_GENERIC );
}

goto ReturnSpot;
}

ReturnSpot:
if( FNullEnt( pSpot ) )
{
ALERT( at_error, "PutClientServer: no spawn points on level" );
return INDEXENT(0);
}

g_pLastSpawn = pSpot;
return pSpot->edict();
}
// 018killer: Team Spawn Points ]
==============================================================



--------------------------------------------------------------
파일명: teamplay_gamerules.cpp
작업위치: 제일 마지막에 작성을 해 준다.

--------------------------------------------------------------
// 018killer: Team Spawn Points [
// Purpose: 각 팀들이 나타날(진영) 위치를 설정한다.
// define: -> .h
class CInfoTeamPlayDetect : public CBaseEntity
{
void Spawn(void)
{
UTIL_SetOrigin( pev, pev->origin );
pev->solid = SOLID_NOT;
pev->effects = EF_NODRAW;
}
};

LINK_ENTITY_TO_CLASS( info_teamplaydetect, CInfoTeamPlayDetect );
// 018killer: Team Spawn Points ]
==============================================================



--------------------------------------------------------------
파일명: halflife-018killer.fgd
작업위치:
My MOD 디렉토리에 아래의 코드를 작성하고 halflife-MY_MOD_NAME.fgd 파일로 저장을 한다.
--------------------------------------------------------------
//
// MOD_NAME: halflife-018killer.fgd file v1.0
// Author: godmode2k (godmode2k@hotmail.com | MSN IM)
// Date: August. 02. 2004
//

@PointClass = info_player_start : "Counter-terrorist start" []
@PointClass = info_player_deathmatch : "Terrorist start" []
@PointClass = info_teamplaydetect : "Teamplay Map Detector" []
@PointClass = info_player_deathmatch : "Deathmatch Start" []
==============================================================



--------------------------------------------------------------
파일명: liblist.gam
작업위치:
gamedll "dlls/mp.dll"
이 코드 윗줄에 추가를 해 준다.
--------------------------------------------------------------
mpentity "info_teamplaydetect"
==============================================================



--------------------------------------------------------------
파일명: Map 파일
작업위치:

--------------------------------------------------------------
VHE(Valve Hammer Editor)나 기타 Map tool로 .fgd 파일에서 설정한 PointClass 의 entity 들을
Map에 각각 지정한다.
아니면, Counter-Strike 에 있는 MAP으로 테스트를 한다.

여기에선 de_dust.bsp (with de_dust.wad) map을 사용했다.
==============================================================

여기까지가 Team Spawn Points에 대한 코드였습니다.





* ScoreBoard에 Team name 출력
--------------------------------------------------------------
파일명: vgui_ScorePanel.cpp
작업위치:
if ( m_iIsATeam[row] )
{
char sz2[128];

switch (col)
{
case COLUMN_NAME:
if ( m_iIsATeam[row] == TEAM_SPECTATORS )
{
sprintf( sz2, "%s", CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ) );
}
else
{
--------------------------------------------------------------
else { } 부분의 아래의 코드를
//sprintf( sz2, "%s", gViewPort->GetTeamName(team_info->teamnumber) );

다음의 코드로 바꾼다.
// 018killer: Choose a Team Menu [
// Purpose: Print Team name to Scoreboard
sprintf( sz2, "%s", CHudTextMessage::BufferedLocaliseTextString( team_info->name) );
// 018killer: Choose a Team Menu ]
==============================================================



--------------------------------------------------------------
파일명: vgui_ScorePanel.cpp
작업위치:
if (team_info->players == 1)
{
sprintf(sz2, "(%d %s)", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player" ) );
--------------------------------------------------------------
sprintf(... ) 부분을 다음의 코드로 바꾼다.

// 018killer: Choose a Team Menu [
// Purpose: Print Team name to Scoreboard
sprintf(sz2, "- %d %s", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player" ) );
// 018killer: Choose a Team Menu ]
==============================================================





-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-=-=-=-=-



-= 추가사항 =-
(*) map 설명을 보이게 할려면 그냥 map 디렉토리에 map 파일과 같은 이름의 text 파일을 만들면 된다.
이렇게 하면 VGUI에서 map 설명이 나온다.


(*) Radio Message가 게임에 참여하고 있는 모든 client들에게 전달 될 수 있게 코딩하기
*** 이부분은 필시 위에서 Team System을 만든 다음에 할 것을 권장한다. ***

--------------------------------------------------------------
파일명: client.cpp
작업위치:
...
else if( FStrEq(pcmd, "menuselect") )
{
...
--------------------------------------------------------------
else if( FStrEq(pcmd, "menuselect") )
{
// CMD_ARGV(1)은 config.cfg에서 BIND 했던 변수와 비교 검색이 안된다.
// 따라서 그냥 string으로 비교한다.
// i.g., if( FStrEq(CMD_ARGV(1), "1") ) {}
// 원래 한다면 이렇게: if( FStrEq(CMD_ARGV(1), "slot1") ) {}

// 018killer: All RadioMsgMenu / TextMenu [
//char rCmdText[128] = {0}; // chat으로 보낼 문자열 앞에 "r@" 이 문자열을 넣어 전송함으로서 Radio Command 라고 인식시킴
// 018killer: All RadioMsgMenu / TextMenu ]

switch( HUD_CommandMenu_id )
{
case Radio1MsgMenu:
ClientPrint(pev, MSG_ONE, "Radio1MsgMenu, It worked!\n");
if( FStrEq(CMD_ARGV(1), "1") )
{
//기존에 작성한 아래의 EMIT_SOUND는 주석처리를 한다.
//EMIT_SOUND(ENT(pev), CHAN_VOICE, slc[11][1], 1, ATTN_NORM);
RadioMsgSend( GetClassPtr( (CBasePlayer *)pev ), slc[11][1] );
//ClientPrint(pev, MSG_ONE, "Radio1MsgMenu 1, comic/haha3.wav, It worked!\n");

// 018killer: All RadioMsgMenu / TextMenu [
//sprintf( rCmdText, "%s", slc[11][0] ); // 여기에선 wave file 경로가 아닌 list name을 적어준다.
sprintf( rCmdText, "%c%s (RADIO): %s", 2, STRING( pEntity->v.netname ), slc[11][0] );
// print to the sending client
MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, &pEntity->v );
WRITE_BYTE( ENTINDEX(pEntity) );
WRITE_STRING( rCmdText );
MESSAGE_END();
//g_engfuncs.pfnServerPrint( rCmdText );
// 018killer: All RadioMsgMenu / TextMenu ]
}
else if( FStrEq(CMD_ARGV(1), "2") )
{
//EMIT_SOUND(ENT(pev), CHAN_VOICE, slc[53][1], 1, ATTN_NORM);
RadioMsgSend( GetClassPtr( (CBasePlayer *)pev ), slc[53][0] );
//ClientPrint(pev, MSG_ONE, "Radio1MsgMenu 2, comic/oh3.wav, It worked!\n");

// 018killer: All RadioMsgMenu / TextMenu [
//sprintf( rCmdText, "r@%s", slc[53][0] ); // 여기에선 wave file 경로가 아닌 list name을 적어준다.
sprintf( rCmdText, "%c%s (RADIO): %s", 2, STRING( pEntity->v.netname ), slc[53][0] );
// print to the sending client
MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, &pEntity->v );
WRITE_BYTE( ENTINDEX(pEntity) );
WRITE_STRING( rCmdText );
MESSAGE_END();
//g_engfuncs.pfnServerPrint( rCmdText );
}
// 018killer: All RadioMsgMenu / TextMenu ]
...,...
...,...
...,...
==============================================================



--------------------------------------------------------------
파일명: client.cpp
작업위치: 제일 마지막 부분에 function을 추가해 준다.
--------------------------------------------------------------
// 018killer: All RadioMsgMenu / TextMenu [
// Purpose: 각 client 들에게 Radio Message Command를 전달한다.
// define: RadioMsgSend -> client.h
void RadioMsgSend( CBasePlayer *pev, char *slc )
//void RadioMsgSend( entvars_t *pev, char *slc )
{
char text[128] = {0};

sprintf( text, "play %s", slc );
strcat( text, "\n" );

for( int i=0; i < gpGlobals->maxClients; i++ )
{
//CLIENT_COMMAND( GetClassPtr( (CBasePlayer *)pev )->edict(), "play comic/nol1.wav\n" );
//CLIENT_COMMAND( GetClassPtr( (CBasePlayer *)pev )->edict(), (char *)&text );
CBaseEntity *Pov = UTIL_PlayerByIndex( i );

if(Pov)
{
CBasePlayer* thisPlayer = (CBasePlayer*) Pov;

if( thisPlayer->IsPlayer() )
{
if( FStrEq(pev->m_szTeamName, thisPlayer->m_szTeamName) )
if( FStrEq(pev->m_szTeamName, "Blue") ) // Team1 Name
//if( FStrEq(pPlayer->m_szTeamName, thisPlayer->m_szTeamName) )
//if( FStrEq(pPlayer->m_szTeamName, "TEAM1") )
{
// CLIENT_COMMAND( pev->edict(), (char *)&text );
//CLIENT_COMMAND( thisPlayer->edict(), "play team1/roger.wav\n" );
//UTIL_SayText( UTIL_VarArgs( "(RADIO) %s: Roger!\n", STRING(pPlayer->pev->netname)), (CBaseEntity *)thisPlayer );
// UTIL_SayText( UTIL_VarArgs( "(RADIO) %s: %s\n", STRING(pev->pev->netname), text ), (CBaseEntity *)thisPlayer );

// CHAN_WEAPON : CHAN_VOICE
EMIT_SOUND_DYN( ENT(pev->pev), CHAN_VOICE, slc, 1, ATTN_NORM, 0, 94 + RANDOM_LONG(0,0xF) );
}

else if( FStrEq(pev->m_szTeamName, "Red") ) // Team2 Name
{
// CLIENT_COMMAND( pev->edict(), (char *)&text );
// UTIL_SayText( UTIL_VarArgs( "(RADIO) %s: %s\n", STRING(pev->pev->netname), text ), (CBaseEntity *)thisPlayer );

EMIT_SOUND_DYN( ENT(pev->pev), CHAN_VOICE, slc, 1, ATTN_NORM, 0, 94 + RANDOM_LONG(0,0xF) );
}
}
}
}
}
// 018killer: All RadioMsgMenu / TextMenu ]
==============================================================

EMIT_SOUND_DYN( ... ); 을 이용하면 상대방에게 sound를 전달합니다.
CHAN_VOICE, _WEAPON 등은 아시겠죠? 무기소리인지, voice 인지를 구별합니다.
숫자 1은 volume 입니다. 1은 최대라고 알고 있습니다.



--------------------------------------------------------------
파일명: client.h
작업위치: 제일 마지막 부분에 function을 추가해 준다.
--------------------------------------------------------------
// 018killer: All RadioMsgMenu / TextMenu [
// Purpose: 각 client 들에게 Radio Message Command를 전달한다.
extern void RadioMsgSend( CBasePlayer *pev, char *slc );
// 018killer: All RadioMsgMenu / TextMenu ]
==============================================================
extern char *slc[58][2]; 를 사용 하려면 cl_dll files Project의
source files 에 xx.cpp 파일을 만든후 cpp 파일 안에 char *slc[58][2] { .. };
이렇게 정의를 해 놓는다.
이건 처음 wave 파일 재생할 때 사용했던 것으로 이전 게시물을 보면 코드가 나와 있으니
복사해서 붙여 넣으면 된다.



--------------------------------------------------------------
파일명: saytext.cpp
작업위치:
int CHudSayText :: MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ) { }
--------------------------------------------------------------
위 함수의 윗 line에 아래의 코드를 추가해 준다.

// 018killer: Sound [
// nfo: slc is "sound_list_comic"
// source: Ansan SPEED-UP PC-ROOM
extern char *slc[58][2];
// 018killer: Sound ]
==============================================================



--------------------------------------------------------------
파일명: saytext.cpp
작업위치:
int CHudSayText :: MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ) { }
--------------------------------------------------------------
아래의 원본 코드에서 SayTextPrint( READ_STRING(), iSize - 1, client_index );
이 부분만 주석처리해 준다.

BEGIN_READ( pbuf, iSize );

int client_index = READ_BYTE(); // the client who spoke the message
// 원본
//SayTextPrint( READ_STRING(), iSize - 1, client_index );

return 1;

다음으로 //SayTextPrint( READ_STRING(), iSize - 1, client_index ); 와 return 1; 사이에
다음의 코드를 추가해 준다.

// 018killer: Sound [
// nfo: slc is "sound_list_comic"
// source: Ansan SPEED-UP PC-ROOM
char get_msgText[128] = {0};
char text[128] = {0};
int noFound = 0; // No found: 1, Found: 1

strcpy( get_msgText, READ_STRING() );

//PlaySound( "comic/new_haha1.wav", 1 );
for( int slc_i=0; slc[slc_i][1] != '\0'; slc_i++ )
{
if( strstr( get_msgText, slc[slc_i][0] ) != NULL )
//if( !strcmp( (get_msgText+2), slc[slc_i][0] ) )
//if( !strcmp( slc[slc_i][0], strstr( get_msgText, slc[slc_i][0] ) ) )
{

/* radio command 도 이곳에서 같이 처리를 했지만 이제는 사용할 필요가 없음
//if( strstr( get_msgText, "(RADIO):" ) != NULL )
if( (get_msgText[0] == 'r') && (get_msgText[1] == '@') ) // Radio Command를 사용 할 경우
//if( strstr( get_msgText, "r@" ) != NULL ) // Radio Command를 사용 할 경우
{
// Radio Command 일 경우
// (i.e., Radio Command 일 경우 chat 문자열이 "[rCmd] ..."로 시작한다.
//sprintf( text, "(RADIO) %s", READ_STRING() );
sprintf( text, "(RADIO) %s", slc[slc_i][1] );
SayTextPrint( text, strlen(text), client_index );
// 혹은, 위에서 strstr(,,, "(RADIO)").. 이 코드를 사용할 경우, SayTextPrint( get_msgText, strlen(get_msgText), client_index );

PlaySound( slc[slc_i][1], 1 );

noFound = 1;
break;
}
*/

//else if( (get_msgText[0] == '!') && (get_msgText[1] == '!') ) // chat 으로 sound를 play 할 경우
if( strstr( get_msgText, "!!" ) != NULL ) // chat 으로 sound를 play 할 경우
//else
{
SayTextPrint( get_msgText, strlen(get_msgText), client_index );
sprintf( text, "(SOUND) %s", slc[slc_i][1] );
SayTextPrint( text, strlen(text), client_index );

PlaySound( slc[slc_i][1], 1 );

noFound = 1;
break;
}
}
} // for

if( !noFound ) // No found
{
// Radio Command 나 chat 으로 sound 를 play 한 경우가 아닌 일반 chat 일 경우
SayTextPrint( get_msgText, strlen(get_msgText), client_index );
}
// 018killer: Sound ]
==============================================================




위의 saytext에서의 원리는 chat을 하기 위해 'u' key를 누르는데요, 여기에서 해당 player의 message를 각 client 들에게 보내기 전에
먼저 가로채서 문자열을 비교하게 됩니다. 바로 재생될 wave 파일 목록과 비교를 하는것 이지요.
비교를 해서 맞다면 해당되는 wave 파일을 재생해 줍니다.
얼핏보면 재상관련은 각각의 client 들에게 전달해 주는것 처럼 보이지만, 실제로는 그렇지 않습니다.

각각의 client 에서 chat message를 filtering -> 비교 해서 맞으면 자신의 pc에 저장된 wave 파일을 재생해 주는 원리입니다.
간단하죠? ^___^



'u' key를 눌러서 chat을 할 때 slc[x][0] 즉 list name을 적어주면 해당되는 wave 파일이 재생됩니다.
e.g., 'u' -> say: 이 상태에서... !!명령 하시면 됩니다. text를 구분하기 위해서 입니다.
say: !!back1
하면 back1에 해당되는 wave 파일이 재생이 되겠습니다.

이렇게 함으로써 Team 끼리 혹은 모든 player가 Radio Command/ Text sound Message를 주고 받을 수 있다.



조금 많은 분량의 코드였습니다.
모두 여기까지 오느라고 수고 많으셨습니다.
좋은 결과 있으시길 바랍니다.

그럼,.,

이상입니다.

Fix 되어야 할 문제점은 godmode2k ( godmode2k@hotmail.com | MSN IM ) 으로 보내주시기 바랍니다.

- godmode2k

Purpose: HLDS로 나의 MOD Server 구성하기

-= HLDS로 나의 MOD Server 구성하기 (기존 CS/DOD 등등, valve에서 support 해주는 MOD 포함) =-

Purpose: HLDS로 나의 MOD Server 구성하기
Author: godmode2k (godmode2k@hotmail.com | MSN IM)
Date: August. 01. 2004
Destination: 모드나라 (http://mod.zoa.to)

SDK: n/a
Code: n/a
Tested: Steam based


Description:
지난번 제가 작성한 tutorials:
0. "자신이 만든 MOD를 STEAM 으로 돌려보기 입니다."
1. 외국 Tutorial 번역 "Creating a New VGUI Menu - Part 1"
2. "게임내에서 wave 파일을 재생하자!"
3. "Radio Command Menu 만들기(CS 처럼)"
4. "MOD 게임 바탕화면 Logo 바꾸기"

주소는: 0. http://cocowest.javasarang.net/mod/bbs/view.php?&bbs_id=hlsdk&page=&doc_num=39
1. http://cocowest.javasarang.net/mod/vgui_p1/VGUIMenuPart1.htm
2. http://cocowest.javasarang.net/mod/bbs/view.php?&bbs_id=hlsdk&page=&doc_num=49
3. http://cocowest.javasarang.net/mod/bbs/view.php?&bbs_id=hlsdk&page=&doc_num=51
4. http://cocowest.javasarang.net/mod/bbs/view.php?&bbs_id=hlsdk&page=&doc_num=53


[ 주의 ]
여기에선 Steam HLDS Windows Server 만 다룹니다.



나리카스 http://www.narics.net (PDS->Server-> Steam HLDS Windows (Full Install) 581.5KB)나
valve site에서 hlds_updatetool.exe 파일을 받는다.
즉, Steam HLDS Windows (스팀용 Half-Life MOD Server).

Platform: Windows
파일명: hlds_updatetool.exe

설치는 간단하다. Next 만 누르면 된다.
설치가 끝났으면 아래와 같은 작업을 해준다.


먼저 Server 를 만들기 위해(master server에 server IP를 등록하기 위한 username) 계정을 만든다.


1. 계정만들기

C:\HLServer> HldsUpdateTool.exe -command create -username "생성할_ID" -email 이메일 -password "비번" -question "질문?" -answer "답변"

e.g.,
C:\HLServer> HldsUpdateTool.exe -command create -username "hehe" -email hehe@hehe.com -password "1234" -question "hehe가 뭐게?" -answer "뭐긴, hehe지"
Checking bootstrapper version ...
Creating Account
Account Created successfully


2. cstrike MOD update 하기 (cstrike 와 같은 기본 구조를 복사하기 위함)

C:\HLServer> HldsUpdateTool.exe -command update -game cstrike -dir c:\hlds -username "생성할_ID" -email 이메일 -password "비번"

e.g.,
C:\HLServer> HldsUpdateTool.exe -command update -game valve -dir c:\hlds -username "hehe" -email hehe@hehe.com -password "1234"
C:\HLServer> HldsUpdateTool.exe -command update -game cstrike -dir c:\hlds -username "hehe" -email hehe@hehe.com -password "1234"
Checking bootstrapper version ...
Updating Installation
Downloading: .\cstrike\tswad.wad
...
HLDS installation up to date


3. c:/hlds/cstrike 디렉토리를 c:/hlds에 "My_MOD_Name" 으로 복사를 한다.


4. c:/hlds/My_MOD_Name/ 에 아래 3개의 파일은 주의하자.

liblist.gam - 여러분이 만든 MOD에서 지정한 liblist.gam 파일을 이곳에 복사 해놓는다. (덮어씌우거나 수정)
mapcycle,txt - 이 파일은 맵 파일의 확장자 (.BSP)를 뺀 파일 이름만 넣어 주도록 한다. (e.g., de_dust)
motd.txt - the Message Of The Day의 약자로 메시지를 넣어두면 된다.

이 외의 파일들은 모두 삭제를 하거나 이름을 바꾸어 놓는다. 왠만하면 삭제하자.
C:\HLServer> HldsUpdateTool.exe -command update -game My_MOD_Name -dir c:\hlds -username "hehe" -email hehe@hehe.com -password "1234"


5. 서버 시작
c:\hlds 로 이동을 한 후 다음 작업을 해줌으로써 Server를 running 하게 된다.

C:\hlds> hlds -game 모드_이름 -port 포트_번호 +map 맵_이름 +maxplayers 총_players
만약 서버를 구동한 PC에서 play를 하고 싶다면 port 번호를 27015 를 제외한 다른 번호로 해주자.
e.g., -port 27016


e.g.,
- GUI Mode
C:\hlds> hlds -game cstrike -port 27015 +map de_dust +maxplayers 4 // Server 전용

- Console Mode (Recommended)
C:\hlds> hlds -console -game cstrike -port 27015 +map de_dust +maxplayers 4 // Server 전용


My_MOD
C:\hlds> hlds -console -game My_MOD_Name -port 27015 +map de_dust +maxplayers 4 // Server 전용
C:\hlds> hlds -console -game My_MOD_Name -port 27016 +map de_dust +maxplayers 4 // Server 겸 Play 할 수 있음

Purpose: MOD 게임 바탕화면 Logo 바꾸기

CS 그림은 제가 포토샾에서 한번 나열해 보았습니다.
그림에서 '마시마로' 인형은 제가 logo 를 바꾼 후 게임을 실행해서 screenshot을 만들어 놓은것입니다.


Purpose: MOD 게임 바탕화면 Logo 바꾸기
Author: godmode2k (godmode2k@hotmail.com | MSN IM)
Date: July. 26. 2004
Destination: 모드나라 (http://mod.zoa.to)

Description:
스팀으로 CS 를 실행하면 게임 메뉴가 보일때 바탕화면이 CS logo로 되어있습니다.
다 아는 내용일 수도 있는데요...
방금까지 포토샾으로 노가다(?)를 좀 해서 만들어 보았습니다.

logo는
C:/Sierra/Steam/SteamApps/Steam_ID_(스팀계정)/half-life/My_MOD_NAME_(모드)/resource/background/ 에 있습니다.
참고로, GCF 파일 구조를 보면 그렇게 되어있습니다.
따라서 저도 그렇게 저의 MOD 디렉토리의 resource/ 디렉토리 안에 background/ 디렉토리를 만들었습니다.

CS의 GCF 파일에서 background/ 디렉토리를 보게 되면 .TGA 파일이 12개가 보입니다.
해상도는 800x600 이며, 256x256 6개, 32x256 2개, 256x88 3개, 32x88 1개 총 12개의 이미지가 있습니다.

256x256 이미지: X=9.0, Y=9.0 / W=9.03 cm, H=9.03 cm / Resolution W=256, H=256
256x88 이미지: W=9.03 cm, H=3.10 cm / Resolution W=256, H=88

32x256 이미지: W=9.03 cm, H=1.13 cm / Resolution W=32, H=256
32x88 이미지: W=1.13 cm, H=3.10 cm / Resolution W=32, H=88


아직 모르시는 분은 포토샆으로 잘 해보시기 바랍니다. ^___^
작업한 내용의 screenshot을 첨부했습니다.

저는 한번 해보니 그렇게 힘들지는 않았지만 조금은 답답하더군요...
시간이 되면 800x600 이미지면 자동으로 잘라주는 코드를 짜야겠습니다.

번복되는 작업을 일일이 하면 정말 비효율적이지요...
혹시나 만들게 되면 올려드리겠습니다.
아니,,, 벌써 누가 만들었는지도 모르겠네요...

그래도 한번 만들어 보죠...

그럼.,.

- godmode2k

Purpose: Command Menu 만들기(CS 처럼)

Command Menu 만들기(CS 처럼)

Purpose: Command Menu 만들기(CS 처럼)
Author: godmode2k (godmode2k@hotmail.com | MSN IM)
Date: July. 25. 2004
Destination: 모드나라 (http://mod.zoa.to)
Source: http://www.thewavelength.net/forums/oldthreads/007197.html
http://forums.bots-united.com/archive/index.php/t-1617
http://u.glassfish.net/~elf/Botman_Forum/2%20Bot%20developer's%20discussions/209.txt
http://u.glassfish.net/~elf/Botman_Forum/2%20Bot%20developer's%20discussions/3034.txt
http://www.thewavelength.net/forums/oldthreads/000248.html

SDK: HL1 SDK 2.3
Code: Single-Player Source
Tested: Steam based

(*)Special thanks to:
- botman @ http://www.bots-united.com
- daas Lead Programer|Liquid Fire Productions @ http://www.planethalflife.com/assignment





Description:
이 예제는 지난번 tutorial "게임내에서 wave 파일을 재생하자!"
주소는: http://cocowest.javasarang.net/mod/bbs/view.php?&bbs_id=hlsdk&page=&doc_num=49
에서 작성을 하신 다음에(필히 참조) 아래의 내용을 보시면 이해가 빠르실 것입니다.

[ 주의 ]
* 여기에서의 라디오 메시지는 자신의 PC에서 밖엔 소리가 나지 않습니다.
code를 추가해 줄 때,

// CMD_MENU: RadioMenu [
// CMD_MENU: RadioMenu ]

사이 부분의 code를 추가해 주시면 됩니다

그럼 code를 작성해 보겠습니다.




파일명: C:/Games/Sierra/Steam/SteamApps/Steam_ID_(스팀계정)/half-life/My_MOD_NAME_(모드)/config.cfg
code:
CMD_MENU: RadioMenu [
bind "z" "radio1"

- slot0, 1, 2, 3, 9가 있는지 확인. 없다면 추가 (대부분 1 - 5 까지는 있을 것입니다.)
bind "0" "slot10"
bind "1" "slot1"
bind "2" "slot2"
bind "3" "slot3"
bind "4" "slot4"
bind "5" "slot5"
bind "6" "slot6"
bind "7" "slot7"
bind "8" "slot8"
bind "9" "slot9"
CMD_MENU: RadioMenu ]

- 그런데, 써놓고 보니 1 - 5 까지만 있어도 문제가 없어 보입니다.
- 마지막에 보시면 아시겠지만 slot1 - slot10 이라는 string을
SDK의 "menuselect" 변수에서 체크를 못합니다. 그래서 숫자를 문자로 사용합니다.
"1", "2", ... 이렇게 말이죠...



파일명: player.h
함수위치:
class CBasePlayer : public CBaseMonster
{
public:

code:
// CMD_MENU: RadioMenu [
void UTIL_ShowMenu( edict_t *pEdict, int slots, int displaytime, bool needmore, char *pText );
// CMD_MENU: RadioMenu ]



파일명: player.cpp
함수위치: 전역변수로 선언
code:
// CMD_MENU: RadioMenu [
int gmsgShowRadioMsgMenu = 0;
// CMD_MENU: RadioMenu ]



파일명: player.cpp
함수위치:
void LinkUserMessages( void )
{
...
}
이 함수 아래
code:
// CMD_MENU: RadioMenu [
void CBasePlayer :: UTIL_ShowMenu( edict_t *pEdict, int slots, int displaytime, bool needmore, char *pText )
{
if (gmsgShowRadioMsgMenu == 0)
gmsgShowRadioMsgMenu = REG_USER_MSG( "ShowMenu", -1 );

while (strlen(pText) >= 64)
{
MESSAGE_BEGIN(MSG_ONE, gmsgShowRadioMsgMenu, NULL, pEdict);
WRITE_SHORT(slots);
WRITE_CHAR(displaytime);
WRITE_BYTE(1);

for (int i = 0; i <= 63; i++)
WRITE_CHAR(pText[i]);

MESSAGE_END();

pText += 64;
}

MESSAGE_BEGIN(MSG_ONE, gmsgShowRadioMsgMenu, NULL, pEdict);
WRITE_SHORT(slots);
WRITE_CHAR(displaytime);
WRITE_BYTE(needmore);
WRITE_STRING(pText);
MESSAGE_END();
}
// CMD_MENU: RadioMenu ]



파일명: client.cpp
함수위치: 전역변수로 선언
// CMD_MENU: RadioMenu [
char *slc_menu = {
"Grunt Radios\n\n\
1. Enemy spotted!!\n\
2. Assault!\n\
3. Move spotted!!\n\
4. Move in!\n\
5. I'm going to check!\n\
6. Area clear!\n\
7. Affirmitive! or Yes, sir!\n\
8. Negative!\n\
9. All guys are safe!\n\
0. Cancel\
"
};
// CMD_MENU: RadioMenu ]



파일명: client.cpp
함수위치:
void ClientCommand( edict_t *pEntity )
{
이 함수 중간에 추가

code:
...
// CMD_MENU: RadioMenu [
else if( FStrEq(pcmd, "radio1") )
{
//EMIT_SOUND(ENT(pev), CHAN_VOICE, slc[0][1], 1, ATTN_NORM);
GetClassPtr( (CBasePlayer *)pev )->UTIL_ShowMenu( pEntity, 0x3FF, -1, FALSE, slc_menu );
//ClientPrint(pev, MSG_ONE, "radio1 worked!\n");


// function: UTIL_ShowMenu( pEntity, 0x1F, -1, FALSE, slc_menu );
//
// 0x3FF: 1024(d), 0-9 ( 참고: 10진수 1024가 16진수로 0x3FF 입니다. 1024란 숫자는 0 - 9 까지 개수인 10 입니다. )
// 0x1F: corresponds to keys 1-5 (bit 0 through bit 4)
// FALSE: is used to indicate that there isn't any more menu text after this one.
// If you are sending a long menu (>120 bytes)
// it will need to be broken up into parts sending TRUE for the first parts and FALSE for the last one.

/*
// source: http://www.thewavelength.net/forums/oldthreads/000248.html
// code by daas
// slot 개수를 Hexa code로 구하기
#include

int main(void)
{
int num;
int out;
int total;

printf( "Enter Amount Menu Options : " );
scanf( "%d", &num );

out = 1;
total = out;

for( int x = 1; x {
out = out * 2;
total += out;
}

printf( "bitValidSlots: %d\n", total );

return 0;
}
*/
}
else if( FStrEq(pcmd, "menuselect") )
{
// CMD_ARGV(1)은 config.cfg에서 BIND 했던 변수와 비교 검색이 안된다.
// 따라서 그냥 string으로 비교한다.
// i.g., if( FStrEq(CMD_ARGV(1), "1") ) {}

if( FStrEq(CMD_ARGV(1), "1") )
{
EMIT_SOUND(ENT(pev), CHAN_VOICE, slc[8][1], 1, ATTN_NORM);
ClientPrint(pev, MSG_ONE, "1, It worked!\n");
}
else if( FStrEq(CMD_ARGV(1), "2") )
{
EMIT_SOUND(ENT(pev), CHAN_VOICE, slc[9][1], 1, ATTN_NORM);
ClientPrint(pev, MSG_ONE, "2, It worked!\n");
}
else if( FStrEq(CMD_ARGV(1), "3") )
{
ClientPrint(pev, MSG_ONE, "3, It worked!\n");
}
else if( FStrEq(CMD_ARGV(1), "4") )
{
ClientPrint(pev, MSG_ONE, "4, It worked!\n");
}
else if( FStrEq(CMD_ARGV(1), "5") )
{
ClientPrint(pev, MSG_ONE, "5, It worked!\n");
}
else if( FStrEq(CMD_ARGV(1), "6") )
{
ClientPrint(pev, MSG_ONE, "6, It worked!\n");
}
else if( FStrEq(CMD_ARGV(1), "7") )
{
ClientPrint(pev, MSG_ONE, "7, It worked!\n");
}
else if( FStrEq(CMD_ARGV(1), "8") )
{
ClientPrint(pev, MSG_ONE, "8, It worked!\n");
}
else if( FStrEq(CMD_ARGV(1), "9") )
{
ClientPrint(pev, MSG_ONE, "9, It worked!\n");
}
else if( FStrEq(CMD_ARGV(1), "10") )
{
ClientPrint(pev, MSG_ONE, "0, It worked!\n");
}
}
// CMD_MENU: RadioMenu ]

이제 스팀으로 MOD를 실행하시고,
게임화면에서 'z' 키를 눌러 보세요.
그러면 Counter-Strike 와 같은 라디오 메시지 메뉴가 나올것입니다.
이제 숫자를 눌러주시면 사운드가 play 될 것입니다.

여기에서의 라디오 메시지는 자신의 PC에서 밖엔 소리가 나지 않습니다.

이상입니다.
수고하셨습니다.

- godmode2k

Purpose: 게임내에서 wave 파일을 재생하자!

Purpose: 게임내에서 wave 파일을 재생하자!
Date: July. 21. 2004
Source: 모드나라 (http://mod.zoa.to)
Author: godmode2k (godmode2k@hotmail.com | MSN IM)
HL1 SDK: 2.3


Description:
가끔 국내 CS 1.5 서버에 들어가 보면(외국은 모르겠습니다만,) 채팅창에 어떤 글을 써 넣으면 재미있는 목소리가 나오지요?
물론 Admin MOD (?) 란 프로그램을 사용하면 된다고 알고는 있습니다만, 저는 직접해보기로 했습니다.

나중에 분명 wave 파일을 재생할 날이 있을꺼란 생각에 테스트겸 해봅니다.

저는 HL1 SDK 코드를 잘 알지는 못합니다만, 한나절 투자끝에 되는군요...
wave 출력해주는 함수는 player.cpp 파일을 참고했습니다.
의외로 player.cpp 파일은 참고할 만한 코드가 상당히 많습니다.



[ 주의 ]
* 여기에서의 라디오 메시지는 자신의 PC에서 밖엔 소리가 나지 않습니다.
code를 추가해 줄 때,

// slc 시작 [
// slc 끝 ]

사이 부분의 code를 추가해 주시면 됩니다.

그럼 시작하겠습니다. ^___^


STEP 1:
play 가능한 wav 파일을
C:/Games/Sierra/Steam/SteamApps/Steam_ID(스팀 아이디)/half-life/My_MOD_NAME(나의 모드)/sound 안에 넣어줍니다.
여기에선 sound/comic/ 에 wav 파일을 넣고 테스트를 할 것입니다.

지료실에 사용된 wave 파일을 올려 놓았습니다.
파일명: comic.zip
이 code에서 사용한 wave 파일들 입니다.
wave 파일은 제가 CS 1.5 할 당시 Ansan SPEED-UP PC방 서버에서 사용했던 wave 파일들입니다.
sound/misc 쪽에 다운로드 되어있더군요...

STEP 2:
wav 파일을 준비가 끝났으면 코드 작업으로 들어가 보겠습니다.
hl project에서 client.cpp 파일을 열고 전역변수로 아래와 같이 wav 파일 리스트를 만들어줍니다.

작업위치: "hl files"->client.cpp

// slc 시작 [
// nfo: slc is "sound_list_comic"
// source: Ansan SPEED-UP PC-ROOM from Ansan, South of Korea
char *slc[58][2] = {
{"back1", "comic/back1.wav"},
{"back2", "comic/back2.wav"},
{"back3", "comic/back3.wav"},
{"camp1", "comic/camp1.wav"},
{"camp2", "comic/camp2.wav"},
{"cmj2", "comic/cmj2.wav"},
{"cmj3", "comic/cmj3.wav"},
{"come1", "comic/come1.wav"},
{"come2", "comic/come2.wav"},
{"haha1", "comic/haha1.wav"},
{"haha2", "comic/haha2.wav"},
{"haha3", "comic/haha3.wav"},
{"haha4", "comic/haha4.wav"},
{"jj3", "comic/jj3.wav"},
{"jj4", "comic/jj4.wav"},
{"jja1", "comic/jja1.wav"},
{"jja2", "comic/jja2.wav"},
{"juk1", "comic/juk1.wav"},
{"kmulti", "comic/kmulti.wav"},
{"ne1", "comic/ne1.wav"},
{"ne2", "comic/ne2.wav"},
{"nol1", "comic/nol1.wav"},
{"nol2", "comic/nol2.wav"},
{"obba", "comic/obba.wav"},
{"oh1", "comic/oh1.wav"},
{"oh2", "comic/oh2.wav"},
{"oh3", "comic/oh3.wav"},
{"oh4", "comic/oh4.wav"},
{"sap1", "comic/sap1.wav"},
{"good1", "comic/good1.wav"},
{"move01", "comic/move01.wav"},
{"move02", "comic/move02.wav"},
{"new_camp1", "comic/new_camp1.wav"},
{"new_camp2", "comic/new_camp2.wav"},
{"new_camp3", "comic/new_camp3.wav"},
{"new_dak1", "comic/new_dak1.wav"},
{"new_dak2", "comic/new_dak2.wav"},
{"new_dak3", "comic/new_dak3.wav"},
{"new_db1", "comic/new_db1.wav"},
{"new_db2", "comic/new_db2.wav"},
{"new_haha1", "comic/new_haha1.wav"},
{"new_haha2", "comic/new_haha2.wav"},
{"new_haha3", "comic/new_haha3.wav"},
{"new_jja1", "comic/new_jja1.wav"},
{"new_jja2", "comic/new_jja2.wav"},
{"new_jja3", "comic/new_jja3.wav"},
{"new_mo1", "comic/new_mo1.wav"},
{"new_mo2", "comic/new_mo2.wav"},
{"new_ne1", "comic/new_ne1.wav"},
{"new_ne2", "comic/new_ne2.wav"},
{"new_ne3", "comic/new_ne3.wav"},
{"new_oh1", "comic/new_oh1.wav"},
{"new_oh2", "comic/new_oh2.wav"},
{"new_oh3", "comic/new_oh3.wav"},
{"new_zot1", "comic/new_zot1.wav"},
{"new_zot2", "comic/new_zot2.wav"},
{"sprayer", "comic/sprayer.wav"},
{'\0', '\0'}
};
// slc 끝 ]


STEP 3:
이젠 리스트에 있는 wav 파일을 play 해주는 code를 작성해 보겠습니다.

작업위치: "hl files"->client.cpp, go to line 224

약 224 line을 보게 되면 몇 line 윗쪽에 아래와 같은 함수를 볼 수 있습니다.
void Host_Say( edict_t *pEntity, int teamonly )
{

우리는 이 함수 안에 code를 작성할 것입니다.
이 함수 시작 위치에서 (224 line) 지역변수가 선언된 곳에 아래의 code를 작성해 줍니다.

...
const char *cpSayTeam = "say_team";
const char *pcmd = CMD_ARGV(0);

// slc 시작 [
char get_text[128] = {0}; // 채팅창에 쓴 text 원본을 가져오기
// slc 끝 ]

// We can get a raw string now, without the "say " prepended
if ( CMD_ARGC() == 0 )
return;
...


아래로 65 line을 보면 다음과 같은 code가 있습니다. 추가해 줍니다.
...
strcat( text, p );
strcat( text, "\n" );

// slc 시작 [
// 변경된 값이 아닌 원본을 copy
strcpy( get_text, p );
get_text[sizeof(get_text)+1] = '\0';
// slc 끝 ]


player->m_flNextChatTime = gpGlobals->time + CHAT_INTERVAL;
...

아래로 39 line 정도 내려가면 다음과 같은 코드가 있습니다. 추가해 줍니다.

// echo to server console
g_engfuncs.pfnServerPrint( text );


// slc 시작 [
//
// Purpose: Sound 출력
// nfo: sound 출력시 edict_t 나 entvars_t의 pointer를 얻어와야 한다.
// i.e.,: edict_t *pev; or entvars_t *pev;
// EMIT_SOUND(ENT(pev), CHAN_VOICE, "comic/new_ne2.wav", 1, ATTN_NORM);
//
ClientPrint(pev, MSG_ONE, "Preparing to play sound...\n");
for( int slc_i=0; slc[slc_i][1] != '\0'; slc_i++ )
{
if( !strcmp( get_text, slc[slc_i][0] ) )
{
ClientPrint(pev, MSG_ONE, "Playing sound...\n");
ClientPrint(pev, MSG_ONE, "get_text(.wav file) = %s\n", (char *)&get_text);

EMIT_SOUND(ENT(pev), CHAN_VOICE, slc[slc_i][1], 1, ATTN_NORM);
}
}
//
// slc 끝 ]


char * temp;
if ( teamonly )
temp = "say_team";


STEP 4:
어떠한 wav 파일들을 play 하기 위해서는 반드시 Precache 되어야 합니다.
내 맘대로 편하게 하면 좋겠지만, 규칙을 따르지요. 당연한거 아닌가요? ㅎㅎ

작업위치: "hl files"->client.cpp, go to line 699

STEP 3 까지 작업이 끝났다면 그 client.cpp code에서 약 699 line으로 내려가면 다음과 같은 함수가 있습니다.

void ClientPrecache( void )
{

이 함수가 wav 파일을 Precache 해주는 함수입니다.
따라서 여기에서 사용할 모든 wav 파일을 지정해 줍니다.
우리는 제일 끝 부분에 추가를 해주겠습니다.

다시 한번 말씀 드리지만, 여기에서의 wav 파일 경로는,
C:/Games/Sierra/Steam/SteamApps/Steam_ID(스팀 아이디)/half-life/My_MOD_NAME(나의 모드)/sound 입니다.
즉, sound/comic/ 에 wav 파일을 넣고 테스트를 합니다.
때문에 PRECACHE_SOUND(" xxx.wav "); .wav 확장자를 포함한 wave 파일 이름의 경로를 정확히 지정해야 합니다.

여기에선 sound/comic/에 wav 파일이 있기 때문에 다음과 같이 합니다.
PRECACHE_SOUND("comic/new_ne2.wav");

모든 wav 파일은 sound 디렉토리 안에서 찾기 때문에 "sound/" 는 빼는것에 주의!
만약 sound/test/my_sound 라면,
PRECACHE_SOUND("test/my_sound/new_ne2.wav"); 이렇게 하면 됩니다.

이제 아래와 같이 code를 추가해 줍니다.

...
PRECACHE_SOUND("player/geiger1.wav");

// slc 시작 [
// nfo: gcf 파일이라고 해도 MOD 디렉토리에 sound 디렉토리가 있다면 그곳도 참조한다.
PRECACHE_SOUND("comic/back1.wav");
PRECACHE_SOUND("comic/back2.wav");
PRECACHE_SOUND("comic/back3.wav");
PRECACHE_SOUND("comic/camp1.wav");
PRECACHE_SOUND("comic/camp2.wav");
PRECACHE_SOUND("comic/cmj2.wav");
PRECACHE_SOUND("comic/cmj3.wav");
PRECACHE_SOUND("comic/come1.wav");
PRECACHE_SOUND("comic/come2.wav");
PRECACHE_SOUND("comic/haha1.wav");
PRECACHE_SOUND("comic/haha2.wav");
PRECACHE_SOUND("comic/haha3.wav");
PRECACHE_SOUND("comic/haha4.wav");
PRECACHE_SOUND("comic/jj3.wav");
PRECACHE_SOUND("comic/jj4.wav");
PRECACHE_SOUND("comic/jja1.wav");
PRECACHE_SOUND("comic/jja2.wav");
PRECACHE_SOUND("comic/juk1.wav");
PRECACHE_SOUND("comic/kmulti.wav");
PRECACHE_SOUND("comic/ne1.wav");
PRECACHE_SOUND("comic/ne2.wav");
PRECACHE_SOUND("comic/nol1.wav");
PRECACHE_SOUND("comic/nol2.wav");
PRECACHE_SOUND("comic/obba.wav");
PRECACHE_SOUND("comic/oh1.wav");
PRECACHE_SOUND("comic/oh2.wav");
PRECACHE_SOUND("comic/oh3.wav");
PRECACHE_SOUND("comic/oh4.wav");
PRECACHE_SOUND("comic/sap1.wav");
PRECACHE_SOUND("comic/good1.wav");
PRECACHE_SOUND("comic/move01.wav");
PRECACHE_SOUND("comic/move02.wav");
PRECACHE_SOUND("comic/new_camp1.wav");
PRECACHE_SOUND("comic/new_camp2.wav");
PRECACHE_SOUND("comic/new_camp3.wav");
PRECACHE_SOUND("comic/new_dak1.wav");
PRECACHE_SOUND("comic/new_dak2.wav");
PRECACHE_SOUND("comic/new_dak3.wav");
PRECACHE_SOUND("comic/new_db1.wav");
PRECACHE_SOUND("comic/new_db2.wav");
PRECACHE_SOUND("comic/new_haha1.wav");
PRECACHE_SOUND("comic/new_haha2.wav");
PRECACHE_SOUND("comic/new_haha3.wav");
PRECACHE_SOUND("comic/new_jja1.wav");
PRECACHE_SOUND("comic/new_jja2.wav");
PRECACHE_SOUND("comic/new_jja3.wav");
PRECACHE_SOUND("comic/new_mo1.wav");
PRECACHE_SOUND("comic/new_mo2.wav");
PRECACHE_SOUND("comic/new_ne1.wav");
PRECACHE_SOUND("comic/new_ne2.wav");
PRECACHE_SOUND("comic/new_ne3.wav");
PRECACHE_SOUND("comic/new_oh1.wav");
PRECACHE_SOUND("comic/new_oh2.wav");
PRECACHE_SOUND("comic/new_oh3.wav");
PRECACHE_SOUND("comic/new_zot1.wav");
PRECACHE_SOUND("comic/new_zot2.wav");
PRECACHE_SOUND("comic/sprayer.wav");
// slc 끝 ]

if (giPrecacheGrunt)
UTIL_PrecacheOther("monster_human_grunt");
}


STEP 5:
이제는 마지막으로 테스트만 하면 되겠습니다.
STEAM 으로 자신의 MOD를 실행하고 play 상태에서 chat 명령인 'y' 를 누르면
"say: " 이렇게 나옵니다.

이때 char *scl[y][x] = { {"wav_name", "wav_file.wav" }; 에서 정의 해줬던 이름을 적어줍니다.
여기에선 "back1"을 하겠습니다.

즉, "say: back1" 이라고 하면,
comic/back1.wav 파일이 재생 됩니다.

여기까지 입니다.
수고 하셨습니다.

오류 정정은 아래의 email로 보내주세요.

godmode2k@hotmail.com (MSN IM)

이상입니다.

- godmode2k

Purpose: Creating a New VGUI Menu - Part 1

Creating a New VGUI Menu - Part 1
Date posted: Jun 09 2003
User Rating: 5 out of 5.0
Number of views: 2392
Number of comments: 2
Description: And getting it to show in your mod

먼저 이 분서의 저작권의 모든 권리는 rkzad님께 있습니다.
이 문서의 출처는 http://articles.thewavelength.net 입니다.

원문 Author: rkzad
번역 Author: godmode2k@hotmail.com (MSN IM)
Released date: July. 14. 2004 (KST)

이 문서는 Half-Life1 Modification을 위한 것으로 화면에 VGUI Menu를 보여 주고 control 하는데 필요한 코드를 잘 제시해 주고 있습니다. 하지만, 작성된 환경이 Half-Life SDK 2.x 로 추정됩니다. 따라서 Half-Life SDK 2.3을 사용하시는 분들은 정확한 line을 찾아야 합니다. 그렇지만, SDK가 2.3으로 version up 되면서 이부분이 바뀐것은 아니니 걱정하지마세요... ^___^

제가 이 문서를 번역할 때(정확히 하면 설명을 한국식으로 한다는 거죠...) 제가 사용한 Half-Life SDK가 2.3 이기 때문에 정확한 line을 사용하도록 하겠습니다. 또한 Half-Life SDK 2.3 아래의 버전의 line도 같이 사용하도록 하겠습니다.

* line이 제시되면 그 라인 바로 아래에 코드를 추가 해주시면 되겠습니다.
* 작업경로와 line 번호를 꼭 참고하셔야 합니다.
* 번역시 나름대로 내용을 추가 또는 의역을 하겠습니다.
* 또한, 이곳에서 사용된 코드는 모두 Single-Player Source 기반임을 알려드립니다.

제가 Single-Player Source에서 테스트 해보고 나서 작성하는것이기 때문에 아직 Multiplayer Source에서는 되는지 모르겠습니다.

번역시작:
이제 오늘 할 일을 정확히 알려주겠습니다. 먼저, 간단한 메뉴를 만들것입니다. 오직 'cancel' 버튼만. 후에, 우리는 명령을 전달할 좀더 많은 button을 추가하고 모든 종류의 멋진것들을 할것입니다. 근데, 우리가 몇몇 VGUI와 함께 작업하는데 있어서, 여러분의 코드내에서 이 문서에 나온 코드를 사용해 정확히 시도? -사용하려면- mp/hl.dll 과 client.dll, 이 두 파일과 같은 코드를 만들 수 있는 Compiler가 있어야 합니다. 제가 이 두 파일을 만들 수있다고 확신하고 가장 권장되는 Compiler중 하나가 Microsoft Visual C++ 6 (MSVC) 입니다.

이제 시작하기에 앞서 조그만한 작업을 하나 하겠습니다.
Half-Life1(이하 하프 혹은 HL1) 이나 Counter-Strike 상용판(이하 카스 혹은 CS), STEAM(이하 스팀 혹은 STEAM)에서 여러분의 Modification(이하 모드 혹은 MOD)을 돌리려면 liblist.gam 파일이 필요합니다.
대부분 맞게 했을 수도 있겠습니다만, 혹시나 하는 생각에...

liblist.gam 파일의 속성에서 다음과 같이 되어있는지 확인하시기 바랍니다.

[원문]
Now, let me tell you exactly what we're gonna be doing today. First, we will create a very basic menu. Only a cancel button. Later on, we will add more buttons sending commands and doing all sorts of nifty things. So, because we are working with some VGUI, if you want to be able to actually try this out in your own code, make sure you have a compiler that will be able to code both the mp/hl.dll and the client.dll. One compiler that I am certain can do both, and is most recommended, is Microsoft Visual C++ 6 (MSVC). Next, make sure that you're liblist.gam has the following inside:

파일명: liblist.gam

CODE (liblist.gam)
svonly "0"
cldll "1"


위의것들은 단지 여러분의 MOD가 client.dll 파일을 찾기위해 필요한 것입니다.
덧붙이자면, dll을 "your_mod_folder/cl_dlls/client.dll"에서 찾는 동안 mp/hl.dll을 찾게 됩니다. dll 파일이 어디에 있든 여러분이 "gamedll" 파라미터에서 정의해 놓은 대로 찾을 것입니다.
liblist.gam 에 대해서는 이쯤하면 된것 같군요...(어디에서든지 liblist.gam에 대한 많은 tutorial 들은 찾을 수 있을것입니다.)
그럼 코딩을 해볼까요!

그럼 mp/hl Workspace를 열고, player.h 파일을 열겠습니다.

[참고]
------------------------------------------
client.dll: "cl_dll Workspace" -> "cl_dll files"
mp/hl.dll : "dlls Workspace" -> "hl files"
------------------------------------------------

여기에서는(작업경로) "dlls Workspace"에서 "hl files"->player.h
HL1 SDK 2.3: go to line 95
HL1 SDK 2.x: go to line 74
그리고 아래의 내용을 추가해 주세요.

[원문]
These are just some necessary things to make sure your mod will look for a client.dll also. It will search for this dll in "your_mod_folder/cl_dlls/client.dll", while it will look for your mp/hl.dll wherever you specified it in the "gamedll" parameter. Enough about liblist.gam though (there are some tutorials on this you can find elsewhere). Let's get coding!

Let's open up the mp/hl workspace, and open player.h. Insert this at line 74:

CODE (C++)
class CBasePlayer : public CBaseMonster
{
public:
void ShowVGUIMenu(int iMenuID); // VGUI Tutorial( 이 부분을 추가 )
int random_seed;

이것은 우리의 함수(function) 즉 client.dll 에서 VGUI 메뉴중에 하나를 보여줄 함수의 정의(prototype)입니다.
There we go. This is a prototype for our function that will tell the client dll to show one of the VGUI menus.
이제 구현을 해봅시다. 먼저 player.cpp 파일을 열고 237 라인을 보세요.


작업경로: "dlls Workspace"에서 "hl files"->player.cpp
HL1 SDK 2.3: go to line 242
HL1 SDK 2.x: go to line 237

그리고 아래의 내용을 추가해 주세요.

[원문]
There we go. This is a prototype for our function that will tell the client dll to show one of the VGUI menus.

Now, let's implement it. Open player.cpp and go to line 237:

CODE (C++)
LINK_ENTITY_TO_CLASS( player, CBasePlayer );

// Start - VGUI Tutorial( 이 부분을 추가 )
void CBasePlayer :: ShowVGUIMenu(int iMenuID)
{
MESSAGE_BEGIN(MSG_ONE, gmsgVGUIMenu, NULL, pev);
WRITE_BYTE( iMenuID );
MESSAGE_END();
}
// End - VGUI Tutorial( 여기까지 추가 )

void CBasePlayer :: Pain( void )


여기서 잠깐!,,, 위의 gmsgVGUIMenu 가 어디에서 왔을까요? 그렇습니다! 정의 되지 않았습니다. 먼저 190 라인으로 가보죠.

작업경로: "dlls Workspace"에서 "hl files"->player.cpp (계속 이어서)
HL1 SDK 2.3: go to line 188
HL1 SDK 2.x: go to line 190

그리고 아래의 내용을 추가해 주세요.

[원문]
But, hold on a sec. Where did gmsgVGUIMenu come from? That's right! It's not declared. First let's go to line 190:

CODE (C++)
int gmsgShowMenu = 0;
int gmsgGeigerRange = 0;
int gmsgVGUIMenu = 0; // VGUI Tutorial( 이 부분을 추가 )

void LinkUserMessages( void )


다음으로, void LinkUserMessages(void) 함수의 끝으로 가서 아래의 내용을 추가하겠습니다.

작업경로: "dlls Workspace"에서 "hl files"->player.cpp (계속 이어서)
HL1 SDK 2.3: go to line 238
HL1 SDK 2.x: go to line ???: 바로 위에서 작성한 부분 아래에 보면 void LinkUserMessages(void)가 있습니다. 이 함수의 끝.

그리고 아래의 내용을 추가해 주세요.

[원문]
Next, let's go to the end of that LinkUserMessages and add the following:

CODE (C++)
gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade));
gmsgAmmoX = REG_USER_MSG("AmmoX", 2);
gmsgVGUIMenu = REG_USER_MSG("VGUIMenu", 1); // VGUI Tutorial( 이 부분을 추가 )
}

LINK_ENTITY_TO_CLASS( player, CBasePlayer );


위의 내용은 client.dll에게 ShowVGUIMenu에서 message를 전달하기 위해 필요합니다. 이제 player.h, player.cpp 부분은 끝났습니다. 이제 우리의 ShowVGUIMenu를 사용할 것입니다! 그럼 client.cpp 파일을 열고 380 라인으로 가보죠.


작업경로: "dlls Workspace"에서 "hl files"->client.cpp
HL1 SDK 2.3: go to line 439 : 함수의 위치는 void ClientCommand( edict_t *pEntity ) {
HL1 SDK 2.x: go to line 380

그리고 아래의 내용을 추가해 주세요.

[원문]
The above is needed to tell the client dll what message we are sending in ShowVGUIMenu. Now that that's done, it's time to put our ShowVGUIMenu to use! Let's open client.cpp and go to line 380:


CODE (C++)
GetClassPtr((CBasePlayer *)pev)->SelectLastItem();
}
// Start - VGUI Tutorial( 이 부분을 추가 )
else if ( FStrEq(pcmd, "vguimenu" ) )
{
if (CMD_ARGC() >= 1)
GetClassPtr((CBasePlayer *)pev)->ShowVGUIMenu(atoi(CMD_ARGV(1)));
}
// End - VGUI Tutorial( 여기까지 추가 )
else if ( g_pGameRules->ClientCommand( GetClassPtr((CBasePlayer *)pev), pcmd ) )


이제, 우리는 "vguimenu xx" 라는 console command로 어떠한 VGUI Menu 들도 우리가 원하는 대로 접근 할 수 있습니다! mp.dll을 Build 하고 그 파일을 여러분의 MOD 디렉토리내 dlls 디렉토리에 넣어주시면 되겠습니다. HL 또는 CS, Steam 으로 여러분의 MOD를 실행한 다음 console을 열어서 "vguimenu 5" (" 없이)를 넣어보고 아래의 그림과 같은지 확인해 보세요.


[원문]
Now, we can use the command "vguimenu xx" to access any of the VGUI Menus we want! Build your mp.dll and put it in your mod's gamedll folder. Start a game of HL, and in the console type "vguimenu 5" (without the ") and something close to the following should show:



일단 "hl files" 코드들을 hl.dll 파일로 build 해야 합니다.
현재 VC++ 6.0 상태에서 왼쪽 Workspace를 보시게 되면 "cl_dll files" 와 "hl files" 두개의 project가 있을 것입니다.

여기에서는 "hl files" 부분을 build 해야 하기 때문에 "hl files" 부분에 마우스 오른쪽 버튼을 눌러서 "Set as Active Project"를 눌러서 build 시킬 project를 활성화 시켜줍니다.

다음으로 VC++ 6.0 메뉴 -> Build -> Build hl.dll 을 선택해 주세요. 또는 F7을 누르셔도 됩니다.

정확히 입력하셨다면 error 없이 hl.dll 파일이 생성 되었을 것입니다.

이제 게임을 하기 위해서는 여러분이 만든 MOD안에 생성된 hl.dll 파일을 넣어줘야 합니다.
정상적이라면(모드나라 http://mod.zoa.to 동영상 강좌를 보고 VC++ 6.0을 Setting 한 경우), hl.dll 파일의 경로는 SDK/Single-Player Source/dlls/debughl/ 입니다. 즉, Build 작업을 거쳐서 hl.dll 파일이 생성될 디렉토리 위치입니다.

* SDK/... 에서 SDK는 여러분이 설치한 HL1 SDK 2.x의 경로입니다.

이 디렉토리 SDK/Single-Player Source/dlls/debughl/ 에 있는 hl.dll 파일을 여러분의 MOD 디렉토리의 dlls 디렉토리에 복사를 합니다.
e.g., SDK/Single-Player Source/dlls/debughl/hl.dll ---> Steam/SteamApps/스팀_ID/half-life/나의_MOD_NAME/dlls/

그리고 게임을 실행해서 "~" tilde를 눌러서 게임의 console로 들어갑니다.
이때 명령창에 "vguimenu 5" 라고 입력합니다. (" double quote-따옴표-는 빼시고 입력하세요.)
정확히 하셨다면 아래의 화면과 같은 화면을 보실 수 있습니다. 어떻습니까? CS 시작할 때의 윈도우과 같지요? ^___^

Figure #1


음, 그런데 메뉴가 좋아 보이지는 않군... 하지만 이 문서의 목적에 근접했다. 이건 우리의 새로운 vguimenu 명령이 동작하는가를 보기위한 테스트였다. 아니면 말고. 이제, 우리는 드디어 우리만의 새로운 VGUI 메뉴를 만들게 될것이다! cl_dll 프로젝트를 열고나서 vgui_TeamFortressViewport.h를 열고 46 라인을 보자.


작업경로: "cl_dll Workspace"에서 "cl_dll files"->vgui_TeamFortressViewport.h
HL1 SDK 2.3: go to line 52
HL1 SDK 2.x: go to line 46

그리고 아래의 내용을 추가해 주세요.


[원문]
So that menu may not look the best... but that's besides the point. That was a test to see whether or not our new vguimenu command works. Now, we will finally create our own new VGUI menu! Lets open up the cl_dll project now. Open up vgui_TeamFortressViewport.h at line 46:

CODE (C++)
class CClassMenuPanel;
class CTeamMenuPanel;
class CFirstMenu; // VGUI Tutorial( 이 부분을 추가 )

char* GetVGUITGAName(const char *pszName);


이제 우리는 우리의 class를 하나 갖게 되었고, 이 경우 우리의 class를 참조하기 위해 class를 만들어 주어야 한다. 다음을 보자. 라인은 1118 이다.


작업경로: "cl_dll Workspace"에서 "cl_dll files"->vgui_TeamFortressViewport.h (계속 이어서)
HL1 SDK 2.3: go to line 1576
HL1 SDK 2.x: go to line 1118

그리고 아래의 내용을 추가해 주세요.


[원문]
Now we have our class prototyped, just in case we need to reference to it before we wrote the next bit of text (goto line 1118):

CODE (C++)
public:
CTFScrollPanel(int x,int y,int wide,int tall);
};

// Start - VGUI Tutorial( 이 부분을 추가 )
//================================================================
// First VGUI menu!
//============================================================
class CFirstMenu : public CMenuPanel
{
private:
CommandButton *m_pCancelButton;

public:
CFirstMenu(int iTrans, int iRemoveMe, int x, int y, int wide, int tall);
};
// End - VGUI Tutorial( 여기까지 추가 )

//================================================================
// Menu Panels that take key input


비로서 우리는 우리의 첫번째 메뉴 class를 갖게 되었다. 그럼 이제 구현을 해보자. "vgui_FirstMenu.cpp" 라는 파일을 cl_dll 프로젝트의 "Source Files"에 새로 추가하자. 추가를 했다면 그 파일을 열고 아래와 같이 작성하자.


아래의 그림대로 파일을 추가 했다면 Workspace에서 "cl_dll files" project의 Source Files/hl/ 안에 vgui_FirstMenu.cpp 파일이 있을것입니다. 이 파일을 마우스로 drag 해서 Source Files에 drop 하세요. 즉, Source/hl 에 있는 파일을 Source Files/ 에 옮기는 것입니다.

Figure #2


작업경로: "cl_dll Workspace"에서 "cl_dll files"->vgui_FirstMenu.cpp
HL1 SDK 2.3: go to line 0
HL1 SDK 2.x: go to line 0

그리고 아래의 내용 전체를 추가해 주세요.


[원문]
There we go, we have our first menu class. Now let's implement our constructor. Add a new file to the Source Files of the cl_dll project named "vgui_FirstMenu.cpp". Open up that file and write in the following:


CODE (C++)
#include "hud.h"
#include "cl_util.h"
#include "vgui_TeamFortressViewport.h"


CFirstMenu :: CFirstMenu(int iTrans, int iRemoveMe, int x, int y, int wide, int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall)
{
m_pCancelButton = new CommandButton( gHUD.m_TextMessage.BufferedLocaliseTextString( "Cancel" ), 5, 5, XRES(75), YRES(30));
m_pCancelButton->setParent( this );
m_pCancelButton->addActionSignal( new CMenuHandler_TextWindow(HIDE_TEXTWINDOW) );
}


이제, 위의 코드는 우리의 "Cancel" 이라는 문자를 넣은 cancel 버튼을 만드는데, 위치는 x(5), y(5) 이고 width(가로)는 75, height(세로)는 30 이다. XRES와 YRES는 우리가 640x480 이나 320x240과 같은 해상도와 관련된 우리의 number들을 만든다. 그 다음 라인은 CFirstMenu가 현재 새로운 자식이 화면에 나타나는지, 숨겨졌는지, 무엇이던간에 이것들은 CFirstMenu의 자식들이다. 마지막 라인은 버튼을 클릭함과 동시에 MenuHandler에게 우리의 메뉴가 숨겨질거라는걸 알려준다.
다음으로, 우리의 ShowVGUIMenu 명령을 통해서 우리의 VGUI 메뉴를 열기위해 필요한 부분을 변경할 것이다. 278 라인으로 가보자.


작업경로: "cl_dll Workspace"에서 "cl_dll files"->vgui_TeamFortressViewport.h (계속 이어서)
HL1 SDK 2.3: go to line 441
HL1 SDK 2.x: go to line 278

그리고 아래의 내용을 추가해 주세요.


[원문]
Now, what this does, is create our cancel button with "Cancel" as the text on it, position it at x(5), y(5), and let it have a width of 75 and a height of 30. XRES and YRES make our numbers relative to either 640x480 or 320x240. The second line tells CFirstMenu that it now has a new child that it has to show, or hide, or do whatever it should to it's children. The last line makes it so that when you click on the button, it'll send a MenuHandler that will hide our menu. Next, we will make the necessary changes so that we may open our VGUI menu through our ShowVGUIMenu command. Goto line 278:

CODE (C++)
void CreateClassMenu( void );
CMenuPanel* ShowClassMenu( void );
// Start - VGUI Tutorial( 이 부분을 추가 )
void CreateFirstMenu( void );
CMenuPanel* ShowFirstMenu( void );
// End - VGUI Tutorial( 여기까지 추가 )
void CreateSpectatorMenu( void );


첫번째 함수(function)는 나중에 우리의 메뉴가 만들어지는 부분이 시작될때 VGUI에서 먼저 호출되어진다. 두번째 함수는 우리가 우리의 FirstMenu가 보여지기를 원하는 언제라도 호출된다. 다음, 399 라인으로 가서 아래의 코드를 추가하자.


작업경로: "cl_dll Workspace"에서 "cl_dll files"->vgui_TeamFortressViewport.h (계속 이어서)
HL1 SDK 2.3: go to line 568
HL1 SDK 2.x: go to line 399

그리고 아래의 내용을 추가해 주세요.


[원문]
The first function, later on, is what gets called early in the VGUI starting stage to create our menu. The second function gets called every time we want our FirstMenu to show up. Next, go to line 399 and add the following:

CODE (C++)
CTeamMenuPanel *m_pTeamMenu;
CClassMenuPanel *m_pClassMenu;
CFirstMenu *m_pFirstMenu; // VGUI Tutorial( 이 부분을 추가 )
ScorePanel *m_pScoreBoard;


이제, 우리의 FirstMenu는 실제로 TeamFortressViewport 클래스의 한 부분이 되었다.

여기까지는 vgui_TeamFortressViewport.h 에 대한 내용이었다! 이제, vgui_TeamFortressViewport.cpp 파일을 열어보자. 468 라인을 한번 보자.


작업경로: "cl_dll Workspace"에서 "cl_dll files"->vgui_TeamFortressViewport.cpp
HL1 SDK 2.3: go to line 523
HL1 SDK 2.x: go to line 468

그리고 아래의 내용을 추가해 주세요.


[원문]
Now, our FirstMenu is part of the actual TeamFortressViewport class.

That's it for vgui_TeamFortressViewport.h! Now, open vgui_TeamFortressViewport.cpp! Let's take a look at line 468:

CODE (C++)
m_pTeamMenu = NULL;
m_pClassMenu = NULL;
m_pFirstMenu = NULL; // VGUI Tutorial( 이 부분을 추가 )
m_pScoreBoard = NULL;


우리는 우리의 메뉴에서 몇몇 이상한 (wacko: 미 속어 -wacky-) 정보을 얻지 못하기 때문에 이러한 작업이 필요하다. 따라서 전부 NULL 로 만든다. 나중에 함수내에서 우리는 우리의 메뉴를 생성할 것이다. 이러한 작업을 532 라인에서 할 수 있다.


작업경로: "cl_dll Workspace"에서 "cl_dll files"->vgui_TeamFortressViewport.cpp (계속 이어서)
HL1 SDK 2.3: go to line 585
HL1 SDK 2.x: go to line 532

그리고 아래의 내용을 추가해 주세요.


[원문]
These are necessary so that we don't get some wacko information in our menu. So we make it all NULL. Later on in this function we will create our menu. You can do that at line 532:

CODE (C++)
// VGUI MENUS
CreateTeamMenu();
CreateClassMenu();
CreateFirstMenu(); // VGUI Tutorial( 이 부분을 추가 )
CreateScoreBoard();


이제, TeamFortressViewport가 만들어질때 우리의 FirstMenu도 생성될 것이다. 553 라인을 계속해서 보자.


작업경로: "cl_dll Workspace"에서 "cl_dll files"->vgui_TeamFortressViewport.cpp (계속 이어서)
HL1 SDK 2.3: go to line 619
HL1 SDK 2.x: go to line 553

그리고 아래의 내용을 추가해 주세요.


[원문]
Now, when the TeamFortressViewport is being constructed, our FirstMenu will be created! Let's continue on to line 553:

CODE (C++)
// Force each menu to Initialize
if (m_pTeamMenu)
{
m_pTeamMenu->Initialize();
}
if (m_pClassMenu)
{
m_pClassMenu->Initialize();
}
// Start - VGUI Tutorial( 이 부분을 추가 )
if (m_pFirstMenu)
{
m_pFirstMenu->setVisible( false );
}
// End - VGUI Tutorial( 여기까지 추가 )
if (m_pScoreBoard)
{
m_pScoreBoard->Initialize();
HideScoreBoard();
}


이것은 새로운 맵(map)이 로드될 때 마다 필요한 것으로, 우리의 메뉴는 다시 숨겨진다. 이제, tf_defs.h(Externel Dependencies에 있는) 파일의 1132 라인에 있는 내용을 간단하게 바꿔보자.


작업경로: "cl_dll Workspace"에서 "cl_dll files"->Externel Dependencies->tf_defs.h
HL1 SDK 2.3: go to line 1128
HL1 SDK 2.x: go to line 1132

그리고 아래의 내용을 추가해 주세요.


[원문]
This is necessary so that whenver a new map gets loaded, our menu will be hidden again. Now, let's make a quick little change in tf_defs.h (External Dependencies) at line 1132:

CODE (C++)
#define MENU_REFRESH_RATE 25

// VGUI Tutorial( 아래 부분을 추가 )
#define MENU_FIRSTMENU 30

//============================
// Timer Types


이제, 우리는 우리의 FirstMenu에 부여될 번호를 정의 했다! 우리는 vguimenu를 사용할 때 이 번호를 사용하게 될것이다 (ex: "vguimenu 30").

그럼, vgui_TeamFortressViewport.cpp로 돌아와서, 1506 라인으로 가보자.


작업경로: "cl_dll Workspace"에서 "cl_dll files"->vgui_TeamFortressViewport.cpp (계속 이어서)
HL1 SDK 2.3: go to line 1751
HL1 SDK 2.x: go to line 1506

그리고 아래의 내용을 추가해 주세요.


[원문]
Now, we have a defined number assigned to our FirstMenu! We may use this number when we use vguimenu (ex: "vguimenu 30").

Now, back to vgui_TeamFortressViewport.cpp, let's go all the way down to line 1506:

CODE (C++)
case MENU_CLASS:
pNewMenu = ShowClassMenu();
break;

// Start - VGUI Tutorial( 이 부분을 추가 )
case MENU_FIRSTMENU:
pNewMenu = ShowFirstMenu();
break;
// End - VGUI Tutorial( 여기까지 추가 )

default:
break;
}


함수의 이름을 이렇게 놓아서 가족처럼 보이지 않나? 음, 모두에게 유의(비위를 맞추려면)하려면 그렇게 해야 한다고 나는 오래전부터 지금까지 말하고 있다. 아니, 이것은 우리가 앞서 했던것과는 같은것이 아니다. 이것은 우리의 메뉴들을 보여주도록 사용하는 실제 VGUI 코드이다. 그것들이 MenuID(우리의 경우 30)을 어떻게 가지는지 본 다음, MENU_FIRSTMENU의 case 문 작성, MenuID 가 30과 같을 때 부터 그것(VGUI 코드)은 우리의 FirstMenu를 보여줄것이다. 멋지지 않나? 응?

다음으로, 우리는 우리의 ShowFirstMenu 와 CreateFirstMenu의 원형을 만들고, 우리는 그 함수들을 코드에서 몇차례나 사용할 것이다. 하지만, 우리는 여전히 그것들을 구현하지 않았다. 1618 라인으로 가서 아래의 코드를 추가하자.


작업경로: "cl_dll Workspace"에서 "cl_dll files"->vgui_TeamFortressViewport.cpp (계속 이어서)
HL1 SDK 2.3: go to line 1877
HL1 SDK 2.x: go to line 1618

그리고 아래의 내용을 추가해 주세요.


[원문]
Does the name of the function we're putting this in look familiar? Well it should if you were paying attention to all I've been saying for a long while now. No, this is not the same thing we had before. This is what the actual VGUI code is using to show our menus. See how it'll take the MenuID (in our case, 30), and then when it gets to case MENU_FIRSTMENU, since MenuID equals 30, it will show our FirstMenu, cool, huh?

Now then. We've made our ShowFirstMenu and CreateFirstMenu prototypes, and we've used the functions on several occasions in the code. But we still haven't implemented them! Let's go down to line 1618 and then add the following:

CODE (C++)
m_pClassMenu->setParent(this);
m_pClassMenu->setVisible( false );
}

// Start - VGUI Tutorial( 이 부분을 추가 )
//======================================================================================
// OUR FIRST MENU
//======================================================================================
// Show the FirstMenu
CMenuPanel* TeamFortressViewport::ShowFirstMenu()
{
// Don't open menus in demo playback
if ( gEngfuncs.pDemoAPI->IsPlayingback() )
return NULL;

m_pFirstMenu->Reset();
return m_pFirstMenu;
}

void TeamFortressViewport::CreateFirstMenu()
{
// Create the panel
m_pFirstMenu = new CFirstMenu(100, false, 0, 0, ScreenWidth, ScreenHeight);
m_pFirstMenu->setParent(this);
m_pFirstMenu->setVisible( false );
}
// End - VGUI Tutorial( 여기까지 추가 )

//======================================================================================
// SPECTATOR MENU



여기까지 왔군! 이제는 그냥 client.dll 을 compile 시키고 나서 여러분의 MOD의 cl_dlls 폴더에 옮기면 된다. HL1 또는 CS, Steam 등에서 여러분의 MOD를 "custom game" 으로 실행 하고나서 게임을 시작하면 된다. console에서 vguimenu 30을 적고 아래와 같은 메뉴가 나오는지 바라보라.


일단 "cl_dll files" 코드들을 client.dll 파일로 build 해야 합니다.
현재 VC++ 6.0 상태에서 왼쪽 Workspace를 보시게 되면 "cl_dll files" 와 "hl files" 두개의 project가 있을것입니다.

여기에서는 "cl_dll files" 부분을 build 해야 하기 때문에 "cl_dll files" 부분에 마우스 오른쪽 버튼을 눌러서 "Set as Active Project"를 눌러서 build 시킬 project를 활성화 시켜줍니다.

다음으로 VC++ 6.0 메뉴 -> Build -> Build client.dll 을 선택해 주세요. 또는 F7을 누르셔도 됩니다.

정확히 입력하셨다면 error 없이 client.dll 파일이 생성 되었을 것입니다.

이제 게임을 하기 위해서는 여러분이 만든 MOD안에 생성된 client.dll 파일을 넣어줘야 합니다.
정상적이라면(모드나라 http://mod.zoa.to 동영상 강좌를 보고 VC++ 6.0을 Setting 한 경우), client.dll 파일의 경로는 SDK/Single-Player Source/cl_dll/Debug/ 가 아닌 Steam/SteamApps/스팀_ID/half-life/나의_MOD_NAME/cl_dlls 입니다. 즉, Build 작업을 거쳐서 client.dll 파일이 생성될 디렉토리 위치입니다.

* SDK/... 에서 SDK는 여러분이 설치한 HL1 SDK 2.x의 경로입니다.

*** 위의 디렉토리로 설정이 되어있지 않다면 위에서 hl.dll 파일을 복사한것 처럼 복사를 해줘야 합니다. ***

이 디렉토리 SDK/Single-Player Source/cl_dll/Debug/ 에 있는 client.dll 파일을
여러분의 MOD 디렉토리의 cl_dlls 디렉토리에 복사를 합니다.
e.g., SDK/Single-Player Source/cl_dll/Debug/client.dll ---> Steam/SteamApps/스팀_ID/half-life/나의_MOD_NAME/cl_dlls/

그리고 게임을 실행해서 "~" tilde를 눌러서 게임의 console로 들어갑니다.
이때 명령창에 "vguimenu 30" 라고 입력합니다. (" double quote-따옴표-는 빼시고 입력하세요.)
정확히 하셨다면 아래의 화면과 같은 화면을 보실 수 있습니다. ^___^


[원문]
There we go! Now just compile your client.dll and move it to your mod's cl_dlls folder. Run Half-Life with your mod as your custom game, and start a game. In the console, type vguimenu 30 and, low and behold, the following pops up:

Figure #3


그래, 이것은 대한단 메뉴가 아니다. 하지만 이번 tutorial에서 메뉴를 어떻게 당신것으로 만드는지 보여주었다. 이것은 단지 기본적인 시작일 뿐이다(시작에 불과 하다.). part 2 에서 좀더 재미있는 우리의 메뉴를 확인하자(만들어 보자.).


[원문]
Yeah, it's not much of a menu. But this tutorial has shown you how you can create your own. This is just the basic start! Check out part 2 for more fun with our menu.

역주:
이제 게임내에서 어떻게 메뉴를 만드는지 아시겠지요? 여러분들도 MOD 게임 시작시에 괜찮은 메뉴를 만들어 보세요.
이 tutorial이 총 3부분으로 나누어져 있습니다. 앞으로 part 2, part 3 를 번역해서 만나 뵙도록하죠...

Ps: 처음엔 제가 존대말로 작성을 했는데요, 영어는 존대말보단 반말이 더 낫더군요. ^^; 미국말은 참 예의가 없지요...
혹시라도 번역상에 문제가 되는게 있다면 아래의 email로 spam을 마구 보내주세요... 보내주시면 정정하도록 하겠습니다.

지금까지 번역에 godmode ( godmode2k@hotmail.com ) 이었습니다.

끝까지 읽어주셔서 감사합니다.
수고하셨습니다.