Shambler team
Статьи по программированию под движком Gold Source (игры Half-Life 1) и работе с SDK

Подбираемый фонарик


версия статьи 2.5

Никогда не задумывались , почему в HEV-костюме идёт сразу же фонарик? Ну кончено же — это специальный навороченный костюм со всеми прибамбасами и т.д. А если, положим, нет фонарика в комплекте? Может сделать так, чтобы его нужно было отдельно подбирать, чтобы затем было легче в различных вентиляциях и т.д.? Что ж ,задумка вполне неплохая, и вот её реализация:
Ниже представлен код подбираемого фонарика. Код разработал BUzer, дополнения и поправки — мои (G-Cont).

Для начала сделаем сам подбираемый объект-энтитю. Для этого откроем файл items.cpp и внесём туда код нового предмета — item_flashlight (можно в конец файла):

class CItemFlashlight : public CItem
{
void Spawn( void )
{ 
Precache( );
SET_MODEL(ENT(pev), "models/w_flashlight.mdl");
CItem::Spawn( );
}
void Precache( void )
{
PRECACHE_MODEL ("models/w_flashlight.mdl");
PRECACHE_SOUND( "items/gunpickup2.wav" );
}
BOOL MyTouch( CBasePlayer *pPlayer )
{
if ( pPlayer->pev->weapons & (1<< WEAPON_FLASHLIGHT) )
return FALSE;
pPlayer->pev->weapons |= (1<< WEAPON_FLASHLIGHT);

MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev );
WRITE_STRING( STRING(pev->classname) );
MESSAGE_END();

EMIT_SOUND_SUIT( pPlayer->edict(), "items/gunpickup2.wav" );
return TRUE;
}
};
LINK_ENTITY_TO_CLASS(item_flashlight, CItemFlashlight);


Теперь нам нужно задекларировать переменную weapon_flashlight в системе. Для этого откройте weapons.h и cdll_dll.h и добавьте в каждый файл следующую строку

#define WEAPON_FLASHLIGHT 30

добавлять переменную в cdll_dll.h нужно для того, что бы она была задекларирована и в client.dll — что нужно для отключения прорисовки фонарика. Добавлять строку нужно (в обоих файлах) перед строкой

#define WEAPON_SUIT 31

Эта строка встречается в каждом файле по одному разу.

Теперь нам нужно прекэшировать фонарик, как предмет, если мы например захотим получить его из консоли, набрав команду
give item_flashlight
Если этого не сделать, то игра вывалится с ошибкой прекэшинга. Итак, откроем weapons.cpp и найдем функцию W_Precache добавим туда строку

UTIL_PrecacheOther( "item_flashlight" );

Добавлять строку нужно после строки UTIL_PrecacheOther( "item_longjump" ); Ну и давайте тогда уж сделаем, чтобы фонарик можно было получить по стандартной "читерской" команде в консоли
impulse 101
Для этого откройте файл player.cpp и найдите в нём строку

case 101

и добавьте ниже строку:

GiveNamedItem( "item_flashlight" );

после строки GiveNamedItem( "item_suit" );

Итак, фонарик как предмет у нас уже есть, и мы даже можем его подбирать. Однако толку от него пока мало, так если Вы скомпилируете вышеприведённый код, то с удивлением обнаружите, что, хотя фонарик и можно подбирать, но от него ничего не зависит, и функция прорисовки фонарика и его отключение по-прежнему зависят только от наличия костюма, а не фонарика. Давайте исправим данную недоработку.

Откройте снова player.cpp, а если Вы его не закрывали, то это ещё лучше; и найдите там строку FlashlightTurnOn, тогда немного ниже вы найдете строку:

if ( (pev->weapons & (1<< WEAPON_SUIT)) )

замените в ней WEAPON_SUIT на WEAPON_FLASHLIGHT .

Теперь фонарик будет невозможно включить, если у Вас его нет. Однако его спрайт в правом верхнем углу экрана всё равно будет рисоваться как при наличии фонарика, так и при отсутствии оного, что не хорошо, будем править и этот баг. Откройте папку cl_dll и найдите там файл flashlight.cpp. Откройте этот файл и найдите в нем строку:

CHudFlashlight::Draw

Затем найдите чуть ниже строку

if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) ))

её нужно заменить на:

if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) && (gHUD.m_iWeaponBits & (1<<(WEAPON_FLASHLIGHT)) )))

Теперь спрайт фонарика будет отрисовываться при соблюдении двух условий — при наличии собственно фонарика и при наличии костюма.

Чтобы фонарик отрисовывался, при его поднятии нужно нарисовать его картинку и запихнуть в спрайт с названием 640hud2. Фонарик для разрешения 320 рисовать необязательно, поскольку при таком разрешении практически никто не играет. Однако если вы всё же надумаете это сделать, то его удобно будет запихнуть в спрайт 320hud2. В файле hud.txt нам будет нужно добавить две строки — для разрешения 320×200 и 640×480 соответственно

item_flashlight 320 320hud2 68 72 20 20

item_flashlight 640 640hud2 176 144 44 44


Из последней четвёрки чисел 68 и 72 представляют отступы по горизонтали и вертикали, а два последних — габариты фрагмента по ширине и высоте соответственно. Так вот, куда эти строки дописывать — я думаю сообразите: в начале файла hud.txt будет прописано некое число, после добавления двух вышеприведенных строк это число нужно будет увеличить на 2. Т.е. если было прописано 125, Вам нужно будет прописать 127. Для самых ленивых моделька и спрайты выложены в архиве внизу. Чтобы добавить фонарик на карту, создайте точечную энтитю item_flashlight.


Дополнение 1

Можно сделать так, чтобы фонарик автоматически появлялся у игрока при запуске карты. Для этого допишем следующий код — в cbase.h добавим логическую переменную:

extern BOOL g_startFlashlight;

Откроем файл gamerules.cpp, найдём строку GetPlayerSpawnSpot и перед строкой
return pentSpawnSpot; добавим новую функцию:
if (pentSpawnSpot->v.spawnflags & 2) 
{
g_startFlashlight = TRUE;
}
Cпаунфлаг равен 2 для совместимости со Спиритом (мод Spirit of Half-Life).Теперь откроем файл single_playgamerules.cpp и найдём в нём строку PlayerSpawn. И после строки
CBaseEntity *pWeaponEntity = NULL;
добавим код, который присваивает переменной WEAPON_FLASHLIGHT значение TRUE по умолчанию.
if (g_startFlashlight)
pPlayer->pev->weapons |= (1<< WEAPON_FLASHLIGHT)
В файле world.cpp перед функциями worldspawn (там, где перечислены спаунфлаги) нужно будет добавить

BOOL g_startFlashlight;

Ну вот собственно и всё, проставьте у объекта info_player_start параметр spawnflags 2, и Вы увидите, что фонарик появляется у Вас при загрузке карты (даже если костюма на Вас нет).


Дополнение 2

Фонарик, который заряжается от батарей. Согласитесь, что самозаряжающийся фонарик — это несколько неестественно. Давайте сделаем так, чтобы фонарик заряжался от батарей, подобно костюму (тут возможны два варианта: либо фонарик заряжается от основных батарей, либо от своих, особых батарей, я подробно рассмотрю оба варианта).

Для начала давайте сделаем, чтобы фонарик не самоподзаряжался. Для этого откройте файл Player.CPP, найдите UpdateClientData и в этой функции найдите:

// Update Flashlight
удалите (или просто закомментируйте) вот эту часть кода
if (m_iFlashBattery < 100)
{
m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time;
ALERT( at_console, "battery is charged\n" );
m_iFlashBattery++;
}
else
Теперь давайте сделаем, чтобы фонарик невозможно было включить, если он полностью разряжен (вы будете удивлены, но если отключить подзарядку, то фонарик продолжает преспокойно гореть, даже когда весь заряд вышел). Для этого найдём функцию (выше UpdateClientData) void CBasePlayer :: FlashlightTurnOn и добавим в ней в самом верху следующий код
if (m_iFlashBattery == 0) //g-cont
{
EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_ON, 1.0, ATTN_NORM, 0, PITCH_NORM ); 
//Клац-клац, а толку = 0
return;
}
строка EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON... нужна для большей реалистичности — так как щёлкать можно и разряженным фонариком. Теперь мы увеличим время разрядки фонарика (в стандартном режиме он разряжается за 3 минуты), согласитесь, было бы нечестно, если бы фонарик без подзарядки разряжался за те же три минуты в жизни, а впереди — километры вентиляций со злобными хедкрабами :) Сделать это очень легко — переходим в самый верх файла player.cpp и ищем строку:

#define FLASH_DRAIN_TIME 1.2

и замените в ней 1.2 на большее значение, например на 15 — это даст примерно полчаса работы фонаря без подзарядки, что довольно реалистично, и заставляет игрока экономить ставший драгоценным теперь свет.

Теперь создадим собственно саму батарейку, для чего откроем items.cpp в самом верху файла, рядом с extern int gmsgItemPickup; добавим ещё одну строку

extern int gmsgFlashBattery;
Это нам нужно, для обновления значка заряда фонарика (для передачи данных в клиент). Теперь у нас два пути — либо сделать так, чтобы обычная батарейка заряжала фонарь совместно с костюмом, либо сделать отдельную батарейку для фонаря. Первый способ проще, но в игровом плане дает меньше гибкости, хотя тогда его можно применять и в обычном Half-Life. Итак способ 1 — общая батарейка. Найдите комментарий
// Suit reports new power level
и перед ним добавьте следующий код.
if (pPlayer->m_iFlashBattery < 100)
{
pPlayer->m_iFlashBattery = pPlayer->m_iFlashBattery + 20;
//ALERT( at_console, "flashlight is charged\n");
if (pPlayer->m_iFlashBattery > 100)
{
pPlayer->m_iFlashBattery = 100;
}
}
else
return FALSE;

MESSAGE_BEGIN( MSG_ONE, gmsgFlashBattery, NULL, pPlayer->pev );
WRITE_BYTE(pPlayer->m_iFlashBattery);
MESSAGE_END();
Всё, теперь обычная батарейка будет подзаряжать заодно и фонарик.


Способ 2 — отдельная батарейка для фонаря (разумеется оба способа можно использовать вместе). Опустимся в конец файла items.cpp и добавим следующий код:

// ================================================== ======
// Battery for charger flashlight
// Copyright 2004 Shambler Team
// All Rights Reserved
// ================================================== ======

class CItemFlashBattery : public CItem
{
void Spawn( void )
{ 
Precache( );
SET_MODEL(ENT(pev), "models/w_battery.mdl");
CItem::Spawn( );
}
void Precache( void )
{
PRECACHE_MODEL ("models/w_battery.mdl");
PRECACHE_SOUND( "items/gunpickup2.wav" );
}
BOOL MyTouch( CBasePlayer *pPlayer )
{
if ( pPlayer->pev->deadflag != DEAD_NO )
{
return FALSE;
}

if (pPlayer->m_iFlashBattery >= 99)
{
return FALSE;
}

if ( ( pPlayer->pev->weapons & (1<< WEAPON_FLASHLIGHT) ) )
{
if (pPlayer->m_iFlashBattery < 100)
{
pPlayer->m_iFlashBattery = pPlayer->m_iFlashBattery + 20;
//ALERT( at_console, "battery is charged\n");
}
else
return FALSE;

MESSAGE_BEGIN( MSG_ONE, gmsgFlashBattery, NULL, pPlayer->pev );
WRITE_BYTE(pPlayer->m_iFlashBattery);
MESSAGE_END();
EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM );

MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev );
WRITE_STRING( STRING(pev->classname) );
MESSAGE_END();

return TRUE;
}
return FALSE;
}
};

LINK_ENTITY_TO_CLASS( item_flashlight_battery, CItemFlashBattery );

Модель — обычная батарейка, но разумеется можно использовать свою, изменив к ней путь. Если Вы вдруг решили использовать второе дополнение, без вставления кода самого подбираемого фонаря (а это также возможно), то замените WEAPON_FLASHLIGHT на WEAPON_SUIT в выше приведенном коде. Данную батарейку невозможно взять, если у Вас нет фонарика, или если его заряд полон. Почти что всё, теперь нам нужно сделать, чтобы батарейку для фонаря мы могли получить по стандартной читерской команде — по give и могли проставить её в спаун разбиваемого ящика. Откроем многострадальный файл кода Player.cpp и найдем строку
case 101
и добавьте куда-нибудь к остальным строкам

GiveNamedItem( "item_flashlight_battery" );

Теперь займемся прекэшингом — откроем weapons.cpp и найдем функцию W_Precache добавим туда строку

UTIL_PrecacheOther( "item_flashlight_battery" );

Остался последний штрих — добавить фонарик с батарейкой в энтити func_pushable и func_breakable. Откройте файл func_break.cpp и в самом верху, где идёт перечисление предметов

const char *CBreakable::pSpawnObjects[] =
{
NULL, // 0
"item_battery", // 1
"item_healthkit", // 2
"weapon_9mmhandgun", // 3
"ammo_9mmclip", // 4
"weapon_9mmAR", // 5
"ammo_9mmAR", // 6
"ammo_ARgrenades", // 7
"weapon_shotgun", // 8
"ammo_buckshot", // 9
"weapon_crossbow", // 10
... и так далее, в самом низу добавьте
"item_flashlight" // 22
"item_flashlight_battery" // 23

Соответственно, вызвать предметы можно будет, если поставить в поле spawnobject 22 — для фонаря или 23 — для его батарейки.

На этом всё, код для файла .fgd для вашего любимого редактора карт Хаммера напишете сами, ибо редактор Кварк умеет такие вещи делать автоматически ;) (g-cont ничего кроме Кварка не признает ;) — прим. Mitoh).

Спасибо за внимание!

BUzer и G-cont

Модель и спрайты утеряны. Но это не большая проблема, т.к. моделью фонарика делится Nicord — скачать модель item_flashlight.mdl в zip архиве [79 Кб].


Вам понадобится или переименовать его в w_flashlight.mdl или исправить название файла модели в коде.
А что касается второй проблемы — спрайтами делится Саня Пасяда — скачать спрайты 320hud2.spr и 640hud2.spr в 7z архиве [19 Кб]. Их исходный вид в bmp таков:
Если хотите переделать, то скачайте эти изображения, измените и под конец преобразуйте .bmp в .spr с помощью известного редактора, точнее мастера Half-Life Sprite Wizard. В нём при создании нужно будет загрузить кадр и выставить режим аддитивной прозрачности:

2004 Shambler Team
  Counter.CO.KZ -     !
Hosted by uCoz