#include <amxmodx>
#include <fakemeta>
#include <hamsandwich>
#include <reapi>
#include <xs>
enum RadiusDamage_Func {
/**
* На любом расстоянии урон будет максимальным.
*
* fFuncVar определяет множитель урона на любом расстоянии.
* По умолчанию = 1.0
*/
RadiusDamage_Static,
/**
* Урон будет линейно зависеть от расстояния.
* Т.е. на сколько дальше, на столько меньше урона.
*
* fFuncVar определяет множитель параметра функции.
* По умолчанию = 1.0
*/
RadiusDamage_Linear,
/**
* Расстояние возводится в степень.
* Ближе к центру урон падает медленнее.
*
* fFuncVar определяет показатель степени.
* По умолчанию = 2.0
*/
RadiusDamage_Sqr,
/**
* Берётся корень от расстояния.
* Ближе к центру урон падает быстрее.
*
* fFuncVar определяет показатель корня.
* По умолчанию = 2.0
*/
RadiusDamage_Sqrt,
/**
* Дальше от центра урон падает быстрее.
*/
RadiusDamage_Sin,
/**
* Ближе к середине радиуса урон падает медленнее.
* На самом деле, получается близко к линейному.
*/
RadiusDamage_Tan,
/**
* От центра сначала падает быстрее, потом замедляется.
* На середине радиуса совпадает с линейной.
*/
RadiusDamage_Arcsin,
}
enum RadiusDamage_InnerRadiusBehavior {
/**
* Расстояние считается от внутреннего радиуса, а не от центра.
*/
RadiusDamage_OffsetX,
/**
* Множитель урона вычисляется от фактического расстояния + внутреннего радиуса, но не больше общего радиуса.
*/
RadiusDamage_OffsetFx,
/**
* Если игрок вне внутреннего радиуса, множитель урона вычисляется от фактического расстояния.
*/
RadiusDamage_Overlay,
}
public plugin_init() {
register_clcmd("radiusdmg", "@Cmd_RadiusDmg");
}
@Cmd_RadiusDmg(const UserId) {
new Float:fvOrigin[3];
get_entvar(UserId, var_origin, fvOrigin);
new RadiusDamage_Func:iFunc = RadiusDamage_Linear;
if (read_argc() >= 2) {
iFunc = RadiusDamage_Func:read_argv_int(1);
}
RadiusDamage(
fvOrigin, UserId, UserId, 1000.0, 500.0,
.fInnerRadius=100.0, .iInnerRadiusBehavior=RadiusDamage_OffsetX,
.iFunc=iFunc, .fFuncVar=0.0,
.iIgnoreEntity=UserId,
.bCalcFromHitbox=true,
.bThroughWalls=true
);
}
/**
* Наносит урон игрокам в указанном радиусе
*
* @param fvOrigin Источник урона
* @param InflictorId Индекс сущности, нанёсшей урон
* @param AttackerId Индекс сущности, инициировавшей нанесение урона
* @param fBaseDamage Базовое значение урона (впритык в источнику)
* @param fRadius Радиус, в котором будет нанесён урон
* @param iClassIgnore Класс сущностей, которым урон не будет нанесён (см. константы CLASS_)
* @param bitsDamageType Тип наносимого урона (см. константы DMG_)
* @param iFunc Функция, для вычисления урона в зависимости от расстояния (см. RadiusDamage_Func)
* @param fFuncVar Дополнительный параметр функции
* @param fInnerRadius Значение внутреннего радиуса, в котором игрок получит максимальный урон
* @param iInnerRadiusBehavior Определяет механику работы внутреннего радиуса (см. RadiusDamage_InnerRadiusBehavior)
* @param iIgnoreEntity Индекс сущности, которой крон не будет нанесён
* @param bCalcFromHitbox Если true - будет считать расстояние от края хитбокса, иначе от центра
* @param bThroughWalls Должен ли наноситься урон через стены
*
* @noreturn
*/
RadiusDamage(
const Float:fvOrigin[3],
const InflictorId,
const AttackerId,
const Float:fBaseDamage,
const Float:fRadius,
const iClassIgnore = CLASS_NONE,
const bitsDamageType = DMG_GENERIC,
// Additional parameters
const RadiusDamage_Func:iFunc = RadiusDamage_Linear,
const Float:fFuncVar = 0.0,
const Float:fInnerRadius = 0.0,
const RadiusDamage_InnerRadiusBehavior:iInnerRadiusBehavior = RadiusDamage_OffsetX,
const iIgnoreEntity = 0,
const bool:bCalcFromHitbox = false,
const bool:bThroughWalls = false
) {
new EntId = -1;
new const Float:fInnerRadiusPercent = fInnerRadius / fRadius;
while ((EntId = engfunc(EngFunc_FindEntityInSphere, EntId, fvOrigin, fRadius)) != 0) {
if (EntId == iIgnoreEntity) {
continue;
}
if (get_entvar(EntId, var_takedamage) == DAMAGE_NO) {
continue;
}
if (iClassIgnore && ExecuteHamB(Ham_Classify, EntId) == iClassIgnore) {
continue;
}
if (FClassnameIs(EntId, "player") && !rg_is_player_can_takedamage(EntId, AttackerId)) {
continue;
}
new Float:fvEntOrigin[3];
RadiusDamage_GetEntCenter(EntId, fvEntOrigin);
new iTrace = create_tr2();
engfunc(EngFunc_TraceLine, fvOrigin, fvEntOrigin, DONT_IGNORE_MONSTERS, FM_NULLENT, iTrace);
new Float:fFraction;
get_tr2(iTrace, TR_flFraction, fFraction);
new iTracedEnt = get_tr2(iTrace, TR_pHit);
if (!bThroughWalls && iTracedEnt != EntId && fFraction != 1.0) {
continue;
}
if (bCalcFromHitbox) {
get_tr2(iTrace, TR_vecEndPos, fvEntOrigin);
}
new Float:fDistance = xs_vec_distance(fvOrigin, fvEntOrigin);
new Float:fDmgMult;
if (iInnerRadiusBehavior == RadiusDamage_Overlay && fDistance <= fInnerRadius) {
fDmgMult = 1.0;
} else {
new Float:fDistancePercent;
if (iInnerRadiusBehavior == RadiusDamage_OffsetX) {
fDistancePercent = 1.0 - floatclamp(((fDistance - fInnerRadius) / (fRadius - fInnerRadius)), 0.0, 1.0);
} else {
fDistancePercent = 1.0 - floatmin((fDistance / fRadius), 1.0);
}
fDmgMult = RadiusDamage_CalcDamage(iFunc, fDistancePercent, fFuncVar);
if (iInnerRadiusBehavior == RadiusDamage_OffsetFx) {
fDmgMult = floatmin(fDmgMult + fInnerRadiusPercent, 1.0);
}
}
if (fFraction == 1.0 || iTracedEnt != EntId) {
ExecuteHamB(Ham_TakeDamage, EntId, InflictorId, AttackerId, fBaseDamage * fDmgMult, bitsDamageType);
} else {
new Float:fvTraceDirection[3];
xs_vec_sub(fvEntOrigin, fvOrigin, fvTraceDirection);
xs_vec_normalize(fvTraceDirection, fvTraceDirection);
rg_multidmg_clear();
ExecuteHamB(Ham_TraceAttack, EntId, InflictorId, fBaseDamage * fDmgMult, fvTraceDirection, iTrace, bitsDamageType);
rg_multidmg_apply(InflictorId, AttackerId);
}
free_tr2(iTrace);
}
}
RadiusDamage_GetEntCenter(const EntId, Float:fvCenter[3]) {
if (FClassnameIs(EntId, "func_breakable")) {
new Float:fvAbsMin[3], Float:fvSize[3];
get_entvar(EntId, var_absmin, fvAbsMin);
get_entvar(EntId, var_size, fvSize);
xs_vec_div_scalar(fvSize, 2.0, fvSize);
xs_vec_add(fvAbsMin, fvSize, fvCenter);
} else {
get_entvar(EntId, var_origin, fvCenter);
}
}
Float:RadiusDamage_CalcDamage(const RadiusDamage_Func:iFunc, const Float:fX, const Float:fFuncVar = 0.0) {
new Float:fVar = fFuncVar;
if (fVar == 0.0) {
switch (iFunc) {
case RadiusDamage_Sqr, RadiusDamage_Sqrt:
fVar = 2.0;
case RadiusDamage_Linear, RadiusDamage_Static:
fVar = 1.0;
// Для остальных не используется
}
}
new Float:fRes;
switch (iFunc) {
case RadiusDamage_Static:
fRes = fVar;
case RadiusDamage_Linear:
fRes = fX * fVar;
case RadiusDamage_Sqr:
fRes = floatpower(fX, fVar);
case RadiusDamage_Sqrt:
fRes = floatpower(fX, 1 / fVar);
// Эти числа подобраны тупым перебором,
// чтобы значения функций влезали в диапазон [0.0; 1.0] при 0.0 < fX < 1.0
case RadiusDamage_Sin:
fRes = floatsin(fX * 1.57, radian);
case RadiusDamage_Tan:
fRes = floattan(fX * 0.7854, radian);
case RadiusDamage_Arcsin:
fRes = (floatasin((fX - 0.5) * 2, radian) + 1.5) / 3;
}
return floatclamp(fRes, 0.0, 1.0);
}