Тутор по созданию стрелы массового поражения.
Пишу этот тутор, чтобы показать, что есть ещё порох в пороховницах Готы, и можно найти интересные скрытые от глаз вещи.
Итак, приступим.
Цель: создать тип заряда лука - "Взрывная стрела".
Особенности заряда: При попадании в жертву окружающие НПСы,находящиеся на расстоянии 6 метров,получают осколочный магический урон в 40 пунктов.
И на них проигрывается эффект горения.
(Это очень похоже на действие заклинания "Огненный шторм")
Также сама жертва получает дополнительный магический урон в 50 пунктов.
Пункт 1.
Создайте новую инстанцию стрелы, например, с именем ItRw_Addon_ExplosiveArrow. Можно просто скопировать обычную стрелу и переименовать.
Пункт 2.
Во-первых, надо прописать визуальный эффект "огненного шторма" при попадании в жертву. Для этого создадим маленькую функцию:
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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);
};
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
И Добавим наше творение в начало функции B_AssessDamage - описывающую восприятие повреждения жертвы.
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
func void B_AssessDamage()
{
B_ArrowBonusDamage(other,self);
...Продолжение...
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
Пункт 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.
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
func void Perception_Set_Monster_Rtn()
{
Npc_PercEnable(self, PERC_ASSESSOTHERSDAMAGE, B_MM_AssessOthersDamage);
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
Этим восприятием является реакция на урон "жертвы - соседа". В нашем случае жертвой-соседом будет цель попадания стрелы.
И описана она функцией B_MM_AssessOthersDamage.
У человека такой функции нет ни в наборе стандартых восприятий, ни в минимальном.
Ну чтож активируем эту функцию, добавив строчку в минимальный и нормальный набор восприятий.
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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);
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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);
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
Теперь надо описать саму функцию B_AssessOtherDamage.
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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;
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
На этом вроде бы кажется всё... Но не всё так быстро.
Первым глюком при тесте будет то, что если физический урон стрелы окажется выше текущих хитпойнтов жертвы стрелы, то визуальный эффект огненного шторма просто не воспроизведется. Смысл заключается в том, что жертва сразу перейдёт в состояние ZS_Dead и функция B_AssessDamage просто не сработает. Это одна из ошибок, которую допускают некоторые модостроители.
Добавим следующие строчки в функцию ZS_Dead
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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");
};
};
};
.............................................................
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
Следующим косяком при тесте окажется то , в ситуации, когда рядом с жертвой-целью в радиусе осколков окажется человек и физический урон стрелы окажется выше текущих хитпойнтов жертвы стрелы, то осколочный урон не достигнет человека.
Этот косяк исправляется тем, что практически всё тело функции добавляется в B_AssessMurder - функцию, описывающую реакцию человека на убийство "соседа".
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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;
};
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
Осталось только не забыть монстров и добавить в функцию B_MM_AssessDamage некоторые дополнения и одно исправление.
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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));
};
};
................................
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
*******************************************************************************
ИТОГО
Основной целью тутора было не написание за вас скриптов, а показать интересный способ и недопущение многих ошибок. Если встретите какие-то мелкие ошибки и недочеты, то это скорей всего связано с написанием самого тутора.
Исходя из всего вышеизложенного вы можете создать не только взрывные стрелы, но всё, что может вообразить ваш мозг, как то:
Стрела с эффектом ледяной волны, Стрела с эффектом Огненного дождя, Волны смерти и любого другого средства массового поражения.
Дерзайте.
Если что-то непонятно по тексту и самому тутору, спрашивайте...
За сим откланиваюсь...
P.S. Особая благодарность выражается[span style=\'font-size:21pt;line-height:100%\']Lev-Lion'у за предоставленную идею и совместную работу и тестирование взрывной стрелы.[/span]
Пишу этот тутор, чтобы показать, что есть ещё порох в пороховницах Готы, и можно найти интересные скрытые от глаз вещи.
Итак, приступим.
Цель: создать тип заряда лука - "Взрывная стрела".
Особенности заряда: При попадании в жертву окружающие НПСы,находящиеся на расстоянии 6 метров,получают осколочный магический урон в 40 пунктов.
И на них проигрывается эффект горения.
(Это очень похоже на действие заклинания "Огненный шторм")
Также сама жертва получает дополнительный магический урон в 50 пунктов.
Пункт 1.
Создайте новую инстанцию стрелы, например, с именем ItRw_Addon_ExplosiveArrow. Можно просто скопировать обычную стрелу и переименовать.
Пункт 2.
Во-первых, надо прописать визуальный эффект "огненного шторма" при попадании в жертву. Для этого создадим маленькую функцию:
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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);
};
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
И Добавим наше творение в начало функции B_AssessDamage - описывающую восприятие повреждения жертвы.
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
func void B_AssessDamage()
{
B_ArrowBonusDamage(other,self);
...Продолжение...
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
Пункт 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.
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
func void Perception_Set_Monster_Rtn()
{
Npc_PercEnable(self, PERC_ASSESSOTHERSDAMAGE, B_MM_AssessOthersDamage);
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
Этим восприятием является реакция на урон "жертвы - соседа". В нашем случае жертвой-соседом будет цель попадания стрелы.
И описана она функцией B_MM_AssessOthersDamage.
У человека такой функции нет ни в наборе стандартых восприятий, ни в минимальном.
Ну чтож активируем эту функцию, добавив строчку в минимальный и нормальный набор восприятий.
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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);
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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);
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
Теперь надо описать саму функцию B_AssessOtherDamage.
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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;
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
На этом вроде бы кажется всё... Но не всё так быстро.
Первым глюком при тесте будет то, что если физический урон стрелы окажется выше текущих хитпойнтов жертвы стрелы, то визуальный эффект огненного шторма просто не воспроизведется. Смысл заключается в том, что жертва сразу перейдёт в состояние ZS_Dead и функция B_AssessDamage просто не сработает. Это одна из ошибок, которую допускают некоторые модостроители.
Добавим следующие строчки в функцию ZS_Dead
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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");
};
};
};
.............................................................
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
Следующим косяком при тесте окажется то , в ситуации, когда рядом с жертвой-целью в радиусе осколков окажется человек и физический урон стрелы окажется выше текущих хитпойнтов жертвы стрелы, то осколочный урон не достигнет человека.
Этот косяк исправляется тем, что практически всё тело функции добавляется в B_AssessMurder - функцию, описывающую реакцию человека на убийство "соседа".
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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;
};
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
Осталось только не забыть монстров и добавить в функцию B_MM_AssessDamage некоторые дополнения и одно исправление.
[!--SpoilerBegin--][/div][table border=\'0\' align=\'center\' width=\'95%\' cellpadding=\'3\' cellspacing=\'1\'][tr][td onclick=\'ShowTableTdFromTd(this,1)\' style=\"cursor:pointer\"]Скрытый текст (нажмите сюда, чтобы увидеть) [/td][/tr][tr][td style=\"display:none\" id=\'SPOILER\'][!--SpoilerEBegin--]
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));
};
};
................................
};
[!--SpoilerEnd--][/td][/tr][/table][div class=\'postcolor\'][!--SpoilerEEnd--]
*******************************************************************************
ИТОГО
Основной целью тутора было не написание за вас скриптов, а показать интересный способ и недопущение многих ошибок. Если встретите какие-то мелкие ошибки и недочеты, то это скорей всего связано с написанием самого тутора.
Исходя из всего вышеизложенного вы можете создать не только взрывные стрелы, но всё, что может вообразить ваш мозг, как то:
Стрела с эффектом ледяной волны, Стрела с эффектом Огненного дождя, Волны смерти и любого другого средства массового поражения.
Дерзайте.
Если что-то непонятно по тексту и самому тутору, спрашивайте...
За сим откланиваюсь...
P.S. Особая благодарность выражается[span style=\'font-size:21pt;line-height:100%\']Lev-Lion'у за предоставленную идею и совместную работу и тестирование взрывной стрелы.[/span]