• Уважаемые гости и новички, приветствуем Вас на нашем форуме
    Здесь вы можете найти ответы практически на все свои вопросы о серии игр «Готика» (в том числе различных модах на нее), «Ведьмак», «Ризен», «Древние свитки», «Эра дракона» и о многих других играх. Можете также узнать свежие новости о разработке новых проектов, восхититься творчеством наших форумчан, либо самим показать, что вы умеете. Ну и наконец, можете обсудить общие увлечения или просто весело пообщаться с посетителями «Таверны».

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!

Тутор по созданию стрелы массового поражения

redleha


Модостроитель
Регистрация
26 Фев 2008
Сообщения
735
Благодарности
665
Баллы
245
Тутор по созданию стрелы массового поражения.

Пишу этот тутор, чтобы показать, что есть ещё порох в пороховницах Готы, и можно найти интересные скрытые от глаз вещи.
Итак, приступим.

Цель: создать тип заряда лука - Взрывная стрела;.
Особенности заряда: При попадании в жертву окружающие НПСы,находящиеся на расстоянии 6 метров,получают осколочный магический урон в 40 пунктов.
И на них проигрывается эффект горения.
(Это очень похоже на действие заклинания Огненный шторм)
Также сама жертва получает дополнительный магический урон в 50 пунктов.
Пункт 1.
Создайте новую инстанцию стрелы, например, с именем ItRw_Addon_ExplosiveArrow. Можно просто скопировать обычную стрелу и переименовать.

Пункт 2.
Во-первых, надо прописать визуальный эффект огненного шторма при попадании в жертву. Для этого создадим маленькую функцию:
func void B_ArrowBonusDamage(var C_Npc oth,var C_Npc slf)
{
var C_Item readyweap; //Текущее оружие в руках
readyweap = Npc_GetReadiedWeapon(oth); //Текущее оружие в руках
//Если типом заряда текущего оружия является Взрывная стрела, то
if(readyweap.munition == ItRw_Addon_ExplosiveArrow)
{
Проигрываем на цель эффект огненного шторма
Wld_PlayEffect("spellFX_Firestorm_SPREAD",slf,slf,0,0,0,FALSE);
Далее делаем проверку, является ли цель бессмертным НПСом.
if(slf.flags == 0) //на бессмертных не действует
{
Помимо физического урона, расчитанного движком, добавляется урон магией.
if(slf.protection[PROT_MAGIC] < 50)
{
slf.attribute[ATR_HITPOINTS] = slf.attribute[ATR_HITPOINTS] - (50 - slf.protection[PROT_MAGIC]);
};
};
Не забываем и про самого стрелка, если он находится в зоне радиуса осколков.
Наносим стрелку осколочный урон в 40 магических пунктов и проигрываем эффект горения.
if(Npc_GetDistToNpc(slf,oth) <= 600)
{
Wld_PlayEffect("VOB_MAGICBURN",oth,oth,0,0,0,FALSE);
if(slf.flags == 0) //на бессмертных не действует
{
if(slf.protection[PROT_MAGIC]) < 40)
{
slf.attribute[ATR_HITPOINTS] = slf.attribute[ATR_HITPOINTS] - (40 - slf.protection[PROT_MAGIC]);
};
};
Если осколочный урон окажется фатальным, то чтобы избежать глюков, проигрываем анимацию смерти.
if(oth.attribute[ATR_HITPOINTS] <= 0)
{
AI_PlayAni(oth,"T_DEAD");
};
};
};
Даем опыт герою, если противник умрёт после эффекта стрелы.
if((slf.attribute[ATR_HITPOINTS] <= 0) && Npc_IsPlayer(oth))
{
B_GivePlayerXP(slf.level*XP_PER_VICTORY);
};
};
И Добавим наше творение в начало функции B_AssessDamage - описывающую восприятие повреждения жертвы.
func void B_AssessDamage()
{
B_ArrowBonusDamage(other,self);
...Продолжение...
};
Пункт 2.
И Самый главный. Как заставить осколки нанести урон окружающим НПСам.
Моей первой попыткой было использование функции :
void AI_SetNpcsToState (c_npc self, func aiStateFunc, int radius); - переводит всех НПС, находящихся от НПС self на расстоянии radius сантиметров, в соответствующее состояние, описанное функцией aiStateFunc.
Этой функцией aiStateFunc я сделал состояние ZS_Fire. НЕ буду приводить её ввиду недоделанности.
Смысл в том её был, что после попадания взрывной стрелы все НПСы в радиусе поражения осколков получали урон и переходили в состояние горения.
Но главной проблемой при тесте было то, что эта встроенная функция AI_SetNpcsToState почему-то не всегда срабатывала, да и из состояния ZS_Fire НПСы преходили как-то неграмотно. Благо мне в голову вовремя пришло другое решение.
Но вы можете попробывать и вышеуказанный способ.

Так что же пришло мне в голову. Покажу сразу решение.
У всех НПСов есть восприятия. Описаны они в функции Perception.d
Их описание указано в туторе Vam'а. Так что не буду на этом останавливаться.
Так вот. Если посмотреть внимательно, то у монстров есть такое восприятие как PERC_ASSESSOTHERSDAMAGE.
func void Perception_Set_Monster_Rtn()
{
Npc_PercEnable(self, PERC_ASSESSOTHERSDAMAGE, B_MM_AssessOthersDamage);
};
Этим восприятием является реакция на урон "жертвы - соседа". В нашем случае жертвой-соседом будет цель попадания стрелы.
И описана она функцией B_MM_AssessOthersDamage.
У человека такой функции нет ни в наборе стандартых восприятий, ни в минимальном.
Ну чтож активируем эту функцию, добавив строчку в минимальный и нормальный набор восприятий.
func void Perception_Set_Normal()
{
self.senses = SENSE_HEAR | SENSE_SEE;
self.senses_range = PERC_DIST_ACTIVE_MAX;
if(Npc_KnowsInfo(self,1) || C_NpcIsGateGuard(self))
{
Npc_SetPercTime(self,0.3);
}
else
{
Npc_SetPercTime(self,1);
};
Npc_PercEnable(self,PERC_ASSESSPLAYER,B_AssessPlayer);
Npc_PercEnable(self,PERC_ASSESSENEMY,B_AssessEnemy);
Npc_PercEnable(self,PERC_ASSESSMAGIC,B_AssessMagic);
Npc_PercEnable(self,PERC_ASSESSDAMAGE,B_AssessDamage);
******Добавил*****
Npc_PercEnable(self,PERC_ASSESSOTHERSDAMAGE,B_AssessOthersDamage);
******************
Npc_PercEnable(self,PERC_ASSESSMURDER,B_AssessMurder);
Npc_PercEnable(self,PERC_ASSESSTHEFT,B_AssessTheft);
Npc_PercEnable(self,PERC_ASSESSUSEMOB,B_AssessUseMob);
Npc_PercEnable(self,PERC_ASSESSENTERROOM,B_AssessPortalCollision);
Npc_PercEnable(self,PERC_ASSESSTHREAT,B_AssessThreat);
Npc_PercEnable(self,PERC_DRAWWEAPON,B_AssessDrawWeapon);
Npc_PercEnable(self,PERC_ASSESSFIGHTSOUND,B_AssessFightSound);
Npc_PercEnable(self,PERC_ASSESSQUIETSOUND,B_AssessQuietSound);
Npc_PercEnable(self,PERC_ASSESSWARN,B_AssessWarn);
Npc_PercEnable(self,PERC_ASSESSTALK,B_AssessTalk);
Npc_PercEnable(self,PERC_MOVEMOB,B_MoveMob);
};
func void Perception_Set_Minimal()
{
self.senses = SENSE_HEAR | SENSE_SEE;
self.senses_range = PERC_DIST_ACTIVE_MAX;
Npc_PercEnable(self,PERC_ASSESSMAGIC,B_AssessMagic);
Npc_PercEnable(self,PERC_ASSESSDAMAGE,B_AssessDamage);
******Добавил*****
Npc_PercEnable(self,PERC_ASSESSOTHERSDAMAGE,B_AssessOthersDamage);
******************
Npc_PercEnable(self,PERC_ASSESSMURDER,B_AssessMurder);
Npc_PercEnable(self,PERC_ASSESSTHEFT,B_AssessTheft);
Npc_PercEnable(self,PERC_ASSESSUSEMOB,B_AssessUseMob);
Npc_PercEnable(self,PERC_ASSESSENTERROOM,B_AssessPortalCollision);
};
Теперь надо описать саму функцию B_AssessOtherDamage.
func void B_AssessOthersDamage()
{
//****************************************************************************
var C_Item readyweap;
readyweap = Npc_GetReadiedWeapon(other);
//******************************************************************************
//******************************************************************************
if(readyweap.munition == ItRw_Addon_ExplosiveArrow)
{
if((Npc_GetDistToNpc(self,victim) <= 600) && (readyweap.munition == ItRw_Addon_ExplosiveArrow))
{
Wld_PlayEffect("VOB_MAGICBURN",self,self,0,0,0,FALSE);
if(slf.flags == 0) //на бессмертных не действует
{
if(slf.protection[PROT_MAGIC]) < 40
{
slf.attribute[ATR_HITPOINTS] = slf.attribute[ATR_HITPOINTS] - (40 - slf.protection[PROT_MAGIC]);
};
};
if((self.attribute[ATR_HITPOINTS] <= 0) && Npc_IsPlayer(other))
{
B_GivePlayerXP((self.level * XP_PER_VICTORY));
};
if(self.aivar[AIV_PARTYMEMBER] == TRUE)
{
if(Npc_IsPlayer(victim))
{
Npc_ClearAIQueue(self);
B_ClearPerceptions(self);
Npc_SetTarget(self,other);
AI_StartState(self,ZS_Attack,0,"");
return;
};
if(Npc_IsPlayer(other) && !Npc_IsDead(victim))
{
Npc_ClearAIQueue(self);
B_ClearPerceptions(self);
Npc_SetTarget(self,victim);
AI_StartState(self,ZS_Attack,0,"");
return;
};
};
if(Wld_GetGuildAttitude(self.guild,other.guild) != ATT_FRIENDLY)
{
Npc_ClearAIQueue(self);
B_ClearPerceptions(self);
//Npc_SetTarget(self,other);
B_Attack(self,other,AR_ReactToDamage,0);
return;
};
};
if(Npc_IsInState(self,ZS_Attack))
{
return;
};
};
return;
};
На этом вроде бы кажется всё... Но не всё так быстро.
Первым глюком при тесте будет то, что если физический урон стрелы окажется выше текущих хитпойнтов жертвы стрелы, то визуальный эффект огненного шторма просто не воспроизведётся. Смысл заключается в том, что жертва сразу перейдёт в состояние ZS_Dead и функция B_AssessDamage просто не сработает. Это одна из ошибок, которую допускают некоторые модостроители.
Добавим следующие строчки в функцию ZS_Dead
func void ZS_Dead()
{
var C_Item readyweap;
readyweap = Npc_GetReadiedWeapon(other);
......................Продолжение........................
if(readyweap.munition == ItRw_Addon_ExplosiveArrow)
{
Wld_PlayEffect("spellFX_Firestorm_SPREAD",self,self,0,0,0,FALSE);
if(Npc_GetDistToNpc(self,other) <= 600)
{
Wld_PlayEffect("VOB_MAGICBURN",other,other,0,0,0,FALSE);
if(other.flags == 0) //на бессмертных не действует
{
if(other.protection[PROT_MAGIC]) < 40)
{
other.attribute[ATR_HITPOINTS] = other.attribute[ATR_HITPOINTS] - (40 - other.protection[PROT_MAGIC]);
};
};
Если осколочный урон окажется фатальным, то чтобы избежать глюков, проигрываем анимацию смерти.
if(other.attribute[ATR_HITPOINTS] <= 0)
{
AI_PlayAni(other,"T_DEAD");
};
};
};
.............................................................
};
Следующим косяком при тесте окажется то , в ситуации, когда рядом с жертвой-целью в радиусе осколков окажется человек и физический урон стрелы окажется выше текущих хитпойнтов жертвы стрелы, то осколочный урон не достигнет человека.
Этот косяк исправляется тем, что практически всё тело функции добавляется в B_AssessMurder - функцию, описывающую реакцию человека на убийство "соседа".
func void B_AssessMurder()
{
//**********************************************************
var C_Item readyweap;
readyweap = Npc_GetReadiedWeapon(other);
//*********************************************
if(Hlp_GetInstanceID(self) == Hlp_GetInstanceID(other))
{
return;
};
if((Npc_GetDistToNpc(self,other) > PERC_DIST_INTERMEDIAT) && (Npc_GetDistToNpc(self,victim) > PERC_DIST_INTERMEDIAT))
{
return;
};
if((Npc_GetHeightToNpc(self,other) > PERC_DIST_HEIGHT) && (Npc_GetHeightToNpc(self,victim) > PERC_DIST_HEIGHT))
{
return;
};
if(!Npc_CanSeeNpcFreeLOS(self,other) && !Npc_CanSeeNpcFreeLOS(self,victim))
{
return;
};
//*****************НАШе ТЕЛО**************************************
if(readyweap.munition == ItRw_Addon_ExplosiveArrow)
{
if((Npc_GetDistToNpc(self,victim) <= 600) && (readyweap.munition == ItRw_Addon_ExplosiveArrow))
{
Wld_PlayEffect("VOB_MAGICBURN",self,self,0,0,0,FALSE);
if(self.flags == 0) //на бессмертных не действует
{
if(self.protection[PROT_MAGIC]) < 40)
{
self.attribute[ATR_HITPOINTS] = slf.attribute[ATR_HITPOINTS] - (40 - slf.protection[PROT_MAGIC]);
};
};
if((self.attribute[ATR_HITPOINTS] <= 0) && Npc_IsPlayer(other))
{
B_GivePlayerXP((self.level * XP_PER_VICTORY));
};
};
};
//************************************************************************
if(B_AssessEnemy())
{
return;
};
};
Осталось только не забыть монстров и добавить в функцию B_MM_AssessDamage некоторые дополнения и одно исправление.
func void B_MM_AssessOthersDamage()
{
//********************************************************************
var C_Item readyweap;
readyweap = Npc_GetReadiedWeapon(other);

//**********************************************************************
if((Npc_GetDistToNpc(self,victim) > PERC_DIST_INTERMEDIAT) && (Npc_GetDistToNpc(self,other) > PERC_DIST_INTERMEDIAT))
{
return;
};
if(!Npc_CanSeeNpcFreeLOS(self,victim))
{
return;
};
//Закоментированно мной и изменено в связи с тем, что у монстров нет уникальных имен и осколки не сработают на соседа, если ID соседа будет равно ID жертвы.
/*
if((Hlp_GetInstanceID(victim) == Hlp_GetInstanceID(self)) || (Hlp_GetInstanceID(other) == Hlp_GetInstanceID(self)))
{
return;
};

*/
//Измененный вариант
if(Hlp_GetInstanceID(other) == Hlp_GetInstanceID(self))
{
return;
};

//**************************************
if((Npc_GetDistToNpc(self,victim) <= 600) && (readyweap.munition == ItRw_Addon_ExplosiveArrow))
{
Wld_PlayEffect("VOB_MAGICBURN",self,self,0,0,0,FALSE);
if(slf.flags == 0) //на бессмертных не действует
{
if(slf.protection[PROT_MAGIC]) < 40)
{
slf.attribute[ATR_HITPOINTS] = slf.attribute[ATR_HITPOINTS] - (40 - slf.protection[PROT_MAGIC]);
};
};
if((self.attribute[ATR_HITPOINTS] <= 0) && Npc_IsPlayer(other))
{
B_GivePlayerXP((self.level * XP_PER_VICTORY));
};
};
................................
};

*******************************************************************************
ИТОГО
Основной целью тутора было не написание за вас скриптов, а показать интересный способ и недопущение многих ошибок. Если встретите какие-то мелкие ошибки и недочеты, то это скорей всего связано с написанием самого тутора.
Исходя из всего вышеизложенного вы можете создать не только взрывные стрелы, но всё, что может вообразить ваш мозг, как то:
Стрела с эффектом ледяной волны, Стрела с эффектом Огненного дождя, Волны смерти и любого другого средства массового поражения.
Дерзайте.
Если что-то непонятно по тексту и самому тутору, спрашивайте... :)
За сим откланиваюсь...
:)

P.S. Особая благодарность выражается Lev-Lion'у за предоставленную идею и совместную работу и тестирование взрывной стрелы.
 

Bezio

Участник форума
Регистрация
27 Июн 2012
Сообщения
211
Благодарности
206
Баллы
200
Классный тутор;), А не знаешь, как можно сделать возможность одним ударом меча наносить повреждение нескольким NPC одновременно?
 

MEG@VOLT

★★★★★★★★★
ТехАдмин
Регистрация
24 Мар 2006
Сообщения
9.860
Благодарности
6.740
Баллы
1.625
Сделать те же самые эффекты мечу.
 

Bezio

Участник форума
Регистрация
27 Июн 2012
Сообщения
211
Благодарности
206
Баллы
200
Спасиб, надо проверить...
 

redleha


Модостроитель
Регистрация
26 Фев 2008
Сообщения
735
Благодарности
665
Баллы
245
Да, аналогично делать.
 
Сверху Снизу