Объявления

Друзья, если не получается зарегистрироваться, напишите на почту vdv_forever@bk.ru.
Я оторву свою задницу от всех дел и обязательно Вас активирую! :smile10:
Добро пожаловать на геройский форум! :smile25:

Как создать плагин для HD мода

Герои Меча и Магии III: Возрождение Эрафии, Герои Меча и Магии III Дыхание Смерти, Герои Меча и Магии III Клинок Армагеддона, Герои Меча и Магии III Хроники Героев
offlinevoid_17  
имя: DM
Мастер
Мастер
 
Сообщения: 416
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 69 раз.

Re: Как создать плагин для HD мода

Сообщение void_17 » 05 ноя 2021, 04:33

Цитата:
Inlined functions would not appear in the symbols (and in fact, symbols do not catch all, there are still plenty of functions called subXYZ) and are difficult to find because there is no call to them.


I know.

Цитата:
but if you go in those functions of the sod 3.2 exe you will see the instructions are inlined. Finding all of them is always possible but it's quite a heavy task whereas a much more beneficial mod would be to change the object limit from 16bit to 32bit so that 252x252 maps could actually be filled properly.

Exactly! I hate it.
 
Moreover, these functions are inlined(at least in SoD) too:
Almost all of the STL functions;
void advManager::GetCursorSampleSet(int);
void TAdvMenu::InitAdvMenu(void);
static bool combatManager::InInvisibleColumn(int)
void type_AI_combat_data::cast_spells(class type_AI_combat_data &,enum type_speed_catagory);
void type_AI_combat_data::do_melee_combat(class type_AI_combat_data &);
void type_AI_combat_data::do_general_melee(class type_AI_combat_data &);
type_town_threat_checker::type_town_threat_checker(long);
type_garrison_purchaser::type_garrison_purchaser(long);
bool hero::is_in_spellbook(enum SpellID)const;
bool combatManager::RowIsOdd(int)const;
void heroWindowManager::FizzleForwardX(struct SLimitData const &,int);
Almost all of the ExtraInfoUnion:: functions;
font::GetCharacterWidth(unsigned char)const;
DoSinglePlayerWindow;
TNormalDialogInfo::TNormalDialogInfo(void);
bool CDPlayHeroes::TransmitRemoteDataDPID(class CNetMsg *,unsigned long,bool,bool);
void combatManager::remove_corpse(class army *);
bool swapManager::IsLeftHero(void);
bool swapManager::IsRightHero(void);
bool swapManager::CanModHero(int);
AND SO ON! :smile30: <--- NWC Programmer


Цитата:
btw, I'm not seeing these two signatures ?
bool type_point::operator<(type_point & const)const;
bool type_point::operator>(type_point & const)const;

I mistaken. There are:
bool type_point::operator==(type_point const &)const;
bool type_point::operator!=(type_point const &)const;
both of them were inlined in SoD. :smile26:
Despite that, type_point::operator< and type_point::operator> did really exist and they were inlined even in RoE.
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5222
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2027 раз.

Re: Как создать плагин для HD мода

Сообщение AlexSpl » 05 ноя 2021, 09:03

Обзор 16 новых заклинаний на канале "Heroes 3.5: In the Wake of Gods":



Цитата:
I understand that all these new spells are made more to demonstrate the capabilities of the plugin. All the spells that give creatures' abilities (vampirism, death blow, claws ...) do not fit into the game design of heroes 3, this is the game design of heroes 4. Death cloud, explosion, summon spells simply repeat existing spells. Fear needs to be tested, seemingly imbalanced. Mobility and the eye of the magi are the only spells that do something new and fit in well.

Вот коммент интересный нашёл под видео. Rolex, Вы согласны с автором? :smile1:
Последний раз редактировалось AlexSpl 05 ноя 2021, 12:11, всего редактировалось 1 раз.
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5222
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2027 раз.

Re: Как создать плагин для HD мода

Сообщение AlexSpl » 05 ноя 2021, 11:00

Краткий гайд по добавлению новых заклинаний с помощью плагина NewSpells

Часть первая. Бафы/дебафы



0. Первым делом следует создать три картинки для заклинания: для магической гильдии (свиток), для книги магии и для эффекта в бою. Для этого следует открыть ресурсный файл NewSpells.lod, что лежит в папке Data, программой MMArchive от GrayFace и извлечь оттуда кадры заклинаний, хранящиеся в файлах: SpellScr.def, spells.def и SpellInt.def (нужно просто кликнуть правой кнопкой мыши по каждому файлу в списке и выбрать пункт меню "Extract For DefTool..."). Получится три папки. В каждую из них для удобства копируем соответствующую картинку нового заклинания: в SpellScr - картинку для магической гильдии (свиток), в spells - картинку для книги магии, в SpellInt - картинку для эффекта в бою.

Далее, запускаем программу DefTool за авторством того же GrayFace, открываем файл с расширением .hdl в папке - давайте начнём с картинок для гильдии магов - SpellScr (или как Вы её назвали при извлечении кадров) и, выделив название последней картинки в списке файлов картинок слева, переходим на вкладку Frames. Здесь выбираем нашу новую картинку для магической гильдии и жмём Enter (может, можно и перетаскиванием, но я не пробовал). Картинка будет добавлена сразу после выделенного файла. Возвращаемся на предыдущую вкладку, сохраняем проект (иконка дискеты) и создаём новый def-файл с добавленной картинкой нового заклинания, щёлкнув на кнопке "Make Def". Повторяем операцию для двух других картинок, т.е. создаём новые spells.def и SpellInt.def. Обратите внимание на то, что в SpellScr.def кадр нового заклинания должен располагаться до последнего кадра с изображением магической книги для гильдии магии Conflux c Aurora Borealis, т.е. быть предпоследним.

Осталось упаковать полученные SpellScr.def, spells.def и SpellInt.def в архив NewSpells.lod. Для этого просто перетащите файлы в окно MMArchive с открытым архивом NewSpells.lod, по желанию нажмите кнопку "Optimize" и смело закрывайте программу MMArchive (lod будет сохранён автоматически). Если для нового заклинания требуется собственная анимация, перед выходом из программы добавьте сюда также def-файл анимации.

Если для нового заклинания Вам требуется добавить звук, воспроизводимый во время каста, добавьте его в архив NewSpells.snd, лежащий в папке Data, с помощью всё той же программы MMArchive.

Поздравляю. Самое сложное позади. Теперь пишем код нового заклинания. Почему, спросите Вы, самое сложное позади, ведь код - это же сложнее? А потому, что в примере мы будем добавлять простейшее заклинание Магии Воды Water Shield, повышающее защиту дружественного отряда на 1 + 10%/1 + 10%/2 + 20%/3 + 30% с округлением вниз для None/Basic/Advanced/Expert Water Magic соответственно на SP раундов. Для определённости бонус защиты будем считать в процентах от базовой защиты существа. Например, для Архангела прирост защиты составит 4/8/12 ед. (Basic/Advanced/Expert). Чтобы было чуть интересней, пусть заклинание, пока действует, также снимает с отряда один случайный негативный эффект в начале каждого раунда. Естественно, в общем случае, чем сложнее заклинание, тем больше кода для него потребуется написать. Итак.



1. Первым делом добавляем структуру нового заклинания в файл NewSpells.ini и заполняем её. Так как мы добавляем баф, то можно просто скопировать структуру заклинания Toughness, например, и изменить параметры заклинания на нужные Вам. Ключ "<Animation.ID>" пока не трогаем: мы будем использовать новую анимацию, а поэтому вернёмся сюда, когда присвоим ей порядковый номер. Вопросы могут возникать и по ключу "<Attributes>". Атрибуты заклинания (или, по-другому, флаги) - это битовое поле свойств заклинания в десятичной системе счисления. Здесь я приведу несколько известных флагов из HoMM3_ids.h:

Код: Выделить всё
// spells flags
#define SPF_BATTLE              0x00000001 //- BF spell
#define SPF_MAP                 0x00000002 //- MAP spell
#define SPF_TIME                0x00000004 //- Has a time scale (3 rounds or so)
#define SPF_CREATURE            0x00000008 //- Creature Spell
#define SPF_ON_STACK            0x00000010 //- target - single stack
#define SPF_ON_SHOOTING_STACK   0x00000020 //- target - single shooting stack (???)
#define SPF_HAS_MASS_ON_EXPERT  0x00000040 //- has a mass version at expert level
#define SPF_ON_LOCATION         0x00000080 //- target - any location
//0x00000100 -
//0x00000200 -
#define SPF_MIND                0x00000400 //- Mind spell
#define SPF_FRIENDLY_HAS_MASS   0x00000800 //- friendly and has mass version
#define SPF_NOT_ON_MACHINE      0x00001000 //- cannot be cast on SIEGE_WEAPON
#define SPF_ARTIFACT            0x00002000 //- Spell from Artifact

Как пользоваться? Решите, какие свойства будет иметь Ваше заклинание, выберите подходящие флаги и сложите их, сумму переведите в десятичную систему счисления. Для нашего заклинания подойдут флаги SPF_BATTLE + SPF_TIME + SPF_ON_STACK + 0x40000 (флаг AI для бафов/дебафов) = 0x40015 = 262165.

Далее, идём в папку Language, открываем ini-файл с переводом (пока у нас есть только English.ini) и добавляем структуру с текстами для нового заклинания. Теперь заполняем значения ключей имени (краткого и полного) и описания (общего и для каждой ступени развития школы магии). Сохраняем NewSpells.ini и ini-файл с переводом.



2. Подготовка структур нового заклинания для будущего кода.

2.1. Добавьте идентификатор нового заклинания и увеличьте количество заклинаний в игре на 1:

Код: Выделить всё
#define SPELLS_NUM 95
...
#define SPL_EXPLOSION 93
#define SPL_WATER_SHIELD 94

Если Вы используете собственную анимацию для нового заклинания, также добавьте её идентификатор в список идентификаторов анимаций и увеличьте количество анимаций на 1:

Код: Выделить всё
#define ANIMS_NUM 90
...
#define ANIM_INCINERATION 88
#define ANIM_WATER_SHIELD 89

Не забудьте указать номер анимации в NewSpells.ini: <Animation.ID> = 89

Добавьте по одному элементу в каждую из таблиц spellIndirectTableA-F (это будут значения по умолчанию для случая, когда что-то пойдёт не так). Далее приведены значения по умолчанию для бафов/дебафов: таблица A - 4, таблица B - 17, таблица C - 32, таблица D - 9, таблица E - 16, таблица F - 9. В будущих версиях плагина я всё перепроверю и постараюсь избавиться от spellIndirectTable.

2.2. В общем случае потребуется завести структуру для хранения параметров нового заклинания, а также массив таких структур для каждого отряда на поле боя. Делается это следующим образом:

Код: Выделить всё
struct WaterShieldSpell
{
   int defenseBonus;   
}
waterShieldSpell[2][21];

Здесь в поле defenseBonus мы будем хранить бонус к защите, получаемый отрядом при касте заклинания Water Shield. Для простых заклинаний можно использовать поле expertise и хранить там уровень школы магии, а величины эффектов брать из NewSpells.ini ("<Effect.None>" .. "<Effect.Expert>"), однако в нашем случае у заклинания два параметра: фиксированный и прибавка в процентах от базовой защиты, а в общем случае они могут быть разными (например, 1 + 40% или 3 + 80% и т.п.), поэтому чтобы не усложнять код сверх необходимого, будем считать эту прибавку лишь однажды (при наложении заклинания прямо в коде функции applySpell()).

2.3. Добавьте идентификатор и название секции заклинания в следующие два массива:

Код: Выделить всё
const int newSpell[] =
{
   SPL_POISON, SPL_DISEASE, SPL_AGE, SPL_FEAR, SPL_DEATH_CLOUD_NEW,
   SPL_DEATH_BLOW, SPL_DRAIN_LIFE, SPL_SPRITE, SPL_MAGIC_ELEMENTAL, SPL_FIREBIRD,
   SPL_MOBILITY, SPL_EYE_OF_THE_MAGI, SPL_TOUGHNESS, SPL_BEHEMOTHS_CLAWS, SPL_INCINERATION,
   SPL_EXPLOSION, SPL_WATER_SHIELD
};

const char* newSpellName[] =
{
   "Poison", "Disease", "Age", "Fear", "Death Cloud",
   "Death Blow", "Drain Life", "Summon Sprite", "Summon Magic Elemental", "Summon Firebird",
   "Mobility", "Eye of the Magi", "Toughness", "Behemoth's Claws", "Incineration",
   "Explosion", "Water Shield"
};

2.4. В функцию afterInit() добавляем инициализацию новой анимации:

Код: Выделить всё
// Water Shield
SpellAnim[ANIM_WATER_SHIELD].defName = "WaterShield.def";
SpellAnim[ANIM_WATER_SHIELD].name = "WaterShield";
SpellAnim[ANIM_WATER_SHIELD].type = 1;

Если Вы хотите инициализировать параметры заклинания для отрядов перед битвой, делайте это в функции initSpellMods(). Для Water Shield ничего инициализировать не нужно. Повезло. Но имейте в виду, что такая возможность имеется, если будете пробовать добавлять более сложные заклинания.



3. Переходим к коду. Для бафов/дебафов обычно достаточно двух функций: applySpell(), которая вызывается при касте заклинания, и resetSpell(), которая вызывается при его снятии (Cure, Dispel, окончание действия). Так как у нашего заклинания есть дополнительный эффект, то нам потребуется ещё одна функция, выполняющаяся в начале каждого игрового раунда - newRoundSpellSettings().

3.1. Пишем код для applySpell(). Здесь нужно добавить кейс для Water Shield:

Код: Выделить всё
case SPL_WATER_SHIELD:
   if (schoolLevel == SL_NONE || schoolLevel == SL_BASIC)
     waterShieldSpell[stack->side][stack->index_on_side].defenseBonus += (int)(1 + o_pCreatureInfo[stack->creature_id].defence * 0.1);

   if (schoolLevel == SL_ADVANCED)
     waterShieldSpell[stack->side][stack->index_on_side].defenseBonus += (int)(2 + o_pCreatureInfo[stack->creature_id].defence * 0.2);

   if (schoolLevel == SL_EXPERT)
     waterShieldSpell[stack->side][stack->index_on_side].defenseBonus += (int)(3 + o_pCreatureInfo[stack->creature_id].defence * 0.3);

   stack->creature.defence += waterShieldSpell[stack->side][stack->index_on_side].defenseBonus;

   break;

Желательно, конечно, иметь базовые знания C++, но можно и без них, если делать всё по аналогии. stack->side - сторона отряда (атакующая/обороняющаяся), stack->index_on_side - порядковый номер отряда у атакующей/обороняющейся стороны, o_pCreatureInfo[stack->creature_id].defence - базовая защита существа в отряде. Дальше - математика.

3.2. Пишем код для resetSpell(). Аналогично пункту 3.1 добавляем новый кейс для Water Shield. Теперь мы забираем бонус (waterShieldSpell[stack->side][stack->index_on_side].defenseBonus) обратно:

Код: Выделить всё
case SPL_WATER_SHIELD:
   stack->creature.defence -= waterShieldSpell[stack->side][stack->index_on_side].defenseBonus;
   
   if (stack->creature.defence < 0)
      stack->creature.defence = 0;

   break;

Последнее условие нужно для того, чтобы защита отряда не упала ниже 0.

3.3. Осталось сделать самое сложное для этого заклинания: снятие случайного негативного эффекта в начале раунда (для этого существует функция newRoundSpellSettings()) - и новое заклинание можно тестировать в бою.

Добавляем условие в newRoundSpellSettings():

Код: Выделить всё
if (stack->active_spell_duration[SPL_WATER_SHIELD])
{
   int negativeSpell[SPELLS_NUM];
   int negativeSpellsNum = 0;
 
   for (int i = 0; i < SPELLS_NUM; ++i)
      if (stack->active_spell_duration[i] && o_Spell[i].type == -1)
         negativeSpell[negativeSpellsNum++] = i;

   if (negativeSpellsNum > 0)
      stack->CancelIndividualSpell(negativeSpell[Randint(0, negativeSpellsNum - 1)]);
}

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



Вот и всё. Новое заклинание готово радовать игроков :smile20:
Последний раз редактировалось AlexSpl 06 ноя 2021, 11:39, всего редактировалось 21 раз(а).
Вернуться к началу

offlineRoseKavalier  
Мастер
Мастер
 
Сообщения: 331
Зарегистрирован: 23 сен 2017, 17:00
Пол: Не указан
Поблагодарили: 233 раз.

Re: Как создать плагин для HD мода

Сообщение RoseKavalier » 05 ноя 2021, 13:54

void_17 писал(а):

I know.

Okay, my bad - wires crossed during translation :smile16:

void_17 писал(а):

Exactly! I hate it.
 
Moreover, these functions are inlined(at least in SoD) too:
Almost all of the STL functions;
void advManager::GetCursorSampleSet(int);
void TAdvMenu::InitAdvMenu(void);
static bool combatManager::InInvisibleColumn(int)
void type_AI_combat_data::cast_spells(class type_AI_combat_data &,enum type_speed_catagory);
void type_AI_combat_data::do_melee_combat(class type_AI_combat_data &);
void type_AI_combat_data::do_general_melee(class type_AI_combat_data &);
type_town_threat_checker::type_town_threat_checker(long);
type_garrison_purchaser::type_garrison_purchaser(long);
bool hero::is_in_spellbook(enum SpellID)const;
bool combatManager::RowIsOdd(int)const;
void heroWindowManager::FizzleForwardX(struct SLimitData const &,int);
Almost all of the ExtraInfoUnion:: functions;
font::GetCharacterWidth(unsigned char)const;
DoSinglePlayerWindow;
TNormalDialogInfo::TNormalDialogInfo(void);
bool CDPlayHeroes::TransmitRemoteDataDPID(class CNetMsg *,unsigned long,bool,bool);
void combatManager::remove_corpse(class army *);
bool swapManager::IsLeftHero(void);
bool swapManager::IsRightHero(void);
bool swapManager::CanModHero(int);
AND SO ON! :smile30: <--- NWC Programmer

STL functions make a mess for certain, but others I don't find those so bad.
In the end it's compiler's decision, although when the person hitting the button forgets to remove symbols then that's indeed the programmer's fault to our benefit!

void_17 писал(а):

I mistaken. There are:
bool type_point::operator==(type_point const &)const;
bool type_point::operator!=(type_point const &)const;
both of them were inlined in SoD. :smile26:
Despite that, type_point::operator< and type_point::operator> did really exist and they were inlined even in RoE.

How would type_point_A > type_point_B logic even go by logically?
(100,2,1) > ( 2, 100, 1) ?
Вернуться к началу

offlinevoid_17  
имя: DM
Мастер
Мастер
 
Сообщения: 416
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 69 раз.

Re: Как создать плагин для HD мода

Сообщение void_17 » 06 ноя 2021, 09:01

Цитата:
Okay, my bad - wires crossed during translation :smile16:

Hey ! I didn't really use google Translator. :smile29:

Цитата:
How would type_point_A > type_point_B logic even go by logically?
(100,2,1) > ( 2, 100, 1) ?


They decided to compare one coordinate only. I don't remember which one exactly.
Последний раз редактировалось void_17 06 ноя 2021, 09:35, всего редактировалось 2 раз(а).
Вернуться к началу

offlinevoid_17  
имя: DM
Мастер
Мастер
 
Сообщения: 416
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 69 раз.

Re: Как создать плагин для HD мода

Сообщение void_17 » 06 ноя 2021, 09:04

@AlexSPL, вы не против, что я по завершению ваших гайдов выложу их все в группу вк?
https://vk.com/mods4heroes3
Вернуться к началу

offlineАватара пользователя
AlexSpl  
имя: Александр
Эксперт
Эксперт
 
Сообщения: 5222
Зарегистрирован: 17 сен 2010, 12:58
Пол: Мужчина
Награды: 14
Высшая медаль (1) Победителю турнира по HMM1_TE (2) Победителю этапа по HMM1 (1) Победителю этапа по HMM2 (1) Лучшему из лучших (1) 2 место 1 этапа по HMM1 (1)
3 место 1 этапа по HMM1 (1) 1 место 2 этапа по HMM2 (1) Победителю турнира по KB (2) Победителю турнира по KB (1) Грандмастер оффлайн-турниров (1) Боевой шлем (1)
Поблагодарили: 2027 раз.

Re: Как создать плагин для HD мода

Сообщение AlexSpl » 06 ноя 2021, 09:40

Не против, конечно. Я только за, если кто-нибудь добавит новое заклинание с их помощью :smile1:
Вернуться к началу

offlinevoid_17  
имя: DM
Мастер
Мастер
 
Сообщения: 416
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 69 раз.

Re: Как создать плагин для HD мода

Сообщение void_17 » 06 ноя 2021, 10:04

AlexSpl писал(а):

Обзор 16 новых заклинаний на канале "Heroes 3.5: In the Wake of Gods":



Цитата:
I understand that all these new spells are made more to demonstrate the capabilities of the plugin. All the spells that give creatures' abilities (vampirism, death blow, claws ...) do not fit into the game design of heroes 3, this is the game design of heroes 4. Death cloud, explosion, summon spells simply repeat existing spells. Fear needs to be tested, seemingly imbalanced. Mobility and the eye of the magi are the only spells that do something new and fit in well.

Вот коммент интересный нашёл под видео. Rolex, Вы согласны с автором? :smile1:


Полностью согласен, как по мне не вписывается. Хотя для WoG-a подошел бы, но он мне не нравится...
Вернуться к началу

offlineRoseKavalier  
Мастер
Мастер
 
Сообщения: 331
Зарегистрирован: 23 сен 2017, 17:00
Пол: Не указан
Поблагодарили: 233 раз.

Re: Как создать плагин для HD мода

Сообщение RoseKavalier » 06 ноя 2021, 13:50

The translator is on my end, the entire page is modified no matter the language... when English is translated to English assuming it is Russian you can expect funny things - so as I said my bad for assuming it was not already in English :smile14:

If you stumble on function/address where operator< or operator> are used, let me know I'd be curious to see the context!
Вернуться к началу

offlinevoid_17  
имя: DM
Мастер
Мастер
 
Сообщения: 416
Зарегистрирован: 25 апр 2021, 15:05
Откуда: Оттуда
Пол: Мужчина
Поблагодарили: 69 раз.

Re: Как создать плагин для HD мода

Сообщение void_17 » 06 ноя 2021, 16:10

https://mobile.twitter.com/diskblitz/st ... 5349466113
Я уже полгода пытаюсь выдолбить этого мужика. Что не только, он тупо игнорит. ТЕБЕ ЭТОТ КОД НАХРЕН НЕ НУЖЕН!
Уже сил нет. Может забить хер?
Вернуться к началу

Пред.След.

Вернуться в Общий раздел

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1

cron