Т.к. добавить новое оружие нельзя, будем "вешать" на уже существующее.
Для начала определимся, что за оружие у нас будет, и какое из существующих будет заменять.
Я выбрал гранатомёт. А заменять будем "xm1014", потому, что иконка немного похожа на модель.
Приблизительный план действий:
Меняем модель.
Блокируем нажатие кнопок.
Делаем свою атаку.
Отлавливаем касание объекта(гранаты), наносим урон.
Начнём.
[pawn]
- #include <amxmodx>
- #include <fakemeta_util>
- #include <hamsandwich>
- // Регистрируем массивы для пути с названием моделей
- new v_m79 [] = "models/weapon/v_m79.mdl"
- new p_m79 [] = "models/weapon/p_m79.mdl"
- public plugin_init()
- {
- // Регистрируем событие выхватывания оружия - xm1014
- RegisterHam(Ham_Item_Deploy, "weapon_xm1014", "deploy_xm1014", 1)
- }
- public plugin_precache()
- {
- // Закачиваем модели игрокам
- precache_model(v_m79)
- precache_model(p_m79)
- }
- // В функцию передаётся только id оружия
- public deploy_xm1014(wpn)
- {
- // Получаем владельца id игрока
- static id; id = get_pdata_cbase(wpn, 41, 4)
- // Заменяем V модель (от первого лица)
- set_pev(id, pev_viewmodel2, v_m79)
- // Заменяем P модель (ту что вядят другие игроки у вас в руках)
- set_pev(id, pev_weaponmodel2, p_m79)
- }
Первый пункт готов, но модель заменится у всех, кто держит в руках это оружие...
Для этого зарегистрируем булевую переменную, которой и будем отличать наше оружие от обычного.
А так как игроков много, сделаем лучше массив.
[pawn]
- new bool:weapon_m79[33]
И поставим условие:
[pawn]
- // В функцию передаётся только id оружия
- public deploy_xm1014(wpn)
- {
- // Получаем владельца, id игрока
- static id; id = get_pdata_cbase(wpn, 41, 4)
- // Если ячейка соответствующая id игрока имеет верное значение (true), то
- if(weapon_m79[id])
- {
- // Заменяем V модель (от первого лица)
- set_pev(id, pev_viewmodel2, v_m79)
- // Заменяем P модель (ту что вядят другие игроки у вас в руках)
- set_pev(id, pev_weaponmodel2, p_m79)
- }
- }
Так. Теперь сделаем выдачу, например вводом в чат "/m79".
Стоимость делать не будем, это легко можно сделать потом.
[pawn]
- public plugin_init()
- {
- // Регистрируем консольную команду для выдачи
- register_clcmd("say /m79", "cmd_m79")
- }
- // Выдаём оружие по команде
- public cmd_m79(id)
- {
- // Защита от повторной выдачи
- // Если ячейка имеет ложное значение (false)
- if(!weapon_m79[id])
- {
- // Меняем нашу переменную(ячейку массива) на true т.е. правда
- weapon_m79[id] = true
- // Выдаём игроку оружие
- fm_give_item(id, "weapon_xm1014")
- }
- // Если ячейка имеет верное значение говорим что у игрока уже есть это оружие
- else client_print(id, print_center, "You have m79")
- }
Английский знаю плохо, в школе учил немецкий (хотя немецкий я знаю ещё хуже)
Теперь давайте сделаем так, чтобы у игрока не было несколько основных оружий..
[pawn]
- // Зарегистрируем двумерный массив, и заполним его ячейки названиями основного оружия
- // В том порядке как оно идёт, а остальные и нулевую(id начинаются с 1) оставим пустыми ""
- new drop_weapon[][] =
- {
- "","weapon_p228","weapon_shield","weapon_scout","","weapon_xm1014","","weapon_mac10","weapon_aug","","","",
- "weapon_ump45","weapon_sg550","weapon_galil","weapon_famas","weapon_usp","","weapon_awp","weapon_mp5navy",
- "weapon_m249","weapon_m3","weapon_m4a1","weapon_tmp","weapon_g3sg1","","","weapon_sg552","weapon_ak47","","weapon_p90"
- }
А это поставим перед выдачей нашего оружия
[pawn]
- // Если у игрока уже есть основное оружие, то
- if(get_pdata_int(id, 116, 5))
- {
- // Получаем все индексы оружия игрока и общее количество
- new weapons[32], num; get_user_weapons(id, weapons, num)
- // Делаем цикл
- for(new i = 0; i < num; i++)
- {
- // Если ячейка не пустая, то выдать в консоль игроку drop+содержимое ячейки
- if(drop_weapon[weapons[i]][0]) engclient_cmd(id, "drop", drop_weapon[weapons[i]])
- }
- }
Теперь можно сделать чтобы наше оружие можно было выкинуть, и снова подобрать.
[pawn]
- // Добавляем w модель (та что лежит на земле)
- new w_m79 [] = "models/weapon/w_m79.mdl"
- public plugin_init()
- {
- // Отлавливаем момент, когда объекту присваивается модель
- register_forward(FM_SetModel, "m79_set_model", 1)
- }
- public plugin_precache()
- {
- // Добавляем модель в прекеш
- precache_model(w_m79)
- }
- // В функцию передаётся id объекта, строка с путём и названием модели
- public m79_set_model(ent, model[])
- {
- // Делаем условие, что id правильный и название модели w_xm1014
- if(pev_valid(ent) && equali(model, "models/w_xm1014.mdl", 20))
- {
- // Получаем id владельца
- static id; id = pev(ent, pev_owner)
- // Ещё одно условие, что у игрока активна наша переменная, т.е. у него есть наше оружие.
- if(weapon_m79[id])
- {
- // Если всё верно, то заменяем модель на нашу
- engfunc(EngFunc_SetModel, ent, w_m79)
- // И сбрасываем переменную
- weapon_m79[id] = false
- // Блокируем дальнейшую обработку события
- return FMRES_SUPERCEDE
- }
- }
- // Необходимо для компилятора, если модель не та просто выход из функции
- return FMRES_IGNORED
- }
Выкинули, теперь подобрать...
Расписывать не буду почему я выбрал такой способ, скажу так:
Если сейчас подобрать оружие, оно будет "стандартное", стандартная модель, атака(хотя до неё ещё не дошли)
Поэтому убъём 2х зайцев одним событием, запишем id weapon_entity и сравним, поехали.
[pawn]
- // Массив куда будем записывать id объектов(оружия) пусть будет максимум 64
- new wpn_id[64]
- public plugin_init()
- {
- // Регистрируем событие получения игроком оружия - xm1014
- RegisterHam(Ham_Item_AddToPlayer, "weapon_xm1014", "add_xm1014")
- }
- public add_xm1014(wpn, id)
- {
- // Проверяем есть ли у игрока наше оружие
- if(weapon_m79[id])
- {
- // Делаем цикл по массиву в котором будем хранить id объектов
- for(new i = 0; i < sizeof(wpn_id); i++)
- {
- // Если ячейка пустая
- if(!wpn_id[i])
- {
- // Записываем в неё данные
- wpn_id[i] = wpn
- // И заканчиваем цикл
- break
- }
- }
- }
- // А если условие не верно, то
- else
- {
- // Опять же, делаем цикл по массиву с данными
- for(new i = 0; i < sizeof(wpn_id); i++)
- {
- // Сравниваем цифры
- if(wpn == wpn_id[i])
- {
- // Если совпало, значит это наш гранатомёт, меняем переменную
- weapon_m79[id] = true
- // Выход
- break
- }
- }
- }
- }
- // Теперь сделаем ограничение
- // Если последняя ячейка занята
- if(wpn_id[charsmax(wpn_id)])
- {
- // Сбрасываем переменную
- weapon_m79[id] = false
- // А про эту переменную чуть позже
- limit_m79 = false
- // Говорим игроку, что мол всё, лимит исчерпан, и блокируем событие
- client_print(id, print_center, "Ended m79")
- return FMRES_SUPERCEDE
- }
- // Ставим это перед циклом
А чтобы настырный игрок не нагружал сервер бесполезными тырканьями, создадим ещё одну булевую
[pawn]
- // Для ограничения покупки, кончилось место в массиве
- new bool:limit_m79
И изменим её там, где у нас кончилось место в массиве, а при выдаче добавим в условие.
На этом с моделями закончим.
Приступим ко второму пункту плана ) Отлов нажатия кнопок и их блокировка.
[pawn]
- public plugin_init()
- {
- // Регистрируем событие ммм...затрудняюсь сказать чего,
- // но если следовать логике, то связано это с вводом в консоль...
- register_forward(FM_CmdStart, "fw_cmd_start")
- }
- // В функцию передаётся id игрока, информация о всех введённых данных(думаю так), и что-то про отбор (игроков?)
- public fw_cmd_start(id, uc_handle, seed)
- {
- // Ставим условие, что в руках у нас xm1014 и переменная верна(т.е. гранатомёт)
- if(weapon_m79[id] && get_user_weapon(id) == CSW_XM1014)
- {
- // Получаем все нажатые кнопки
- new buttons = get_uc(uc_handle, UC_Buttons)
- // Если среди них есть кнопка атаки, то
- if((buttons & IN_ATTACK))
- {
- //блокировка кнопки
- buttons &= ~IN_ATTACK
- set_uc(uc_handle, UC_Buttons, buttons)
- // Здесь будет наша атака
- }
- }
- }
Кнопка атаки блокирована(вторичная не используется), но остались спрайты и звуки ((
[pawn]
- public plugin_init()
- {
- // Регистрируем событие обновления данных клиента
- register_forward(FM_UpdateClientData, "fw_UpdateClientData_Post", 1)
- }
- // В функцию передаётся id игрока, информация о оружии, и событии?
- public update_client_data(id, sendweapons, cd_handle)
- {
- // Опять условие, что это гранатомёт, и если да, то переносим воспроизведение на 1 тысячную секунды вперёд
- if(weapon_m79[id] && get_user_weapon(id) == CSW_XM1014) set_cd(cd_handle, CD_flNextAttack, get_gametime() + 0.001)
- }
Всё, оружие зачищено.
Переходим к атаке.
[pawn]
- //Добавляем ещё одну модель, можно взять стандартную из valve\models и качать не придётся
- new grenade_model [] = "models/grenade.mdl"
- // Добавляем звук
- new fire_sound [] = "weapons/m79/m79_fire1.wav"
- // Переменная под индекс спрайта
- new trail
- public plugin_precache()
- {
- // Закачиваем модель игрокам, на всякий случай, если модели нет, то она скачается
- precache_model(grenade_model)
- // Закачиваем звук игрокам
- precache_sound(fire_sound)
- // Закачиваем спрайт игрокам
- trail = precache_model("sprites/laserbeam.spr")
- }
- // Атака
- public fire_grenade(id)
- {
- // Создаём объект
- new grenade = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "info_target"))
- // Проверяем что его ид больше 0, т.е. объект создался
- if(grenade)
- {
- // Присваиваем имя объекту
- set_pev(grenade, pev_classname, "weapon_m79")
- // Устанавливаем модель
- engfunc(EngFunc_SetModel, grenade, grenade_model)
- // Получаем координаты игрока, где-то в районе груди
- static Float:origin[3]; pev(id, pev_origin, origin)
- // Проверяем, если у игрока нажата кнопка Ctrl т.е. он сидит, прибавить к высоте 13 юнит
- // Просто когда игрок сидит, координата высоты получается ровно пол, а нам нужна высота вгляда, центр экрана
- if(pev(id, pev_button) & IN_DUCK) origin[2] += 13.0
- // Если игрок стоит, то прибавить 16 юнит. Цифры получены опытным путём)
- else origin[2] += 16.0
- // Устанавливаем объекту полученные координаты
- set_pev(grenade, pev_origin, origin)
- // Задаём размеры(как хитбокс) зад, лево, низ
- set_pev(grenade, pev_mins, {-1.0, -1.0, -1.0})
- // Коробка как бы получается их безразмерных листов, которые придвинули друг к другу,
- // и сами координаты - удаление от центра двигают эти листы только по одной оси...
- // Перед, право, верх
- set_pev(grenade, pev_maxs, {1.0, 1.0, 1.0})
- // Устанавливаем "плотность" объекта, здесь как бы полый - SOLID_SLIDEBOX
- set_pev(grenade, pev_solid, 3)
- // Устанавливаем тип спавна объекта(гравитация), падение до столкновения - MOVETYPE_TOSS
- set_pev(grenade, pev_movetype, 6)
- // Устанавливаем владельца
- set_pev(grenade, pev_owner, id)
- // Получить скорость игрока и умножить по взгляду на 2000
- static Float:velocity[3]; velocity_by_aim(id, 2000, velocity)
- // Устанавливаем полученную скорость объекту
- set_pev(grenade, pev_velocity, velocity)
- // Получаем угол из направления скорости(вектор)
- static Float:flAngle[3]; engfunc(EngFunc_VecToAngles, velocity, flAngle)
- // Устанавливаем угол объекту
- set_pev(grenade, pev_angles, flAngle)
- // Задаём угол при столкновении
- set_pev(id, pev_punchangle, Float:{0.0, 0.0,0.0})
- // Воспроизводим звук выстрела
- emit_sound(grenade, CHAN_WEAPON, fire_sound, VOL_NORM, ATTN_NORM, 0, PITCH_NORM)
- // Рисуем след
- message_begin(MSG_BROADCAST, SVC_TEMPENTITY)
- write_byte(TE_BEAMFOLLOW) // Тип
- write_short(grenade) // Объект
- write_short(trail) // id спрайта
- write_byte(10) // Время существования
- write_byte(3) // Толщина линии
- write_byte(255) // Красный
- write_byte(255) // Зелёный
- write_byte(255) // Синий
- write_byte(255) // Яркость
- message_end() // Конец
- }
- }
Добавляем туда где блокировали кнопку:
[pawn]
- fire_grenade(id)
Уже в принципе можно опробовать, только за 1 короткое нажатие выстрела, вылетает 10 гранат, потому, что FM_CmdStart работает с частотой фпс сервера.
Нужно поставить ограничение по времени.
[pawn]
- // Переменная для ограничения атаки
- new Float:shot_time[33]
- // И ставим вот такой нехитрый кодец после блокировки кнопки:
- //блокировка кнопки
- buttons &= ~IN_ATTACK
- set_uc(uc_handle, UC_Buttons, buttons)
- // Регистрируем статическую переменную, и записываем в неё время
- // Чем меньше дёргаем функции тем меньше нагрузки, а здесь используется 2 раза
- static Float:gametime; gametime = get_gametime()
- // Если время записанное в переменную меньше или равно действительному, то выполнить
- if(shot_time[id] <= gametime)
- {
- // Здесь будет наша атака
- fire_grenade(id)
- // Теперь задаём переменной текущее время + время задержки
- // Есливы подумали а как же срабатывает условие при первом выстреле
- // когда ещё не записали время в переменную, всё просто, изначально все переменные имеют значение 0 (или false)
- shot_time[id] = gametime + 3.2 // время на задержку получаем из модели, количество кадров делим на фпс
- }
Ну и буквально последний штришок.
Взрыв, урон.
[pawn]
- // Задаём максимальный урон
- new const Float:damage = 100.0
- // Задаём радиус поражения
- new const Float:radius = 200.0
- public plugin_init()
- {
- // Отлавливаем касание объектов
- register_forward(FM_Touch, "grenade_touch", 1)
- }
- // В функцию передаются id того кто коснулся, и id того кого коснулись.Т.к. у нас граната касается, то она и идёт первая.
- public grenade_touch(grenade, ent)
- {
- // Проверяем что объект существует. Если нет, то выходим
- if(!pev_valid(grenade)) return FMRES_IGNORED
- // Получаем имя объекта, не факт, что это она, это может быть и игрок
- new gclass[33]; pev(grenade, pev_classname, gclass, 32)
- // А теперь сравниваем полученное имя с тем, что мы задали объекту, когда его создали, если совпало, идём дальше
- if(equal(gclass, "grenade_m79"))
- {
- // Получаем владельца, id игрока который выстрелил
- new owner = pev(grenade, pev_owner)
- // Получаем координаты гранаты
- new Float:origin[3]; pev(grenade, pev_origin, origin)
- // Создаём зрыв, само пламя
- engfunc(EngFunc_MessageBegin,MSG_PVS,SVC_TEMPENTITY,origin,0)
- write_byte(TE_EXPLOSION) // Тип
- engfunc(EngFunc_WriteCoord,origin[0]) // позиция X
- engfunc(EngFunc_WriteCoord,origin[1]) // позиция Y
- engfunc(EngFunc_WriteCoord,origin[2]) // позиция Z
- write_short(explode) // Индекс спрайта
- write_byte(30) // Масштаб
- write_byte(15) // Частота кадров
- write_byte(0) // Флаг
- message_end() // Конец
- // Получаем имя класса второго объекта, которого коснулась граната
- new classname[33]; pev(ent, pev_classname, classname, 32)
- //Иконка xm при убийстве
- set_pev(grenade, pev_classname, "weapon_xm1014")
- // Если имя player - граната попала в игрока
- // Наверное это лишнее, потому, что игрока потом ещё раз долбанёт )
- if(equal(classname, "player"))
- {
- // Проверяем, что он жив, не в одной команде с владельцем гранаты, и если это так, то наносим урон
- if(is_user_alive(ent) && get_user_team(ent) != get_user_team(owner)) ExecuteHamB(Ham_TakeDamage, ent, grenade, owner, damage, DMG_BULLET)
- }
- // Если нет, и граната попала в ящик, которые ломаются при взрыве, то сломать его
- else if(equal(classname, "func_breakable")) dllfunc(DLLFunc_Use, ent, owner)
- // Граната просто воткнулась в карту, создаём урон в радиусе
- // Регистрируем переменную, присваиваем ей -1 (неизвесно кого искать)
- new target = -1
- // Теперь делаем цикл с поиском объектов в радиусе, вводим координаты гранаты, радиус
- while((target = engfunc(EngFunc_FindEntityInSphere, target, origin, radius)))
- {
- // Если объект жив и не в одной команде с владельцем
- if(is_user_alive(target) && get_user_team(target) != get_user_team(owner))
- {
- // Получаем координаты жертвы
- new Float:v_origin[3]; pev(target, pev_origin,v_origin)
- // Расчёт наносимого урона
- // От радиуса отнимаем дистанцию и умножаем на урон делённый на радиус
- /*
- дистанция 100, урон 100, радиус 200 100/200 = 0.5 200 - 100 = 100 100 * 0.5 = 50 урон
- дистанция 50, урон 100, радиус 200 100/200 = 0.5 200 - 50 = 150 150 * 0.5 = 75 урон
- дистанция 30, урон 100, радиус 200 100/200 = 0.5 200 - 30 = 170 170 * 0.5 = 85 урон
- */
- new Float:damag = (radius - get_distance_f(origin, v_origin)) * (damage / radius)
- // Наносим урон
- ExecuteHamB(Ham_TakeDamage, target, grenade, owner, damag, DMG_BULLET)
- }
- }
- // Удаляем гранату
- engfunc(EngFunc_RemoveEntity, grenade)
- }
- // Возврат
- return FMRES_IGNORED
- }
Анимация
[pawn]
- // Зарегистрируем перечисления с названиями анимаций в том порядке как они идут в модели
- // так-то можно и без этого, но так нам же будет понятнее, вместо названий анимаций просто подставляются цифры ...
- enum
- {
- idle, // 0
- shoot, // 1
- reload, // 2
- draw // 3
- }
- // Этот сток куда-нибудь в конец плагина. Проигрывание анимации оружия
- stock play_weapon_anim(id, sequence)
- {
- set_pev(id, pev_weaponanim, sequence)
- message_begin(MSG_ONE, SVC_WEAPONANIM, {0, 0, 0}, id)
- write_byte(sequence)
- write_byte(pev(id, pev_body))
- message_end()
- }
- // А так будем воспроизводить, поставим это сразу после того как заменили модель оружия
- play_weapon_anim(id, draw)
- // Воспроизводим анимацию выстрела там где блокируем нажатие кнопки атаки
- play_weapon_anim(id, shoot)
И последнее, делаем перезарядку, и кончающиеся патроны...
...статья не дописана, допишу завтра...
Хоть тут и описан принцип, но о том как было бы лучше сделать, с удовольствием прочту...