목요일, 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

댓글 없음:

댓글 쓰기