QuakeC:Плавающие объекты в Quake
Написано: воскресенье, 24 марта 2013 г. автор st1x51
0
Сложность: Средняя
Вы хотите, чтобы различные части тела, мертвые тела и
рюкзачки в воде плавали? Тогда вы не должны пропустить этот урок, и даже
с малыми знаниями QuakeC-программирования, вы сможете заставить другие
вещи плавать.
Шаг 1 - Подготовка новой директории для мода.
Для начала, нам нужно скопировать неизмененные файлы QuakeC версии 1.06, а также скопировать файл floater.qc, содержание которого находится в самом конце этого урока, разумеется, вам нужно будет только создать файл с именем floater.qc и вставить в него текст, о котором чуть позже...
Итак, создайте в директории с Quake директорию, которую мы назовем FLOATER, и, чтобы Вы могли нормально делать мод, создайте внутри только что созданной директории еще одну директорию, которую назовем SRC.
Теперь скопируйте QuakeC файлы, а также файл floater.qc, в директорию SRC.
Итак, создайте в директории с Quake директорию, которую мы назовем FLOATER, и, чтобы Вы могли нормально делать мод, создайте внутри только что созданной директории еще одну директорию, которую назовем SRC.
Теперь скопируйте QuakeC файлы, а также файл floater.qc, в директорию SRC.
Шаг 2 - Добавление нового файла с исходного кодами
Так, как мы с вами внесли в наш мод новый файл, мы
должны добавить его в список файлов для компиляции. Этим списком
является файл progs.src, который сообщает компилятору, какие файлы с исходными кодами принадлежат к нашему моды.
Добавьте floater.qc после defs.qc в progs.src так, как показанно ниже:
Добавьте floater.qc после defs.qc в progs.src так, как показанно ниже:
defs.qc
floater.qc // наш новый файл
subs.qc
fight.qc
Шаг 3 - Изменение существующего исходного кода.
Изменения будут начинатся и заканчиватся коментариями // FLOATER.
Изменение файла client.qc. Сейчас мы с вами сделаем некоторое изменение в функции PlayerPreThink (ориентир на 914-ю строчку). После изменение начало функции должно выглядет так:
Изменение файла client.qc. Сейчас мы с вами сделаем некоторое изменение в функции PlayerPreThink (ориентир на 914-ю строчку). После изменение начало функции должно выглядет так:
local float mspeed, aspeed;
local float r;
// FLOATER
// контроль "плавучести" над плавающими объектами
floaterPreThink();
// FLOATER
if (intermission_running)
Добавив floaterPreThink();, мы предоставляем Quake способность контролировать плавающие объекты (для краткости назовем их плавуны).
Этот выполняется всегда на протяжении всей игры с некоторой задержкой
(в один кадр), то есть если существуют какие-либо плавуны, то они сразу
же становятся зависимыми от их текущих характеристик : они могут быть
падающими, плавающими, погружающимися или могут просто лежать на
поверхности.
Шаг 5 - Даем объектам способность плавать.
Погружающийся плавун зависит от скорости падения, но
потом падает на какую-нибудь поверхность, где плавает, как поплавок,
верх-вниз в течение 30 секунд и наконец падает на саму поверхность и
теряет скорость.
Иногда вы сможете найти плавун, который удерживает свою скорость или кружится вокруг в течении того времени, пока он плавает, как поплавок. Скажу сразу, я не писал код для чего-нибудь подобного. Еще объекты иногда удерживают скорость по x или y.
Максумум 32 плавуна могут быть активированны в одно и тоже время (смотрите конфигурации в файле floater.qc) - в этом случае, когда какой-нибудь активированный плавун достигает поверхности (дна, пола, земли, и т.д.) нам нужно его деактивировать (т.е. сделать так, чтобы плавун использовал обычную quake-физику, например, как у объектов на поверхности).
Чтобы активировать у объекта способность плавания, мы должны вызвать свойство, которое называется floaterEnabl. Его мы опишем в файле floater.qc.
Сделайте изменения в файле player.qc (начиная, примерно, с 466-ой строчки) так, как показанно ниже:
Иногда вы сможете найти плавун, который удерживает свою скорость или кружится вокруг в течении того времени, пока он плавает, как поплавок. Скажу сразу, я не писал код для чего-нибудь подобного. Еще объекты иногда удерживают скорость по x или y.
Максумум 32 плавуна могут быть активированны в одно и тоже время (смотрите конфигурации в файле floater.qc) - в этом случае, когда какой-нибудь активированный плавун достигает поверхности (дна, пола, земли, и т.д.) нам нужно его деактивировать (т.е. сделать так, чтобы плавун использовал обычную quake-физику, например, как у объектов на поверхности).
Чтобы активировать у объекта способность плавания, мы должны вызвать свойство, которое называется floaterEnabl. Его мы опишем в файле floater.qc.
Сделайте изменения в файле player.qc (начиная, примерно, с 466-ой строчки) так, как показанно ниже:
new.origin = self.origin;
setmodel (new, gibname);
// FLOATER
// зададим объекту размеры по оси Z ... Тогда будет казаться, что он как бы "висит" в воде
setsize( new, '0 0 -4', '0 0 12' );
// FLOATER
new.velocity = VelocityForDamage (dm);
new.movetype = MOVETYPE_BOUNCE;
Причина для изменения размеров останков игрока или монстра (далее джибзы) заключается в том, чтобы создать иллюзию плавания объекта в воде, в то время, когда он уже находится на дне.
Следующие изменения в файле player.qc будут начинаются, с примерно 484-ой строчки.
Следующие изменения в файле player.qc будут начинаются, с примерно 484-ой строчки.
new.frame = 0;
new.flags = 0;
// FLOATER
// указываем джибзам, чтобы они могли плавать
floaterEnable (new, 2);
// FLOATER
Если сейчас вы откомпилируете и запустите полученное, то вы сможете увидеть джибзы, плавающие в воде. Но можно и дальше пойти.
Вызов свойства floaterEnable заставляет джибз плавать, как только он ныряет под воду.
От этого свойства передаются два параметра.
Первый параметр - объект активируется на плавучесть.
Второй параметр - балансирует центр объекта, когда он в воде или на ее поверхности.
Вы можете сразу следовать на шаг 6, если вам неинтересно, как расчитывается центр объектов, плавающих в воде.
Центр объекта мы подсчитаем следующим образом:
Во-первых, как мы видим центр объекта в Quake?
Все модели в Quake окружает так называемая коробка, находящаяся в координатах 'X Y Z' (ниже центра модели, назовем их mins) и 'X Y Z' (выше центра модели, назовем их maxs), что дает частичный размер (maxs - mins) и центр (размер - maxs) коробке. Вы, наверное спросите, зачем нужна эта коробка? Что ж, она нужна для того, чтобы задать так называемый контур объекта, который будет принимать на себя все попадания по объекту, в том числе и попадания других объектов...
Следующая диаграмма покажет размещение головы (в смысле, воображаемой ;), которая имеет коробку с размерами '-16 -16 0', '16 16 56':
Вызов свойства floaterEnable заставляет джибз плавать, как только он ныряет под воду.
От этого свойства передаются два параметра.
Первый параметр - объект активируется на плавучесть.
Второй параметр - балансирует центр объекта, когда он в воде или на ее поверхности.
Вы можете сразу следовать на шаг 6, если вам неинтересно, как расчитывается центр объектов, плавающих в воде.
Центр объекта мы подсчитаем следующим образом:
Во-первых, как мы видим центр объекта в Quake?
Все модели в Quake окружает так называемая коробка, находящаяся в координатах 'X Y Z' (ниже центра модели, назовем их mins) и 'X Y Z' (выше центра модели, назовем их maxs), что дает частичный размер (maxs - mins) и центр (размер - maxs) коробке. Вы, наверное спросите, зачем нужна эта коробка? Что ж, она нужна для того, чтобы задать так называемый контур объекта, который будет принимать на себя все попадания по объекту, в том числе и попадания других объектов...
Следующая диаграмма покажет размещение головы (в смысле, воображаемой ;), которая имеет коробку с размерами '-16 -16 0', '16 16 56':
====== --- верхушка коробки (56)
------
[голова]
------ --- низ коробки (0), центр (0)
~~~~~~ --- поверхность воды
примечание : наша диаграма не имеет действительных размеров..
Когда мы проверяем, есть ли голова в воде или нет, мы используем этот центр. В случае, когда наша голова выше, то объект почти всегда будет в воде, поэтому этот центр слишком закрытый для дна коробки.
Только координата центра объекта по оси Z важна, когда мы делаем голову плавающей так, как надо. Подсчитав выражение ((56 - 0) - 56) мы найдем эту координату. В результате у нас получилось 0, т.е. координата находится фактически на дне коробки.
Чтобы взять плавающую как поплавок голову, мы передаем ей второй параметр свойства floaterEnable. Значение 5 (я использую 5, так как внешний размер (модель) головы как правило составляет 20% от всего размера коробки) сделает визуальный центр головы в воде так, как показано ниже:
Когда мы проверяем, есть ли голова в воде или нет, мы используем этот центр. В случае, когда наша голова выше, то объект почти всегда будет в воде, поэтому этот центр слишком закрытый для дна коробки.
Только координата центра объекта по оси Z важна, когда мы делаем голову плавающей так, как надо. Подсчитав выражение ((56 - 0) - 56) мы найдем эту координату. В результате у нас получилось 0, т.е. координата находится фактически на дне коробки.
Чтобы взять плавающую как поплавок голову, мы передаем ей второй параметр свойства floaterEnable. Значение 5 (я использую 5, так как внешний размер (модель) головы как правило составляет 20% от всего размера коробки) сделает визуальный центр головы в воде так, как показано ниже:
====== --- верхушка коробки (56)
------
[голова] --- визуальный центр в воде (5)
------ --- низ коробки (0), центр (0)
~~~~~~ --- поверхность воды
Далее немного другой пример. Если тело умирает, то размеры коробки следующие: '-16 -16 -24', '16 16 32'.
====== --- верхушка коробки (56)
--- центр (24)
------
[тело] --- визуальный центр в воде (6)
------
====== --- низ коробки
~~~~~~ --- поверхность воды
В случае мертвого тела, нам нужно изменить центр на -18 (6-24), чтобы изменить визуальный центр для плавуна.
Выше я рассказал не о всех случаях, но тех легких и простых, за которые можно было бы ухватиться (просто все это достаточно сложно объяснить), но если вы попробуете порасчитывать различные изменения в центре плавающего объекта, то я уверен, что вы сможете увидеть, как это все работает.
Выше я рассказал не о всех случаях, но тех легких и простых, за которые можно было бы ухватиться (просто все это достаточно сложно объяснить), но если вы попробуете порасчитывать различные изменения в центре плавающего объекта, то я уверен, что вы сможете увидеть, как это все работает.
Шаг 6 - Доделываем наш мод.
1. Изменения в файле client.qc
Первые изменения начинаются примерно с 483-ей строки и заключаются в следующем:
Первые изменения начинаются примерно с 483-ей строки и заключаются в следующем:
void() PutClientInServer =
{
local entity spot;
// FLOATER
// делаем так, чтобы только мертвые игроки плавали как поплавки
floaterDisable (self);
// FLOATER
spot = SelectSpawnPoint ();
Следующее изменение начинается примерно с 791-ой строки.
if (self.movetype == MOVETYPE_NOCLIP)
return;
// FLOATER
// это будет проверкой для жизней при <0%, чтобы
// мертвый игрок не плавает так, как люди,
// а плавал так, как плавают трупы ;)
if( self.health <=0 ) return;
// FLOATER
if (self.waterlevel !=3)
2. Изменения в файле player.qc
Во-первых, перейдем на, примерно 504-ую строчку и сделаем следующие изменения:
Во-первых, перейдем на, примерно 504-ую строчку и сделаем следующие изменения:
self.flags = self.flags-(self.flags & FL_ONGROUND);
self.avelocity = crandom()* '0 600 0';
// FLOATER
// делаем голову плавающей
floaterEnable (self, 5);
// FLOATER
Далее перейдем на примерно 573-ю строчку:
self.angles_x = 0;
self.angles_z = 0;
// FLOATER
// теперь мы делаем тело плавающим
floaterEnable (self, -18);
// FLOATER
if (self.weapon == IT_AXE)
3. Изменения в файле combat.qc ведутся примерно с 78-ой строчки и заключаются в следующем:
if (self.flags & FL_MONSTER)
{
// FLOATER
// делаем тела мертвых монстров плавающими
floaterEnable (self, -18);
// FLOATER
killed_monsters = killed_monsters+1;
WriteByte (MSG_ALL, SVC_KILLEDMONSTER);
}
4. В файле world.qc изменения начинаются примерно с 390-ой строки:
setorigin (bodyque_head, ent.origin);
setsize (bodyque_head, ent.mins, ent.maxs);
// FLOATER
bodyque_head.sFloating = ent.sFloating;
bodyque_head.state = ent.state;
bodyque_head.speed = ent.speed;
bodyque_head.fOriginOffset = ent.fOriginOffset;
bodyque_head.ltime = ent.ltime;
if( ent.flags & FL_INWATER )
bodyque_head.flags = bodyque_head.flags | FL_INWATER;
// FLOATER
bodyque_head = bodyque_head.owner;
Изменения, которые были чуть выше (4), делают копии
мертвых тел игрока, который еще не появился вновь (т.е. до того, как
игрок нажмет на гашетку, чтобы респавнится).
5. Изменения в файле items.qc протекают с 1380-ой строки:
5. Изменения в файле items.qc протекают с 1380-ой строки:
item.nextthink = time+120; // убрать объект через 2 минуты
item.think = SUB_Remove;
// FLOATER
// делаем рюкзак, который выкидывается при смерти (назовем его бэкпэком) плавающим.
floaterEnable (item, 6);
// FLOATER
Шаг 7 - Вот и все.
Вот и все, что нам нужно было сделать, теперь компилируйте и запускайте Quake с нашими новыми модификациями.
Надеюсь вы будете в восторге от этого!
--------------------------------------------------------------------------------
P.S.: И еще одна последняя вещь. Вы наверное спросите где же все-таки взять содержимое для файла floater.qc? Отвечаем: оно находится ниже....
Надеюсь вы будете в восторге от этого!
--------------------------------------------------------------------------------
P.S.: И еще одна последняя вещь. Вы наверное спросите где же все-таки взять содержимое для файла floater.qc? Отвечаем: оно находится ниже....
/*
==============================================================================
"floater.qc" разработал Alan Kivlin
12 мая 1997 года
Коментарии к модификации
-------------------------------
Данная модификация нужна для того, чтобы делать объекты плавающими в воде и на ее поверхности
Погружающийся объект (назовем его плавун) зависит от скорости падения, но потом падает на какую-нибудь поверхность, где плавает, как поплавок, верх-вниз в течение 30 секунд и наконец падает на саму поверхность и теряет скорость.
Иногда вы сможете найти плавун, который удерживает свою скорость или кружится вокруг в течении того времени, пока он плавает, как поплавок. Скажу сразу, я не писал код для чего-нибудь подобного. Еще объекты иногда удерживают скорость по x или y.
Эта модификация зарегестрирована Alan Kivlin'ом в 1997 году.
Другие авторы МОГУТ использовать эту модификацию, как основу для других своих дополнений.
^^^^^^^^
ВНИМАНИЕ: Alan Kivlin (или как его еще называют Virtuoso) не несет ответственности за ущерб, психологические воздействия, потерю сна или усталость после использования данной модификации.
Если вы скопируете это на компакт-диск, то вы будете должны мне одну копию CD. Если вам это не нравится, то не копируйте это на CD!
Если вы возьмете мою работу и ни разу не упомяните обо мне, то ВЫ СГОРИТЕ В АДУ, так, как горят поросята Фроги в духовке.
Использование этой модификации в комерческих целях привет к тому, что к вам ночью завалятся дружки Сатаны и вам будет очень плохо, потому, что надо сначала переговорить со мной (Alan Kivlin), ok?
Спасибо Dave 'Zoid' Kirsch за его копирайт, который я поместил выше.
==============================================================================
*/
// вызов последнего кадра
float fFloaterLastFrame;
// делаем на объекте метку "floating", когда тот плавает
.string sFloating;
// изменения центра плавуна после проверки
.float fOriginOffset;
// флаги состояния плавуна
float FS_FALLING = 1; // падает
float FS_SURFACING = 2; // на поверхности
float FS_FLOATING = 3; // плавает
float FS_SINKING = 4; // опускается
// максимальное число одновременно плавающих объектов
float FLOATER_MAXIMUM = 32;
//----------------------------------------------------------------------------
// возвращаем значение TRUE (правда), если находится в воде, лаве или кислоте
float (entity ent) floaterInWater;
// делаем объект плавающим
void (entity ent, float offset) floaterEnable;
// делаем объект не плавающим
void (entity ent) floaterDisable;
// контролируем плавание всех плавунов
void () floaterPreThink;
//----------------------------------------------------------------------------
/*
==============================================================================
Значение "floaterInWater"
Возвращаем значение TRUE, если находится в воде, лаве или кислоте
==============================================================================
*/
float (entity ent) floaterInWater =
{
local vector where;
local float contents;
where = ent.origin;
where_z = where_z+ent.fOriginOffset;
contents = pointcontents (where);
if (contents >= -5 && contents <= -3) // в воде (-3), кислоте (-4) или лаве (-5) возвращаем TRUE.
return true;
return false;
};
/*
==============================================================================
Функция "floaterenable" вызывает у объекта способность плавать
==============================================================================
*/
void (entity ent, float offset) floaterenable=
{
local float floatercount;
local entity floater, oldest;
oldest = floater = find (world, sfloating, "floating");
while (floater)
{
floatercount = floatercount + 1;
if (floater.ltime <= oldest.ltime) oldest=floater;
floater = find (floater, sfloating, "floating");
}
if (floatercount == floater_maximum)
floaterdisable (oldest);
ent.sfloating = "floating";
ent.state = FS_FALLING;
ent.speed = 0; // сохраним изменения центра, использованного при проверке
ent.foriginoffset = offset; // "стартуем" время опускания плавуна
ent.ltime = time+30+random()*5;
if (floaterinwater(ent))
{
ent.movetype = MOVETYPE_TOSS;
ent.flags = ent.flags | fl_inwater; // ставим флаг "в воде"
}
else
{
ent.movetype = MOVETYPE_BOUNCE;
ent.flags = ent.flags-(ent.flags & fl_inwater); // убираем флаг "в воде"
}
};
/*
==============================================================================
Функция "floaterdisable" отнимает у объекта способность плавать
==============================================================================
*/
void( entity ent ) floaterdisable=
{
// прекращаем плавание
ent.sfloating = string_null;
};
/*
==============================================================================
Функция "floaterprethink" контролирует плавание всех плавунов
==============================================================================
*/
void() floaterprethink=
{
local entity ent;
if (ffloaterlastframe == framecount) return;
ffloaterlastframe = framecount;
ent = find (world, sfloating, "floating");
while (ent)
{
if ((ent.state == FS_FLOATING) && (ent.flags & fl_onground))
{
// если мы на земле, то мы должны уже упасть, это случается
// когда плавун на движущейся платформе, которая покинула воду
ent.state = FS_FALLING;
ent.speed = 0;
}
if (ent.state == FS_FLOATING)
{
if (ent.speed > 0) ent.velocity_z = ent.speed*(1+frametime*8); // плывем вверх
else ent.velocity_z = 0; // плывем вниз
}
else if (ent.state == FS_SURFACING)
// если у объекта стоит флаг "на поверхности", то
{
if (ent.velocity_z > 0) ent.velocity_z = ent.speed; // сохраняем скорость на поверхности
else if (floaterInWater(ent)) ent.state = FS_SINKING; // can't reach the surface so make it sink
}
else if (ent.state == FS_SINKING)
// иначе, если у объекта стоит флаг "погружающийся", то
if (ent.flags & FL_ONGROUND) floaterDisable(ent);
// если он уже на дне, то отбираем у него способность плавать
else ent.velocity_z = 0; // иначе, заставляем его погружаться медленно
if (floaterInWater(ent))
{
if (!(ent.flags & FL_INWATER))
{
ent.movetype = MOVETYPE_TOSS;
// ставим флаг "в воде"
ent.flags = ent.flags | FL_INWATER;
if (ent.state == FS_FLOATING) ent.speed = 72+random()*16;
// начинаем плыть вверх
else if (ent.state == FS_FALLING) sound (ent, CHAN_BODY, "player/h2ojump.wav", 1, ATTN_NORM);
// проигрываем звук вхождения в воду
}
if (ent.state == FS_FALLING)
{
if (!ent.speed)
{
if (ent.velocity_z < 0) // ставим максимальную скорость падения, до того как плавун достигнет поверхности
ent.speed = ent.velocity_z*8;
}
if ((ent.velocity_z <= ent.speed) || (ent.flags & fl_onground))
{
// start surfacing
ent.state = FS_SURFACING;
ent.speed = 128+random()*32;
ent.velocity_z = ent.speed;
ent.velocity_x = 0;
ent.velocity_y = 0;
}
}
}
else
{
if ((ent.flags & fl_inwater))
{
ent.movetype = MOVETYPE_BOUNCE;
//убираем флаг "в воде"
ent.flags = ent.flags-fl_inwater;
if (ent.state == FS_FLOATING) ent.speed = 0; // начинаем плавать вниз
else if (ent.state == FS_SINKING)
{
ent.state = FS_FALLING;
ent.speed = 0;
}
if (ent.state == FS_FALLING) sound (ent, chan_body, "player/h2ojump.wav" , 1, attn_norm); // play leave water sound
}
if (ent.state == FS_SURFACING)
{
// как только объект касается поверхности воды, то выталкиваем его наружу...
ent.velocity_z = ent.speed*1.5;
// ...и заставляем его плыть вниз
ent.state = FS_FLOATING;
ent.speed = 0;
}
}
if (ent.ltime <=time)
{
if (ent.flags & fl_inwater)
{
// если плавун достаточно опустился в воду, то ставим ему флаг "погружающийся"
ent.state = FS_SINKING;
ent.ltime = time+10+random()*5;
}
}
ent.watertype = 0;
// доругие физические движения не работают для плавуна
ent.flags = ent.flags-(ent.flags & fl_onground);
ent = find (ent, sfloating, "floating");
}