Кодим функции: ульт акса (axe_culling_blade)

Илья

Друзья CG
25 Сен 2015
2,348
41
Предисловие
В процессе разработки аддонов иногда приходится реализовывать стандартные умения героев с нуля вручную, чтобы изменить их функционал под себя. И последний раз когда я это делал, меня посетила мысль - выкладывать подобные реализации с разъяснением кода на форуме, дабы дать новичкам пищу для пережевывания, а старичкам, возможно, заготовки на будущее.


Для кого эта статья
Целевой аудиторией являются новички в моддинге, которые хотят освоить скриптинг на языке lua под workshop. Реализация стандартных абилок с нуля - это превосходный способ освоить механику создания своих собственных умений в workshope.

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

Culling Blade
Итак, что же мы видим, когда используем аксом его ульт:
"ну, он подпрыгивает и бьет наотмашь своим топором противника, лопая его тушку и разбрызгивая на всех вокруг его кишки и кровь"

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

А теперь рассмотрим это описание с точки зрения абилки workshopa.
Здесь мы имеем два варианта, когда все плохо:

1. Применение на цель (target), которая является героем (unit hero)
2. Проверка здоровья цели (попадает ли под порог убийства)
3. Нанесение урона цели
4. Проигрывание анимации


И когда все хорошо:
1. Применение на цель (target), которая является героем (unit hero)
2. Проверка здоровья цели (попадает ли под порог убийства)
3. Убийство цели
4. Поиск союзников в определенном радиусе от нашего героя
5. Наложение бафа (модификатора) на себя и союзников, попавших в радиус.
6. Проигрывание анимации


Естественно шагов можно еще больше накрутить, но мы придерживаемся политики "прозрачности" повествования и лени.

Что мы должны вынести из этого анализа? То, что мы имеем два объекта - абилку и модификатор (баф). Модификатор я реализовывать в ручную не стал, так как нашел здесь подходящий мне modifier_axe_culling_blade_boost. Поэтому сразу переходим к реализации абилки:

Первое, что нам надо, это подготовить скрипт-файл, в котором будем описывать механику работы абилки. Обычно создают подобные файлы по адресу:

...\Steam\SteamApps\common\dota 2 beta\game\dota_addons\"имя аддона"\scripts\vscripts\abilities

Можете и не создавать папку abilities или создать папку с иным именем, это не важно, главное, чтобы лежало все в папке vscripts. Создаем пустой файл с именем axe_culling_blade_custom.lua (ну, как все делают - создаешь txt файл, а там меняешь его формат с txt на lua обычным переименованием) .

Далее заходим в папку

...\Steam\SteamApps\common\dota 2 beta\game\dota_addons\"имя аддона"\scripts\npc\

и открываем файл npc_abilities_custom.txt.

В нем создаем обложку абилки. И, так как я не хотел придумывать чего-то нового (параметры), то просто копируем оригинал и меняем его под себя:

Код:
"axe_culling_blade_custom"
	{
		// General
		//--------------------------------------------------------------------------------------------------------

		"BaseClass"						"ability_lua"
		"AbilityTextureName"			"axe_culling_blade"
		"ScriptFile"					"abilities/axe_culling_blade_custom.lua"
		"AbilityType"					"DOTA_ABILITY_TYPE_ULTIMATE		
		"AbilityBehavior"				"DOTA_ABILITY_BEHAVIOR_UNIT_TARGET"
		"SpellImmunityType"				"SPELL_IMMUNITY_ENEMIES_NO"
		"AbilityUnitTargetTeam"			"DOTA_UNIT_TARGET_TEAM_ENEMY"
		"AbilityUnitTargetType"			"DOTA_UNIT_TARGET_HERO"
		"AbilityUnitTargetFlags"		"DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES"
		"SpellImmunityType"				"SPELL_IMMUNITY_ENEMIES_YES"
		"AbilityUnitDamageType"			"DAMAGE_TYPE_MAGICAL"	
		"FightRecapLevel"				"2"

		// Casting
		//--------------------------------------------------------------------------------------------------------
		"AbilityCastRange"				"150"
		"AbilityCastPoint"				"0.3"

		// Time		
		//--------------------------------------------------------------------------------------------------------
		"AbilityCooldown"				"15 10 5"
	
		// Cost
		//--------------------------------------------------------------------------------------------------------
		"AbilityManaCost"				"10"

		"AbilitySpecial"
		{
			"01"
			{
				"var_type"					"FIELD_INTEGER"
				"kill_threshold"			"250 325 400"
			}
			"02"
			{
				"var_type"					"FIELD_INTEGER"
				"damage"					"150 250 300"
			}
			"03"
			{
				"var_type"					"FIELD_FLOAT"
				"speed_duration"			"3"
			}
			"04"
			{
				"var_type"					"FIELD_INTEGER"
				"speed_aoe"					"900"
			}
			"05"
			{
				"var_type"					"FIELD_INTEGER"
				"kill_threshold_scepter"	"300 500 700"
			}
			"06"
			{
				"var_type"					"FIELD_FLOAT"
				"speed_duration_scepter"	"10"
			}
		}

	}



Что мы здесь указали?

BaseClass - каков наш объект, тип реализации умения, в нашем случае это реализация через lua скрипт - ability_lua
ScriptFile - путь к самому скрипту, то есть - abilities/axe_culling_blade_custom.lua
AbilityType - тип абилки указываем ультой (хотя это все можно прописать в скрипте) - DOTA_ABILITY_TYPE_ULTIMATE
AbilityBehavior - указываем, что у нас абилка будет кастоваться на цель - DOTA_ABILITY_BEHAVIOR_UNIT_TARGET
AbilityUnitTargetTeam - цель эта является врагом - DOTA_UNIT_TARGET_TEAM_ENEMY
AbilityUnitTargetType - и цель эта должна быть героем - DOTA_UNIT_TARGET_HERO

Остальное все расписывать не буду, все это можно найти на форуме.


Теперь перейдем в файл скрипта axe_culling_blade_custom.lua:

Код:
axe_culling_blade_custom = class({})

function axe_culling_blade_custom:GetCastAnimation() 
  return ACT_DOTA_CAST_ABILITY_4 
end

 function axe_culling_blade_custom:GetBehavior() 
   return DOTA_ABILITY_BEHAVIOR_UNIT_TARGET
 end


function axe_culling_blade_custom:OnSpellStart()

  local caster = self:GetCaster()
  local target = self:GetCursorTarget()
  local killThreshold = self:GetSpecialValueFor("kill_threshold_scepter")
  local speedDuration = self:GetSpecialValueFor("speed_duration")

  if caster:HasItemInInventory("item_ultimate_scepter") then
    killThreshold = self:GetSpecialValueFor("kill_threshold_scepter")
    speedDuration = self:GetSpecialValueFor("speed_duration_scepter")
  end
  
  if target:GetHealth() <= killThreshold then
    EmitSoundOn("Hero_Axe.Culling_Blade_Success", self:GetCaster())
    self:KillUnit(target) 
    self:GiveBaff(caster,speedDuration)   
  else
    EmitSoundOn("Hero_Axe.Culling_Blade_Fail", self:GetCaster())
    ApplyDamage( { victim = target, attacker = caster, damage = self:GetSpecialValueFor("damage"),
            damage_type = DAMAGE_TYPE_PURE, ability = self} )
  end
end


function axe_culling_blade_custom:KillUnit(target) 
  target:ForceKill(false)
  ParticleManager:CreateParticle("particles/units/heroes/hero_life_stealer/life_stealer_infest_emerge_bloody.vpcf", PATTACH_ABSORIGIN, target)
end


function axe_culling_blade_custom:GiveBaff(caster,speedDuration)
  local units = FindUnitsInRadius( caster:GetTeamNumber(), caster:GetAbsOrigin(), caster, self:GetSpecialValueFor("speed_aoe") ,
    DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
    
  for i = 1, #units do    
    if units[ i ] then
      units[ i ]:AddNewModifier(caster, self, "modifier_axe_culling_blade_boost", { duration = speedDuration}) 
      ParticleManager:CreateParticle("particles/units/heroes/hero_ogre_magi/ogre_magi_bloodlust_buff_symbol.vpcf", PATTACH_ABSORIGIN, units[ i ])


    end
  end 
end

function axe_culling_blade_custom:IsStealable()
  return true
end

function axe_culling_blade_custom:IsHiddenWhenStolen()
	return true
end

Ну и давайте по кусочкам пережевывать:

Код:
axe_culling_blade_custom = class({})
- говорим, что переменная с именем axe_culling_blade_custom теперь является объектом типа класс class({}). Без этого никуда, это самая важная строчка, сердце скрипта и нашей абилки.

Функция GetCastAnimation() - к ней обращается компилятор, когда работает с анимацией каста абилки.
Код:
function axe_culling_blade_custom:GetCastAnimation() 
  return ACT_DOTA_CAST_ABILITY_4 
end

В теле функции мы просто говорим, чтобы на любые запросы к ней, она "возвращала", отвечала, глобальной переменной ACT_DOTA_CAST_ABILITY_4, что можно подсмотреть здесь. Под этой циферкой (переменной) забита анимация прыжка акса с размашистым ударом топором.

Далее мы так же как и в случае с анимацией, переопределяем метод GetBehavior():

Код:
 function axe_culling_blade_custom:GetBehavior() 
   return DOTA_ABILITY_BEHAVIOR_UNIT_TARGET
 end

Иными словами, говорим, чтобы эта функция на любые расспросы компилятором нашего файла о типе абилки возвращала глобалку DOTA_ABILITY_BEHAVIOR_UNIT_TARGET.

Теперь механизм, логика поведения нашей абилки, переопределяем метод OnSpellStar(), но сначала пара уточнений:
self - текущий класс, т.е. наша абилка
local - тип переменной, что мы определяем (то есть локальный тип переменной, существующей в пределах функции/ блока)

Код:
function axe_culling_blade_custom:OnSpellStart()

  local caster = self:GetCaster() // заводим под кастера (тот, кто активирует абилку) переменную caster 
  local target = self:GetCursorTarget() //заводим под цель (кого атакуем) переменную target 
  local killThreshold = self:GetSpecialValueFor("kill_threshold_scepter") //считываем порог убийства из special переменных нашей абилки в npc_abilities_custom.txt и заносим в переменную killThreshold
  local speedDuration = self:GetSpecialValueFor("speed_duration")//считываем продолжительность бафа из special переменных и заносим значение в переменную speedDuration

//здесь проверяем наличие в инвентаре игрока аганима, чтобы усилить те или иные параметры
  if caster:HasItemInInventory("item_ultimate_scepter") then
    killThreshold = self:GetSpecialValueFor("kill_threshold_scepter")
    speedDuration = self:GetSpecialValueFor("speed_duration_scepter")
  end
 
//теперь проверка здоровья цели  
  if target:GetHealth() <= killThreshold then //вариант, когда все клево
    EmitSoundOn("Hero_Axe.Culling_Blade_Success", self:GetCaster()) //отыгрываем звук успеха
    self:KillUnit(target) //самодельная функция убийства, реализация будет ниже
    self:GiveBaff(caster,speedDuration) //даем баф (самодельная функция, её реализация будет ниже)  
  else //вариант, когда все плохо
    EmitSoundOn("Hero_Axe.Culling_Blade_Fail", self:GetCaster()) //отыгрываем унылый звук
    ApplyDamage( { victim = target, attacker = caster, damage = self:GetSpecialValueFor("damage"),
            damage_type = DAMAGE_TYPE_MAGICAL, ability = self} ) //наносим урон цели, магический
  end
end


Теперь рассмотрим самодельные функции, что я создавал для большей прозрачности кода:

Код:
function axe_culling_blade_custom:KillUnit(target) //в функцию передается цель, target, с ней и работаем
  target:ForceKill(false) //принудительно убиваем цель
  ParticleManager:CreateParticle("particles/units/heroes/hero_life_stealer/life_stealer_infest_emerge_bloody.vpcf", PATTACH_ABSORIGIN, target) //отрисовываем кровищу (оригинальный партикль ульты акса у меня не работал, взял гули) 
end

Код:
function axe_culling_blade_custom:GiveBaff(caster,speedDuration) //в функцию передается кастующий и время бафа
  //ищем героев союзных в радиусе speed_aoe, что в txt файле в специалках указывали
 local units = FindUnitsInRadius( caster:GetTeamNumber(), caster:GetAbsOrigin(), caster, self:GetSpecialValueFor("speed_aoe") ,
    DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
    
  for i = 1, #units do //цикл от 1 до количества найденных юнитов    
    if units[ i ] then //если юнит существует, т.е. реальный объект, а не nil
      //вешаем модификатор 
      units[ i ]:AddNewModifier(caster, self, "modifier_axe_culling_blade_boost", { duration = speedDuration}) 
      //отрисовываем какую-нибудь анимацию
       ParticleManager:CreateParticle("particles/units/heroes/hero_ogre_magi/ogre_magi_bloodlust_buff_symbol.vpcf", PATTACH_ABSORIGIN, units[ i ])
    end
  end 
end

Теперь вспоминаем о многоуважаемом Рубике и переопределяем два метода, существующих только из-за его ульты:

//хотим ли мы, чтобы наш ульт можно было спиздить? Ну, я не вижу проблем в его отработке на другом герое - таймерами мы никакими не пользовались, так что ульт независим. Пускай его пиздят

Код:
function axe_culling_blade_custom:IsStealable()
  return true
end

//хрен знает, что это за функция - скрыт, когда украден - я не вникал, но если её не переопределить, то могут быть ошибки в случае, когда в функции выше стоит true

Код:
function axe_culling_blade_custom:IsHiddenWhenStolen()
	return true
end

Поздравляю, вы превосходны, абилка готова.


Послесловие
Устал. Вспомнил, почему не люблю подобные статьи писать. Думал более дотошно все рассказать, с картинками, но получилось как есть. Вполне приемлемо и некоторые моменты для новичков я уточню здесь:

Много, много мелочей и тонкостей, о которых я не упоминал и не реализовывал в коде, но, как уже говорилось, мы преследуем цель "прозрачности" и "рабочего прототипа", а не "идеального повторения".

Все "переопределяемые" функции можно найти здесь в блоке CDOTABaseAbility. В смысле, те, что можно и иногда нужно переопределять, когда создаете свои абилки.

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

Звуки, где взять их названия? Распаковываете папку soundevents из архива фалов дотки (на форуме поищите инфу, как это делается, ключевое слово GCFScape). И далее в папочке game_sounds_heroes открываете файл нужного вам героя обычным текстовым редактором и смотрите. А в папочке sounds, что лежит там же, где и soundevents, можно и все остальные звуки найти, не только героев.

Партикли, анимация, где её искать? Ну, запускаете в workshope утилиту Particle Manager, или в hammere во вкладке particle. На понравившемся клацаете правой и читаете Assert Info - и вот вам путь до партикля. Так же советую распаковать себе папочку particles из архива дотовксих файлов, чтобы там спокойно смотреть путь к той или иной папке партиклей (для подгрузки).


Советую в дальнейшем самостоятельно реализовывать абилки в такой последовательности
* пассивки - самые простые в реализации умения
* направленные target (как наш ульт акса)
* ненаправленные no target (ну там всякие включаемые или, например, агр у акса)
* направленные point (ну, на точку)
* направленные AOE (по сути те же, что и предыдущие, но с областью)

Естественно это мой взгляд и наверняка я какие-нибудь забыл, но к тому времени как освоите хотя бы вплоть до "no target", уже будете "шарить".

Дополнять/преобразовывать статью я вряд ли буду (скорее что-то новое выложу), но все может быть.
Если хотите почитать похожий от меня материал, то вбивайте в поиск по форуму "кодим функции" и читайте.
 
Последнее редактирование модератором:

CryDeS

Друзья CG
14 Июл 2015
1,210
11
>Объектом типа класс
Объект это экземпляр класса же, он не может быть типом класса, ну. А так отлично.
 
Последнее редактирование модератором:

Илья

Друзья CG
25 Сен 2015
2,348
41
Верное замечание ;)

Верное в том случае, если здесь действительно объявление экземпляра такого класса, как абилка. В противном случае, мы создаем объект, которому назначаем "тип данных" класс.
 
Последнее редактирование модератором:

CryDeS

Друзья CG
14 Июл 2015
1,210
11
Верное замечание ;)

Верное в том случае, если здесь действительно объявление экземпляра такого класса, как абилка. В противном случае, мы создаем объект, которому назначаем "тип данных" класс.
Код:
axe_culling_blade_custom = class({})
Неа, мы создаем экземпляр класса(объект) типа таблица ({}).
class(data_type) - функция создающая из данного типа объект.
 
Последнее редактирование модератором:

Илья

Друзья CG
25 Сен 2015
2,348
41
Ну да, верно, в lua же все через таблицы реализовано.
 

qofma

Активный
1 Апр 2016
81
0
Верно ли я понял,улучшить скил можно любым предметом ?
 

Илья

Друзья CG
25 Сен 2015
2,348
41
В смысле?

Если ты про этот блок

Код:
  if caster:HasItemInInventory("item_ultimate_scepter") then
    killThreshold = self:GetSpecialValueFor("kill_threshold_scepter")
    speedDuration = self:GetSpecialValueFor("speed_duration_scepter")
  end

Ну, да, можешь вместо аганима прописать другую вещь и будет тебе улучшение скилла.
 
Последнее редактирование модератором:

CryDeS

Друзья CG
14 Июл 2015
1,210
11
[quote author=Илья link=topic=978.msg5135#msg5135 date=1467747188]
В смысле?

Если ты про этот блок

Код:
  if caster:HasItemInInventory("item_ultimate_scepter") then
    killThreshold = self:GetSpecialValueFor("kill_threshold_scepter")
    speedDuration = self:GetSpecialValueFor("speed_duration_scepter")
  end

Ну, да, можешь вместо аганима прописать другую вещь и будет тебе улучшение скилла.
[/quote]
Немного не правильно делаешь, если на герое висит съеденный аганим от алхима?
Используй caster:HasScepter()
 
Последнее редактирование модератором:

Илья

Друзья CG
25 Сен 2015
2,348
41
Ну да, для аганимов от алхима нужно делать так.
 

somsim

Пользователь
14 Мар 2017
23
0
[quote author=Илья link=topic=978.msg5126#msg5126 date=1467647264]
Предисловие
В процессе разработки аддонов иногда приходится реализовывать стандартные умения героев с нуля вручную, чтобы изменить их функционал под себя. И последний раз когда я это делал, меня посетила мысль - выкладывать подобные реализации с разъяснением кода на форуме, дабы дать новичкам пищу для пережевывания, а старичкам, возможно, заготовки на будущее.


Для кого эта статья
Целевой аудиторией являются новички в моддинге, которые хотят освоить скриптинг на языке lua под workshop. Реализация стандартных абилок с нуля - это превосходный способ освоить механику создания своих собственных умений в workshope.

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

Culling Blade
Итак, что же мы видим, когда используем аксом его ульт:
"ну, он подпрыгивает и бьет наотмашь своим топором противника, лопая его тушку и разбрызгивая на всех вокруг его кишки и кровь"

Применяем на цель, которая должна быть героем.
1. Применение на цель (target), которая является героем (unit hero)
1. Применение на цель (target), которая является героем (unit hero)
[/quote]
Это конечно не то что бы прям придирка НО ульт акса используется на все ентити !не включая здания и прочие нестандартные как юниты цели(например: варды веника)!
Об чем это я - егошный ульт можно использовать и в крипа и (если не поправили или так и должно быть) в курьера. соответственно в эти рамки входят и герои
так что это не обязательно должен быть "unit hero"
[size=7pt]реп'ки б[/size]
 
Последнее редактирование модератором:

Илья

Друзья CG
25 Сен 2015
2,348
41
Ну естественно не обязательно. Цель статьи не сделать копию ульта, а показать, как её делать.
 

MahouShoujo

Продвинутый
3 Ноя 2016
251
23

ha-ha.

Функция KillUnit из двух строчек которая вызывается в одном месте - зачем?

способность не перезаряжает себя при удачном использовании.

кулдаун не меняется с появлением аганима.

оригинальный ульт не делает :KillUnit а убивает настоящим дамагом.

тип урона в случае фейла неверный.

//хрен знает, что это за функция
оригинальный партикль ульты акса у меня не работал, взял гули
Туториал мастер просто
 

CryDeS

Друзья CG
14 Июл 2015
1,210
11
ha-ha.

Функция KillUnit из двух строчек которая вызывается в одном месте - зачем?

способность не перезаряжает себя при удачном использовании.

кулдаун не меняется с появлением аганима.

оригинальный ульт не делает :KillUnit а убивает настоящим дамагом.

тип урона в случае фейла неверный.
Туториал мастер просто
Ульт акса как раз таки и делает KillUnit при достаточном хп. Именно по этому он и пробивает крест дазла. Ни один урон не пробьет крест дазла, ваш к.о.
И вот ты отметился во всех почти туторах и всех назвал раками, мб сам тогда напишешь парочку туторов? Или по чему предложишь новичкам учиться тогда?

---
upd
Кстати о, с выходом 7.0 такое больше нельзя делать caster:HasItemInInventory("item_ultimate_scepter") т.к она вернет true даже если айтем в бэкпаке.
 
Последнее редактирование модератором:

ZLOY

Администратор
Команда форума
27 Июн 2016
953
182
Doc прав, не умеешь - не пиши.
+убийство не засчитывается герою таким способом.
[quote author=CryDeS link=topic=978.msg9335#msg9335 date=1494654848]
Кстати о, с выходом 7.0 такое больше нельзя делать caster:HasItemInInventory("item_ultimate_scepter") т.к она вернет true даже если айтем в бэкпаке.
[/quote]
Код:
function CDOTA_BaseNPC:HasItemInInventory(itemName,bIncludeBackpack)
	local slots = bIncludeBackpack and 8 or 5

	for i = 0, slots do
		local item = self:GetItemInSlot(i)
		if item and item:GetAbilityName() == itemName then
			return true
		end
	end
	return false
end
 
Последнее редактирование модератором:

MahouShoujo

Продвинутый
3 Ноя 2016
251
23
Миллион раз было обмусолено уже что он сначала пурджит баффы включая крест а потом дамажит. Возьми бладсикера, поставь и у него и у акса по 200 хп и ультани аксом в блейдмейл, накинув на акса бладрейдж. Бладсикер выживет.

:HasScepter не помогает?

Я туторы уже писал. Сиди, переводи, если хочется
 

I_Explorer

Друзья CG
30 Июл 2016
318
16
Проект
Жизнь в тюрьме
Миллион раз было обмусолено уже что он сначала пурджит баффы включая крест а потом дамажит. Возьми бладсикера, поставь и у него и у акса по 200 хп и ультани аксом в блейдмейл, накинув на акса бладрейдж. Бладсикер выживет.

:HasScepter не помогает?

Я туторы уже писал. Сиди, переводи, если хочется
Только что в лобби проверил вроде бы сикер умирает, далеко тебе пока до фишек Нс-а.
 
Последнее редактирование модератором:
Реклама: