Данная страница оптимизирована под:
Internet Explorer 5.0
Разрешение 800×600
Кодировку Windows-1251


А это место под ваш баннер!



   На главную страницу
Для новичков:
   О Quake3 вообще
   О тактике
   О модах
    ·Quake 3 Arena Mods
    ·Создание Мода. Урок 1
    ·Создание Мода. Урок 2
    ·Создание Мода. Урок 3
    ·Создание Мода. Урок 4
   О скинах
   О терминалогии
Статьи:
   Changelog Quake 3 Arena v1.32
   Игроки с приставкой "HARDCORE"
   Пасхальные Яйца В Quake 3 Arena
   Насколько умны игроки в Quake?
Консоль:
   О консольных командах
   О скриптах
Файлы:
   О утилах
   О патчах
   О картах
   О модельках
   О демках
   О конфигах
Арт:
   О разделе Арт
Сайт:
   О гостевой книге
   О ссылках
   О нашем баннере
   О нас

Rambler's Top100 Игры

TopCTO Игры

Создание Мода. Урок 1

   Итак, напишем простейший мод для Quake III Arena. Он прост, но состоит из тех базовых элементов, которые используются в более сложных модах. Идея мода такова: игрок должен убивать других игроков различным заранее оговоренным оружием, которое будет ему выдаваться в определенном порядке, при этом сам он погибнуть не должен, иначе подсчет начнется заново. Игра заканчивается если кто-то наберет нужное количество фрагов каждым из указанных видов оружия. Администратор устанавливает правила через переменную g_wofOrder. Например, если для окончания игры надо набрать 2 фрага используя rocket launcher, 1 - rail и 1 - BFG, то переменная g_wofOrder устанавливается в значение "5579".

Создаем новую переменную g_wofOrder

Первое что необходимо сделать это создать новую переменную g_wofOrder. Для этого откройте файл g_main.c на строке 57 и найдите следующие строки:

vmCvar_t g_teamForceBalance;
vmCvar_t g_banIPs;
vmCvar_t g_filterBan;

Теперь добавим новую переменную типа cvar. Для этого дописываем в конце этих строк vmCvar_t g_wofOrder;
   Получается:

vmCvar_t g_teamForceBalance;
vmCvar_t g_banIPs;
vmCvar_t g_filterBan;
// =============
vmCvar_t g_wofOrder;
// =============

Теперь перейдем на строку 121. Здесь Q3 устанавливает переменные для работы с консолью. Добавим в массив еще один элемент, который будет описывать новую переменную:

{ &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse },
{ &g_allowVote, "g_allowVote", "1", 0, 0, qfalse },
// =============
{ &g_wofOrder, "g_wofOrder", "2345678", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_LATCH, 0, qfalse }
// =============

Добавленная строка устанавливает Q3 для работы с новой переменной. Рассмотрим подробнее параметры:
&g_wofOrder : указатель на созданную нами переменную
"g_wofOrder" : имя переменной. Оно не обязательно должно совпадать с тем, что в программе, т.к. это имя по которому можно будет обращаться к переменной из консоли Q3.
"2345678" : начальное значение переменной. Обратите внимание - это строка, даже если сама переменная вещественного или целого типа. CVAR_SERVERINFO | CVAR_ARCHIVE| CVAR_LATCH : этот параметр устанавливает свойства переменной. CVAR_SERVERINFO говорит о том, что переменная будет показываться в свойствах сервера. Устанавливайте этот флаг для переменной, если она сказывается на геймплее (например fraglimit, sv_minping итд) CVAR_ARCHIVE означает что переменная будет сохраняться в файле конфигурации. CVAR_LATCH означает что не будет эффекта от изменения данной переменной до конца текущей игры.
0 : начальное значение счетчика изменений
qfalse : Это поле может принимать значения qtrue и qfalse. qtrue говорит клиенту о том что изменение переменной нужно отображать при помощи строки консоли.
Теперь, когда мы добавили новую переменную, попробуем ее использовать.

Изменяем структуру клиента Нам необходимо отслеживать каким оружием пользуется игрок. Самый простой способ это сделать - просто добавить переменную типа int в структуру игрока. Откроем g_local.h и добавим новую переменную:

// timeResidual is used to handle events that happen every second
// like health / armor countdowns and regeneration
int timeResidual;
// =============
int iCurrentWeapon; // номер текущего оружия для клиента
// =============

По этой новой переменной мы будем следить за оружием игрока и менять оружие, когда игрок достигнет лимита с текущим. Теперь нам необходима функция которая бы переключала оружие игрока. Добавляем новую функцию. Назовем новую функция Wof_NextWeapon и, поскольку она используется в клиентской части, то и добавим ее в конец файла g_client.c.

void Wof_NextWeapon(gentity_t *ent)
{
  gclient_t *client = ent->client;
  int iWep;

   client->iCurrentWeapon++;

  iWep = g_wofOrder.string[client->iCurrentWeapon];

  switch(iWep)
  {
  case '1': //gauntlet
    client->ps.stats[STAT_WEAPONS] = ( 1 << WP_GAUNTLET );
    client->ps.ammo[WP_GAUNTLET] = -1;
    client->ps.weapon = WP_GAUNTLET;
    break;

  case '2': //machingun
    client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN );
    client->ps.ammo[WP_MACHINEGUN] = 150;
    client->ps.weapon = WP_MACHINEGUN;
    break;

  case '3': //shotgun
    client->ps.stats[STAT_WEAPONS] = ( 1 << WP_SHOTGUN );
    client->ps.ammo[WP_SHOTGUN] = 100;
    client->ps.weapon = WP_SHOTGUN;
    break;

  case '4': //gren
    client->ps.stats[STAT_WEAPONS] = ( 1 << WP_GRENADE_LAUNCHER );
    client->ps.ammo[WP_GRENADE_LAUNCHER] = 100;
    client->ps.weapon = WP_GRENADE_LAUNCHER;
    break;

  case '5': //rocket
    client->ps.stats[STAT_WEAPONS] = ( 1 << WP_ROCKET_LAUNCHER );
    client->ps.ammo[WP_ROCKET_LAUNCHER] = 100;
    client->ps.weapon = WP_ROCKET_LAUNCHER;
    break;

  case '6': //light
    client->ps.stats[STAT_WEAPONS] = ( 1 << WP_LIGHTNING );
    client->ps.ammo[WP_LIGHTNING] = 300;
    client->ps.weapon = WP_LIGHTNING;
    break;

  case '7': //rail
    client->ps.stats[STAT_WEAPONS] = ( 1 << WP_RAILGUN );
    client->ps.ammo[WP_RAILGUN] = 100;
    client->ps.weapon = WP_RAILGUN;
    break;

  case '8': //plasma
    client->ps.stats[STAT_WEAPONS] = ( 1 << WP_PLASMAGUN );
    client->ps.ammo[WP_PLASMAGUN] = 300;
    client->ps.weapon = WP_PLASMAGUN;
    break;

  case '9': //bfg
    client->ps.stats[STAT_WEAPONS] = ( 1 << WP_BFG );
    client->ps.ammo[WP_BFG] = 150;
    client->ps.weapon = WP_BFG;
    break;
  }

  client->ps.weaponstate = WEAPON_READY;

  G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/weapons/change.wav")); }

Рассмотрим подробнее эту функцию: при инициализации клиентской части в первый раз признак текущего оружия (iCurrentWeapon) установлен в -1. При вызове данной функции его значение увеличивается на 1, т.е. становиться равным 0. Далее по этому индексу в переменную iWep заноситься число, взятое из строки g_wofOrder. Это число задает номер оружия. Далее нам остается только установить это оружие игроку, добавить ему патронов и сделать это оружие текущим. Да, и не забыть еще озвучить это переключение.
Однако остается одна маленькая проблема: программа не предусматривает условие победы кого-либо из игроков. Поэтому изменим окончание нашей функции следующим образом: т.к. текстовая строка всегда заканчивается нулем (а оружия номер 0 у нас в списке нет), то мы можем считать это признаком того что игрок совершал требуемый порядок действий и может быть объявлен победителем. Таким образом добавим в нашу функцию (следом за кодом выбора в качестве оружия BFG):

case '9': //bfg
  client->ps.stats[STAT_WEAPONS] = ( 1 << WP_BFG );
  client->ps.ammo[WP_BFG] = 150;
  client->ps.weapon = WP_BFG;
  break;

default:
  trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " wins.\n\"",client->pers.netname ) );

  G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/feedback/excellent.wav"));

  LogExit( va("print \"%s wins.\n\"",client->pers.netname ));
  return;
}

Так же необходимо добавить объявления функции LogExit и переменной g_wofOrder, иначе мы получим ошибку при компиляции этого мода. Поэтому перейдем в начало данного файла и допишем:

// Copyright (C) 1999-2000 Id Software, Inc.
//
#include "g_local.h"
// =============
void LogExit( const char *string );
extern vmCvar_t g_wofOrder;
// =============

Теперь надо собрать все вместе, что бы наш мод работал так, как мы задумали. Собираем все вместе Необходимо быть уверенными, что клиент создается со значением iCurrentWeapon равным -1. Иначе отсчет начнется не с первого указанного в g_wofOrder оружия. Так же нам необходимо вызвать Wof_NextWeapon для установки первого оружия. Для этого откроем файл g_client.c и перейдем на строку 996 (это функция ClientSpawn) и добавим следующее:

} else {
// fire the targets of the spawn point
G_UseTargets( spawnPoint, ent );

// =============
ent->client->iCurrentWeapon = -1;

Wof_NextWeapon(ent);
// =============

Теперь нам необходимо сделать так, что бы игрок переключился на следующее оружие, когда он заработает фраг при помощи текущего. Для этого перейдем на строку 240 файла g_combat.c (функция player_di). Перед функцией AddScore мы изменяем оружие атакующего игрока на следующее по установленному списку. Выглядит это так:

} else {
// =============
Wof_NextWeapon(attacker);
// ============
AddScore( attacker, 1 );

Так же для успешной компиляции необходимо объявить функцию Wof_NextWeapon в файле g_local.h дописав в конец следующие строки:

// =============
void Wof_NextWeapon(gentity_t *ent);
// =============

Мод уже можно компилировать и играть, но игрок может найти и использовать другое оружие. Чтобы этого не произошло, мы должны не давать ему поднять оружие. Это можно проконтролировать в функции Pickup_Weapon, которая находится в файле g_items.c. Перейдем на строку 163 и закомментируем команду, которая дает клиенту подобранное оружие:

// add the weapon
// =============
//other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
// =============
Add_Ammo( other, ent->item->giTag, quantity );

Теперь игрок может поднять только патроны от оружия.
   Следующая проблема заключается в том, что после того, как игрок заработал фраг он меняет оружие. Однако это оружие может отсутствовать на карте и тогда начнется его загрузка. Это вызовет небольшие зависания в игре. Чтобы этого избежать, надо при запуске игры загрузить все оружие в память. Оно займет немного памяти. Для этого откроем файл g_items.c на строке 576. Это функция ClearRegisteredItems.

void ClearRegisteredItems( void ) {
memset( itemRegistered, 0, sizeof( itemRegistered ) );

// players always start with the base weapon
RegisterItem( BG_FindItemForWeapon( WP_MACHINEGUN ) );
RegisterItem( BG_FindItemForWeapon( WP_GAUNTLET ) );
// =============
RegisterItem( BG_FindItemForWeapon(WP_SHOTGUN) );
RegisterItem( BG_FindItemForWeapon(WP_GRENADE_LAUNCHER) );
RegisterItem( BG_FindItemForWeapon(WP_ROCKET_LAUNCHER) );
RegisterItem( BG_FindItemForWeapon(WP_LIGHTNING) );
RegisterItem( BG_FindItemForWeapon(WP_RAILGUN) );
RegisterItem( BG_FindItemForWeapon(WP_PLASMAGUN) );
RegisterItem( BG_FindItemForWeapon(WP_BFG) );
RegisterItem( BG_FindItemForWeapon(WP_GRAPPLING_HOOK) );
// =============
}

Было бы лучше если бы загружалось только оружие которое нам необходимо, но пока ограничимся этим. Добавляем новую команду Добавить новую команду очень легко. Надо только найти функцию ClientCommand в файле g_cmds.c(строка 1094).

else if (Q_stricmp (cmd, "setviewpos") == 0)
         Cmd_SetViewpos_f( ent );
    // =============
    else if (Q_stricmp (cmd, "wof_backWeapon") == 0)
    {
        if(ent->client->iCurrentWeapon > 0)
            ent->client->iCurrentWeapon-=2;
        else
            ent->client->iCurrentWeapon=-1;

    Wof_NextWeapon(ent);
  }
  // =============
  else
    trap_SendServerCommand( clientNum, va("print \"unknown cmd %s\n\"", cmd ) );

Теперь когда игрок наберет команду wof_backWeapon, то оружие смениться на одно назад. Вывод сообщений Было бы неплохо, если бы игрок знал, что он начинает игру в нестандартном режиме и не был удивлен невозможностью поднять оружие. Для этого мы покажем ему порядок смены оружия при присоединении к игре. Для этого отредактируем функцию ClientBegin, которая находится в файле g_client.c (строка 814). После сообщения о вступлении в игру мы добавим информациюо моде.

if ( g_gametype.integer != GT_TOURNAMENT ) {
  trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) );
  // =============
  trap_SendServerCommand( ent-g_entities, va("print \"wof_order is %s\n\"", g_wofOrder.string));

  trap_SendServerCommand( ent-g_entities, va("cp \"This Server is Running WoF\""));
  // =============
}

Ну и последнее что необходимо сделать - заменить строку с версией игры. Для этого перейдем к строке 12 файла g_local.h:

// the "gameversion" client command will print this plus compile date
// =============
//#define GAMEVERSION "baseq3"
#define GAMEVERSION "wof"
// =============

Вот и все. Осталось только скомпилировать мод и запустить его. Для дальнейшей тренировки могу предложить дать возможность игроку знать сколько фрагов и какким оружием ему еще надо набрать. Это можно реализовать в функции Wof_NextWeapon. Если вы поняли как был написан этот мод, то сможете самостоятельно писать любые моды для Quake III Arena. Ограничение будет только в фантазии и возможностями самой Quake III Arena. Удачи!
   Откомпилированный MOD можно скачать здесь

www.u3d.agava.ru

Hosted by uCoz