Загрузка данных
/*
* Corpse Gore 2.0
* Refactored from Corpse Gore by RedSMURF
*
* AMX Mod X / Counter-Strike 1.6
*
* Cvars:
* corpsegore_enable 1
* corpsegore_gib_knife 1
* corpsegore_gib_explosive 1
* corpsegore_break_velocity 12
* corpsegore_break_count 12
* corpsegore_break_life 50
* corpsegore_blood_color 247
* corpsegore_blood_scale 10
*
* Notes:
* - Preserves the original ClCorpse interception.
* - Uses a bounded collision box calculated from model bones.
* - Limits the wait for a clear corpse position.
* - Cleans up corpse entities and their tasks.
*/
#include <amxmodx>
#include <fakemeta>
#include <hamsandwich>
#define PLUGIN_NAME "Corpse Gore"
#define PLUGIN_VERSION "2.0"
#define PLUGIN_AUTHOR "RedSMURF / Refactor"
#define CORPSE_CLASS "corpse_gore"
#define CORPSE_KEY 981222
#define CORPSE_PENDING pev_iuser1
#define CORPSE_CREATED_AT pev_fuser1
#define CORPSE_BONES 36
#define CORPSE_BBOX_PAD 4.0
#define CORPSE_BBOX_MIN_X 20.0
#define CORPSE_BBOX_MIN_Y 12.0
#define CORPSE_BBOX_MAX_X 40.0
#define CORPSE_BBOX_MAX_Y 28.0
#define CORPSE_BBOX_HEIGHT 4.0
#define CORPSE_WAIT_INTERVAL 0.15
#define CORPSE_WAIT_TIMEOUT 2.0
#define CORPSE_DMG_EXPLOSIVE ((1 << 6) | (1 << 24))
#define CORPSE_BREAK_FLESH 0x04
#define MAX_CLIENTS 32
new g_pEnable;
new g_pGibKnife;
new g_pGibExplosive;
new g_pBreakVelocity;
new g_pBreakCount;
new g_pBreakLife;
new g_pBloodColor;
new g_pBloodScale;
new bool:g_bEnabled;
new bool:g_bGibKnife;
new bool:g_bGibExplosive;
new g_iBreakVelocity;
new g_iBreakCount;
new g_iBreakLife;
new g_iBloodColor;
new g_iBloodScale;
new g_iBloodSprite;
new g_iBloodSpraySprite;
new g_iGibsModel;
new g_iMaxPlayers;
new Float:g_flLastKnifeHit[MAX_CLIENTS + 1];
new const g_szKnifeImpactSound[] = "common/bodysplat.wav";
new const g_szKnifeWallSound[] = "weapons/knife_hitwall1.wav";
new const g_iBloodDecals[] = {204, 205};
// --------------------------------------------------------------------------
// Plugin lifecycle
// --------------------------------------------------------------------------
public plugin_precache()
{
g_iBloodSprite = precache_model("sprites/blood.spr");
g_iBloodSpraySprite = precache_model("sprites/bloodspray.spr");
g_iGibsModel = precache_model("models/hgibs.mdl");
precache_sound(g_szKnifeImpactSound);
}
public plugin_init()
{
register_plugin(PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR);
register_cvar(
"RedSMURF_CorpseGore",
PLUGIN_VERSION,
FCVAR_SERVER | FCVAR_SPONLY
);
g_pEnable = register_cvar("corpsegore_enable", "1");
g_pGibKnife = register_cvar("corpsegore_gib_knife", "1");
g_pGibExplosive = register_cvar("corpsegore_gib_explosive", "1");
g_pBreakVelocity = register_cvar("corpsegore_break_velocity", "12");
g_pBreakCount = register_cvar("corpsegore_break_count", "12");
g_pBreakLife = register_cvar("corpsegore_break_life", "50");
g_pBloodColor = register_cvar("corpsegore_blood_color", "247");
g_pBloodScale = register_cvar("corpsegore_blood_scale", "10");
cacheCvars();
hook_cvar_change(g_pEnable, "onCvarChanged");
hook_cvar_change(g_pGibKnife, "onCvarChanged");
hook_cvar_change(g_pGibExplosive, "onCvarChanged");
hook_cvar_change(g_pBreakVelocity, "onCvarChanged");
hook_cvar_change(g_pBreakCount, "onCvarChanged");
hook_cvar_change(g_pBreakLife, "onCvarChanged");
hook_cvar_change(g_pBloodColor, "onCvarChanged");
hook_cvar_change(g_pBloodScale, "onCvarChanged");
register_message(get_user_msgid("ClCorpse"), "onClCorpse");
register_forward(FM_EmitSound, "onEmitSound");
RegisterHam(Ham_TraceAttack, "info_target", "onTraceAttack");
RegisterHam(Ham_TakeDamage, "info_target", "onTakeDamage");
register_logevent("onRoundStart", 2, "1=Round_Start");
g_iMaxPlayers = get_maxplayers();
}
public plugin_end()
{
removeAllCorpses();
}
public onCvarChanged(pCvar, const szOldValue[], const szNewValue[])
{
cacheCvars();
}
stock cacheCvars()
{
g_bEnabled = get_pcvar_num(g_pEnable) != 0;
g_bGibKnife = get_pcvar_num(g_pGibKnife) != 0;
g_bGibExplosive = get_pcvar_num(g_pGibExplosive) != 0;
g_iBreakVelocity = clamp(get_pcvar_num(g_pBreakVelocity), 0, 255);
g_iBreakCount = clamp(get_pcvar_num(g_pBreakCount), 0, 255);
g_iBreakLife = clamp(get_pcvar_num(g_pBreakLife), 0, 255);
g_iBloodColor = clamp(get_pcvar_num(g_pBloodColor), 0, 255);
g_iBloodScale = clamp(get_pcvar_num(g_pBloodScale), 0, 255);
}
// --------------------------------------------------------------------------
// Corpse creation
// --------------------------------------------------------------------------
public onClCorpse()
{
if (!g_bEnabled)
return PLUGIN_CONTINUE;
new szModel[32];
get_msg_arg_string(1, szModel, charsmax(szModel));
new Float:flOrigin[3];
new Float:flAngles[3];
// ClCorpse origin is transmitted in 1/128-unit coordinates.
for (new i; i < 3; i++)
{
flOrigin[i] = float(get_msg_arg_int(i + 2)) / 128.0;
flAngles[i] = get_msg_arg_float(i + 5);
}
new iSequence = get_msg_arg_int(9);
new iBody = get_msg_arg_int(10);
createCorpse(szModel, flOrigin, flAngles, iSequence, iBody);
// Suppress the original client corpse.
return PLUGIN_HANDLED;
}
stock createCorpse(
const szModel[],
const Float:flOrigin[3],
const Float:flAngles[3],
iSequence,
iBody
)
{
new iEnt = create_entity("info_target");
if (!pev_valid(iEnt))
return 0;
new szModelPath[96];
formatex(
szModelPath,
charsmax(szModelPath),
"models/player/%s/%s.mdl",
szModel,
szModel
);
set_pev(iEnt, pev_classname, CORPSE_CLASS);
set_pev(iEnt, pev_impulse, CORPSE_KEY);
// Initialize entity before setting model and collision.
dllfunc(DLLFunc_Spawn, iEnt);
engfunc(EngFunc_SetModel, iEnt, szModelPath);
engfunc(EngFunc_SetOrigin, iEnt, flOrigin);
set_pev(iEnt, pev_angles, flAngles);
set_pev(iEnt, pev_body, iBody);
set_pev(iEnt, pev_sequence, iSequence);
set_pev(iEnt, pev_frame, 255.0);
set_pev(iEnt, pev_framerate, 0.0);
set_pev(iEnt, pev_animtime, get_gametime());
set_pev(iEnt, pev_movetype, MOVETYPE_NONE);
set_pev(iEnt, pev_solid, SOLID_NOT);
set_pev(iEnt, pev_takedamage, DAMAGE_YES);
set_pev(iEnt, CORPSE_PENDING, 1);
set_pev(iEnt, CORPSE_CREATED_AT, get_gametime());
corpseFitBox(iEnt);
if (corpseIsBlocked(iEnt))
{
set_task(
CORPSE_WAIT_INTERVAL,
"taskActivateCorpse",
iEnt,
"",
0,
"b"
);
}
else
{
activateCorpse(iEnt);
}
return iEnt;
}
// --------------------------------------------------------------------------
// Collision box
// --------------------------------------------------------------------------
stock corpseFitBox(iEnt)
{
if (!isCorpse(iEnt))
return;
new Float:flOrigin[3];
new Float:flAngles[3];
pev(iEnt, pev_origin, flOrigin);
pev(iEnt, pev_angles, flAngles);
new Float:flMins[3];
new Float:flMaxs[3];
flMins[0] = 9999.0;
flMins[1] = 9999.0;
flMaxs[0] = -9999.0;
flMaxs[1] = -9999.0;
new iValidBones;
// GoldSrc yaw is around the Z axis.
new Float:flYaw = flAngles[1];
new Float:flCos = floatcos(flYaw, degrees);
new Float:flSin = floatsin(flYaw, degrees);
for (new iBone; iBone < CORPSE_BONES; iBone++)
{
new Float:flBone[3];
new Float:flBoneAngles[3];
engfunc(
EngFunc_GetBonePosition,
iEnt,
iBone,
flBone,
flBoneAngles
);
new Float:flDX = flBone[0] - flOrigin[0];
new Float:flDY = flBone[1] - flOrigin[1];
// Transform world-space bone offsets into yaw-local space.
new Float:flLocalX = flDX * flCos + flDY * flSin;
new Float:flLocalY = -flDX * flSin + flDY * flCos;
// Reject invalid or extreme bone positions.
if (floatabs(flLocalX) > 96.0 || floatabs(flLocalY) > 96.0)
continue;
flMins[0] = floatmin(flMins[0], flLocalX);
flMins[1] = floatmin(flMins[1], flLocalY);
flMaxs[0] = floatmax(flMaxs[0], flLocalX);
flMaxs[1] = floatmax(flMaxs[1], flLocalY);
iValidBones++;
}
// Fallback for models with unusable bone information.
if (!iValidBones)
{
flMins[0] = -32.0;
flMins[1] = -16.0;
flMaxs[0] = 32.0;
flMaxs[1] = 16.0;
}
else
{
flMins[0] -= CORPSE_BBOX_PAD;
flMins[1] -= CORPSE_BBOX_PAD;
flMaxs[0] += CORPSE_BBOX_PAD;
flMaxs[1] += CORPSE_BBOX_PAD;
// Clamp the extents so one bad bone cannot produce
// a huge collision box.
flMins[0] = floatmax(flMins[0], -CORPSE_BBOX_MAX_X);
flMins[1] = floatmax(flMins[1], -CORPSE_BBOX_MAX_Y);
flMaxs[0] = floatmin(flMaxs[0], CORPSE_BBOX_MAX_X);
flMaxs[1] = floatmin(flMaxs[1], CORPSE_BBOX_MAX_Y);
// Ensure a minimum width and depth.
if (flMaxs[0] - flMins[0] < CORPSE_BBOX_MIN_X)
{
flMins[0] = -CORPSE_BBOX_MIN_X * 0.5;
flMaxs[0] = CORPSE_BBOX_MIN_X * 0.5;
}
if (flMaxs[1] - flMins[1] < CORPSE_BBOX_MIN_Y)
{
flMins[1] = -CORPSE_BBOX_MIN_Y * 0.5;
flMaxs[1] = CORPSE_BBOX_MIN_Y * 0.5;
}
}
// Trace to the ground to position the thin collision slab.
new Float:flTraceStart[3];
new Float:flTraceEnd[3];
flTraceStart[0] = flOrigin[0];
flTraceStart[1] = flOrigin[1];
flTraceStart[2] = flOrigin[2] + 16.0;
flTraceEnd[0] = flOrigin[0];
flTraceEnd[1] = flOrigin[1];
flTraceEnd[2] = flOrigin[2] - 64.0;
engfunc(
EngFunc_TraceLine,
flTraceStart,
flTraceEnd,
IGNORE_MONSTERS,
iEnt,
0
);
new Float:flFraction;
get_tr2(0, TR_flFraction, flFraction);
new Float:flFloorOffset;
if (flFraction < 1.0)
{
new Float:flHit[3];
get_tr2(0, TR_vecEndPos, flHit);
flFloorOffset = flHit[2] - flOrigin[2];
}
else
{
// No floor found: keep the box close to the entity origin.
flFloorOffset = -8.0;
}
flMins[2] = flFloorOffset;
flMaxs[2] = flFloorOffset + CORPSE_BBOX_HEIGHT;
engfunc(EngFunc_SetSize, iEnt, flMins, flMaxs);
engfunc(EngFunc_SetOrigin, iEnt, flOrigin);
}
// --------------------------------------------------------------------------
// Deferred activation
// --------------------------------------------------------------------------
public taskActivateCorpse(iEnt)
{
if (!isCorpse(iEnt) || !pev(iEnt, CORPSE_PENDING))
{
remove_task(iEnt);
return;
}
new Float:flCreatedAt;
pev(iEnt, CORPSE_CREATED_AT, flCreatedAt);
// Do not wait indefinitely if a player occupies the corpse.
if (get_gametime() - flCreatedAt >= CORPSE_WAIT_TIMEOUT)
{
activateCorpse(iEnt);
return;
}
if (!corpseIsBlocked(iEnt))
activateCorpse(iEnt);
}
stock activateCorpse(iEnt)
{
if (!isCorpse(iEnt))
return;
new Float:flOrigin[3];
pev(iEnt, pev_origin, flOrigin);
set_pev(iEnt, CORPSE_PENDING, 0);
set_pev(iEnt, pev_solid, SOLID_BBOX);
engfunc(EngFunc_SetOrigin, iEnt, flOrigin);
remove_task(iEnt);
}
stock bool:corpseIsBlocked(iEnt)
{
if (!isCorpse(iEnt))
return false;
new Float:flCorpseMin[3];
new Float:flCorpseMax[3];
pev(iEnt, pev_absmin, flCorpseMin);
pev(iEnt, pev_absmax, flCorpseMax);
for (new id = 1; id <= g_iMaxPlayers; id++)
{
if (!is_user_alive(id))
continue;
new Float:flPlayerMin[3];
new Float:flPlayerMax[3];
pev(id, pev_absmin, flPlayerMin);
pev(id, pev_absmax, flPlayerMax);
new bool:bOverlap = true;
for (new axis; axis < 3; axis++)
{
if (
flPlayerMin[axis] >= flCorpseMax[axis]
|| flPlayerMax[axis] <= flCorpseMin[axis]
)
{
bOverlap = false;
break;
}
}
if (bOverlap)
return true;
}
return false;
}
// --------------------------------------------------------------------------
// Damage and knife handling
// --------------------------------------------------------------------------
public onTraceAttack(iEnt, iAttacker, Float:flDamage, Float:flDirection[3], iTrace, iDamageBits)
{
if (!g_bEnabled || !isCorpse(iEnt) || pev(iEnt, CORPSE_PENDING))
return HAM_IGNORED;
if (
g_bGibKnife
&& is_user_alive(iAttacker)
&& get_user_weapon(iAttacker) == CSW_KNIFE
)
{
g_flLastKnifeHit[iAttacker] = get_gametime();
gibCorpse(iEnt);
// Prevent normal damage processing on the removed corpse.
return HAM_SUPERCEDE;
}
new Float:flHitPosition[3];
get_tr2(iTrace, TR_vecEndPos, flHitPosition);
emitBlood(flHitPosition);
// Corpses are effect entities; suppress ordinary damage.
return HAM_SUPERCEDE;
}
public onTakeDamage(iEnt, iInflictor, iAttacker, Float:flDamage, iDamageBits)
{
if (!g_bEnabled || !isCorpse(iEnt))
return HAM_IGNORED;
if (!(iDamageBits & CORPSE_DMG_EXPLOSIVE))
return HAM_IGNORED;
if (g_bGibExplosive)
gibCorpse(iEnt);
return HAM_SUPERCEDE;
}
public onEmitSound(id, iChannel, const szSample[], Float:flVolume, Float:flAttn, iFlags, iPitch)
{
if (
!g_bEnabled
|| id < 1
|| id > g_iMaxPlayers
|| get_gametime() - g_flLastKnifeHit[id] > 0.1
|| !equal(szSample, g_szKnifeWallSound)
)
{
return FMRES_IGNORED;
}
engfunc(
EngFunc_EmitSound,
id,
iChannel,
g_szKnifeImpactSound,
flVolume,
flAttn,
iFlags,
iPitch
);
return FMRES_SUPERCEDE;
}
// --------------------------------------------------------------------------
// Gore effects
// --------------------------------------------------------------------------
stock gibCorpse(iEnt)
{
if (!isCorpse(iEnt))
return;
new Float:flMin[3];
new Float:flMax[3];
pev(iEnt, pev_absmin, flMin);
pev(iEnt, pev_absmax, flMax);
new Float:flCenter[3];
new Float:flSize[3];
flCenter[0] = (flMin[0] + flMax[0]) * 0.5;
flCenter[1] = (flMin[1] + flMax[1]) * 0.5;
flCenter[2] = flMin[2] + 8.0;
flSize[0] = (flMax[0] - flMin[0]) * 0.25;
flSize[1] = (flMax[1] - flMin[1]) * 0.25;
flSize[2] = 8.0;
message_begin_f(MSG_PVS, SVC_TEMPENTITY, flCenter);
write_byte(TE_BREAKMODEL);
write_coord_f(flCenter[0]);
write_coord_f(flCenter[1]);
write_coord_f(flCenter[2]);
write_coord_f(flSize[0]);
write_coord_f(flSize[1]);
write_coord_f(flSize[2]);
write_coord_f(0.0);
write_coord_f(0.0);
write_coord_f(0.0);
write_byte(randomCvarRange(g_iBreakVelocity, 2, 0, 255));
write_short(g_iGibsModel);
write_byte(randomCvarRange(g_iBreakCount, 3, 0, 255));
write_byte(randomCvarRange(g_iBreakLife, 15, 0, 255));
write_byte(CORPSE_BREAK_FLESH);
message_end();
emitBlood(flCenter);
emitBloodDecal(flCenter);
removeCorpse(iEnt);
}
stock emitBlood(const Float:flPosition[3])
{
message_begin_f(MSG_PVS, SVC_TEMPENTITY, flPosition);
write_byte(TE_BLOODSPRITE);
write_coord_f(flPosition[0]);
write_coord_f(flPosition[1]);
write_coord_f(flPosition[2]);
write_short(g_iBloodSpraySprite);
write_short(g_iBloodSprite);
write_byte(g_iBloodColor);
write_byte(randomCvarRange(g_iBloodScale, 3, 0, 255));
message_end();
}
stock emitBloodDecal(const Float:flPosition[3])
{
new Float:flStart[3];
new Float:flEnd[3];
flStart[0] = flPosition[0];
flStart[1] = flPosition[1];
flStart[2] = flPosition[2];
flEnd[0] = flStart[0];
flEnd[1] = flStart[1];
flEnd[2] = flStart[2] - 24.0;
engfunc(
EngFunc_TraceLine,
flStart,
flEnd,
IGNORE_MONSTERS,
0,
0
);
new Float:flFraction;
get_tr2(0, TR_flFraction, flFraction);
if (flFraction >= 1.0)
return;
get_tr2(0, TR_vecEndPos, flEnd);
message_begin_f(MSG_BROADCAST, SVC_TEMPENTITY, flEnd);
write_byte(TE_WORLDDECAL);
write_coord_f(flEnd[0]);
write_coord_f(flEnd[1]);
write_coord_f(flEnd[2]);
write_byte(g_iBloodDecals[random_num(0, sizeof(g_iBloodDecals) - 1)]);
message_end();
}
// --------------------------------------------------------------------------
// Entity utilities
// --------------------------------------------------------------------------
stock bool:isCorpse(iEnt)
{
return (
pev_valid(iEnt)
&& pev(iEnt, pev_impulse) == CORPSE_KEY
);
}
stock removeCorpse(iEnt)
{
if (!pev_valid(iEnt))
return;
remove_task(iEnt);
engfunc(EngFunc_RemoveEntity, iEnt);
}
stock removeAllCorpses()
{
new iEnt = -1;
while (
(iEnt = engfunc(
EngFunc_FindEntityByString,
iEnt,
"classname",
CORPSE_CLASS
))
)
{
removeCorpse(iEnt);
}
}
stock randomCvarRange(iValue, iSpread, iMin, iMax)
{
new iLow = max(iMin, iValue - iSpread);
new iHigh = min(iMax, iValue + iSpread);
return random_num(iLow, iHigh);
}
// --------------------------------------------------------------------------
// Round cleanup
// --------------------------------------------------------------------------
public onRoundStart()
{
removeAllCorpses();
}