532 lines
12 KiB
C++
532 lines
12 KiB
C++
// vim: set ts=4 sw=4 tw=99 noet:
|
|
//
|
|
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
|
|
// Copyright (C) The AMX Mod X Development Team.
|
|
//
|
|
// This software is licensed under the GNU General Public License, version 3 or higher.
|
|
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
|
|
// https://alliedmods.net/amxmodx-license
|
|
|
|
//
|
|
// Natural Selection Module
|
|
//
|
|
|
|
#include <string.h>
|
|
|
|
#include "amxxmodule.h"
|
|
|
|
#include "cbase.h" // TakeDamage
|
|
|
|
#include "ns.h"
|
|
|
|
#include "utilfunctions.h"
|
|
#include "NEW_Util.h"
|
|
|
|
#include "GameManager.h"
|
|
#include "SpawnManager.h"
|
|
#include "LocationManager.h"
|
|
#include "TitleManager.h"
|
|
|
|
#include "CPlayer.h"
|
|
|
|
|
|
edict_t* avhgameplay=NULL;
|
|
|
|
// drop-in replacement for user_kill
|
|
static cell AMX_NATIVE_CALL ns_user_kill(AMX *amx, cell *params)
|
|
{
|
|
|
|
CreatePlayerPointer(amx,params[1]);
|
|
|
|
// 2 is commander, never slay commander
|
|
if (player->GetPev()->iuser3 == 2)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (MF_IsPlayerIngame(params[1]) && MF_IsPlayerAlive(params[1]))
|
|
{
|
|
REAL bef = player->GetPev()->frags;
|
|
|
|
edict_t *pEntity = CREATE_NAMED_ENTITY(MAKE_STRING("trigger_hurt"));
|
|
|
|
if (pEntity)
|
|
{
|
|
KeyValueData kvd;
|
|
|
|
kvd.szClassName="trigger_hurt";
|
|
kvd.szKeyName="classname";
|
|
kvd.szValue="trigger_hurt";
|
|
kvd.fHandled=0;
|
|
MDLL_KeyValue(pEntity,&kvd);
|
|
|
|
kvd.szClassName="trigger_hurt";
|
|
kvd.szKeyName="dmg";
|
|
kvd.szValue="50000.0";
|
|
kvd.fHandled=0;
|
|
MDLL_KeyValue(pEntity,&kvd);
|
|
|
|
kvd.szClassName="trigger_hurt";
|
|
kvd.szKeyName="damagetype";
|
|
kvd.szValue="1";
|
|
kvd.fHandled=0;
|
|
MDLL_KeyValue(pEntity,&kvd);
|
|
|
|
kvd.szClassName="trigger_hurt";
|
|
kvd.szKeyName="origin";
|
|
kvd.szValue="8192 8192 8192";
|
|
kvd.fHandled=0;
|
|
MDLL_KeyValue(pEntity,&kvd);
|
|
|
|
MDLL_Spawn(pEntity);
|
|
|
|
pEntity->v.classname=MAKE_STRING("slay");
|
|
|
|
MDLL_Touch(pEntity,player->GetEdict());
|
|
|
|
REMOVE_ENTITY(pEntity);
|
|
}
|
|
|
|
// If the optional parameter is 1, restore the frag count
|
|
if (params[2])
|
|
{
|
|
player->GetPev()->frags = bef;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
// drop-in replacement for user_slap
|
|
#define ANGLEVECTORS (*g_engfuncs.pfnAngleVectors)
|
|
static cell AMX_NATIVE_CALL ns_user_slap(AMX *amx, cell *params) /* 2 param */
|
|
{
|
|
|
|
CreatePlayerPointer(amx,params[1]);
|
|
|
|
int power = abs((int)params[2]);
|
|
|
|
// Check if commander, if player is comm then stop
|
|
if (player->GetPev()->iuser3 == 2)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (MF_IsPlayerIngame(player->index()) && MF_IsPlayerAlive(player->index()))
|
|
{
|
|
if (player->GetPev()->health <= power)
|
|
{
|
|
float bef = player->GetPev()->frags;
|
|
/*MDLL_ClientKill(pPlayer->pEdict);*/
|
|
edict_t *pEntity = CREATE_NAMED_ENTITY(MAKE_STRING("trigger_hurt"));
|
|
if (pEntity)
|
|
{
|
|
KeyValueData kvd;
|
|
|
|
kvd.szClassName="trigger_hurt";
|
|
kvd.szKeyName="classname";
|
|
kvd.szValue="trigger_hurt";
|
|
kvd.fHandled=0;
|
|
MDLL_KeyValue(pEntity,&kvd);
|
|
|
|
kvd.szClassName="trigger_hurt";
|
|
kvd.szKeyName="dmg";
|
|
kvd.szValue="20000.0";
|
|
kvd.fHandled=0;
|
|
MDLL_KeyValue(pEntity,&kvd);
|
|
|
|
kvd.szClassName="trigger_hurt";
|
|
kvd.szKeyName="damagetype";
|
|
kvd.szValue="1";
|
|
kvd.fHandled=0;
|
|
MDLL_KeyValue(pEntity,&kvd);
|
|
|
|
kvd.szClassName="trigger_hurt";
|
|
kvd.szKeyName="origin";
|
|
kvd.szValue="8192 8192 8192";
|
|
kvd.fHandled=0;
|
|
MDLL_KeyValue(pEntity,&kvd);
|
|
|
|
MDLL_Spawn(pEntity);
|
|
|
|
pEntity->v.classname=MAKE_STRING("slap");
|
|
|
|
MDLL_Touch(pEntity,player->GetEdict());
|
|
|
|
REMOVE_ENTITY(pEntity);
|
|
}
|
|
|
|
player->GetPev()->frags = bef;
|
|
}
|
|
else
|
|
{
|
|
int numparam = *params/sizeof(cell);
|
|
if (numparam<3 || params[3])
|
|
{
|
|
player->GetPev()->velocity.x += RANDOM_LONG(-600,600);
|
|
player->GetPev()->velocity.y += RANDOM_LONG(-180,180);
|
|
player->GetPev()->velocity.z += RANDOM_LONG(100,200);
|
|
}
|
|
else
|
|
{
|
|
vec3_t v_forward, v_right;
|
|
vec3_t vang = player->GetPev()->angles;
|
|
float fang[3];
|
|
fang[0] = vang.x;
|
|
fang[1] = vang.y;
|
|
fang[2] = vang.z;
|
|
ANGLEVECTORS( fang, v_forward, v_right, NULL );
|
|
player->GetPev()->velocity = player->GetPev()->velocity + v_forward * 220 + Vector(0,0,200);
|
|
}
|
|
player->GetPev()->punchangle.x = RANDOM_LONG(-10,10);
|
|
player->GetPev()->punchangle.y = RANDOM_LONG(-10,10);
|
|
|
|
player->GetPev()->health -= power;
|
|
|
|
int armor = (int)player->GetPev()->armorvalue;
|
|
armor -= power;
|
|
|
|
if (armor < 0)
|
|
{
|
|
armor = 0;
|
|
}
|
|
|
|
player->GetPev()->armorvalue = armor;
|
|
|
|
player->GetPev()->dmg_inflictor = player->GetEdict();
|
|
|
|
static const char *bit_sound[3] = {
|
|
"weapons/cbar_hitbod1.wav",
|
|
"weapons/cbar_hitbod2.wav",
|
|
"weapons/cbar_hitbod3.wav"
|
|
};
|
|
|
|
EMIT_SOUND_DYN2(player->GetEdict(), CHAN_VOICE, bit_sound[RANDOM_LONG(0,2)], 1.0, ATTN_NORM, 0, PITCH_NORM);
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
// native ns_get_locationname(Float:x, Float:y, name[], len, lookup=0);
|
|
static cell AMX_NATIVE_CALL ns_get_locationname(AMX *amx, cell *params)
|
|
{
|
|
vec3_t location;
|
|
|
|
|
|
location.x=amx_ctof2(params[1]);
|
|
location.y=amx_ctof2(params[2]);
|
|
|
|
if ((params[0] / sizeof(cell)) >= 5)
|
|
{
|
|
return MF_SetAmxString(amx,params[3],LocationMan.Lookup(location,params[5]),params[4]);
|
|
}
|
|
else
|
|
{
|
|
return MF_SetAmxString(amx,params[3],LocationMan.Lookup(location,0),params[4]);
|
|
}
|
|
}
|
|
// ns_lookup_title(const Key[], Output[], Size)
|
|
static cell AMX_NATIVE_CALL ns_lookup_title(AMX *amx, cell *params)
|
|
{
|
|
// FIX: some keys have upper case characters; to fix i store all keys as lower case
|
|
ke::AString Input(UTIL_ToLowerCase(MF_GetAmxString(amx,params[1],0,NULL)));
|
|
|
|
const char *Output=TitleMan.Lookup(Input);
|
|
|
|
if (Output==NULL) // not found
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return MF_SetAmxString(amx,params[2],Output,params[3]);
|
|
};
|
|
// ns_round_in_progress()
|
|
static cell AMX_NATIVE_CALL ns_round_in_progress(AMX *amx, cell *params)
|
|
{
|
|
return GameMan.RoundInProgress();
|
|
}
|
|
// ns_get_spawn(team,number=0,Float:ret[3])
|
|
static cell AMX_NATIVE_CALL ns_get_spawn(AMX *amx, cell *params)
|
|
{
|
|
return SpawnMan.Lookup(amx,params);
|
|
}
|
|
// ns_get_mask(id,MASK_*)
|
|
static cell AMX_NATIVE_CALL ns_get_mask(AMX *amx, cell *params)
|
|
{
|
|
CreateEdict(amx,params[1],-1);
|
|
|
|
if (Entity->v.iuser4 & static_cast<int>(params[2]))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
// ns_set_mask(id,MASK_*,1 or 0)
|
|
static cell AMX_NATIVE_CALL ns_set_mask(AMX *amx, cell *params)
|
|
{
|
|
CreateEdict(amx,params[1],-1);
|
|
|
|
if (static_cast<int>(params[3]) > 0)
|
|
{
|
|
if (Entity->v.iuser4 & static_cast<int>(params[2]))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
Entity->v.iuser4 |= static_cast<int>(params[2]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (Entity->v.iuser4 & static_cast<int>(params[2]))
|
|
{
|
|
Entity->v.iuser4 &= ~static_cast<int>(params[2]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
// ns_popup(id (0 for all),"text",OnlyShowWithCLHelpOn=0)
|
|
static cell AMX_NATIVE_CALL ns_popup(AMX *amx, cell *params)
|
|
{
|
|
GameMan.UpdateHudText2();
|
|
|
|
if (params[1])
|
|
{
|
|
CreatePlayerPointer(amx, params[1]);
|
|
|
|
if (!player->IsConnected())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
MESSAGE_BEGIN(MSG_ONE,GameMan.GetHudText2(),NULL,player->GetEdict());
|
|
}
|
|
else
|
|
{
|
|
MESSAGE_BEGIN(MSG_ALL,GameMan.GetHudText2());
|
|
}
|
|
|
|
char msg[190];
|
|
strncpy(&msg[0],MF_GetAmxString(amx,params[2],0,NULL),188);
|
|
|
|
WRITE_STRING(msg);
|
|
WRITE_BYTE(params[3]);
|
|
|
|
MESSAGE_END();
|
|
|
|
return 1;
|
|
}
|
|
// ns_is_combat()
|
|
static cell AMX_NATIVE_CALL ns_is_combat(AMX *amx, cell *params)
|
|
{
|
|
return GameMan.IsCombat();
|
|
}
|
|
/**
|
|
* Pretty much a direct port of CheezyPeteza's unstick routine
|
|
* from the old unstuck.amxx plugin. This is included here
|
|
* to remove the engine requirement.
|
|
* -
|
|
* Return values:
|
|
* 1 success
|
|
* 0 no spot to move to
|
|
* -1 invalid state (stunned/webbed)
|
|
* -2 invalid class (commander/gorge)
|
|
* -3 player is dead or a spectator
|
|
* -4 player is invalid (unknown)
|
|
* -5 player is invalid (disconnected)
|
|
*/
|
|
|
|
inline int GetPlayerHullSize(CPlayer *Player, int PlayerClass)
|
|
{
|
|
switch (PlayerClass)
|
|
{
|
|
case CLASS_SKULK:
|
|
case CLASS_GORGE:
|
|
case CLASS_LERK:
|
|
return static_cast<int>(head_hull);
|
|
|
|
case CLASS_FADE:
|
|
case CLASS_JETPACK:
|
|
case CLASS_HEAVY:
|
|
case CLASS_MARINE:
|
|
if (Player->GetPev()->button & IN_DUCK || Player->GetPev()->flags & FL_DUCKING)
|
|
{
|
|
return static_cast<int>(head_hull);
|
|
}
|
|
|
|
return static_cast<int>(human_hull);
|
|
|
|
case CLASS_ONOS:
|
|
if (Player->GetPev()->button & IN_DUCK || Player->GetPev()->flags & FL_DUCKING)
|
|
{
|
|
return static_cast<int>(human_hull);
|
|
}
|
|
|
|
return static_cast<int>(large_hull);
|
|
|
|
default:
|
|
return -1;
|
|
|
|
}
|
|
return -1;
|
|
}
|
|
static cell AMX_NATIVE_CALL ns_unstick_player(AMX *amx, cell *params)
|
|
{
|
|
CreatePlayerPointer(amx,params[1]);
|
|
|
|
if (!player->IsConnected())
|
|
{
|
|
return -5;
|
|
}
|
|
|
|
if (player->GetPev()->iuser4 & (MASK_ENSNARED | MASK_PLAYER_STUNNED))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
int PlayerClass=player->GetClass();
|
|
if (PlayerClass == CLASS_GESTATE || PlayerClass == CLASS_COMMANDER)
|
|
{
|
|
return -2;
|
|
}
|
|
|
|
if (PlayerClass == CLASS_DEAD || PlayerClass == CLASS_NOTEAM || PlayerClass == CLASS_UNKNOWN)
|
|
{
|
|
return -3;
|
|
}
|
|
|
|
int HullSize=GetPlayerHullSize(player, PlayerClass);
|
|
|
|
|
|
if (HullSize==-1)
|
|
{
|
|
return -4;
|
|
}
|
|
|
|
Vector OriginalOrigin=player->GetPev()->origin;
|
|
|
|
Vector NewOrigin;
|
|
int Distance=params[2];
|
|
int Attempts;
|
|
TraceResult Result;
|
|
|
|
while (Distance < 1000)
|
|
{
|
|
Attempts=params[3];
|
|
|
|
while (Attempts--)
|
|
{
|
|
NewOrigin.x = RANDOM_FLOAT(OriginalOrigin.x - Distance,OriginalOrigin.x + Distance);
|
|
NewOrigin.y = RANDOM_FLOAT(OriginalOrigin.y - Distance,OriginalOrigin.y + Distance);
|
|
NewOrigin.z = RANDOM_FLOAT(OriginalOrigin.z - Distance,OriginalOrigin.z + Distance);
|
|
|
|
// (const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr);
|
|
TRACE_HULL(NewOrigin, NewOrigin, 0, HullSize, player->GetEdict(), &Result);
|
|
|
|
if (Result.fInOpen && !Result.fAllSolid && !Result.fStartSolid)
|
|
{
|
|
SET_ORIGIN(player->GetEdict(),NewOrigin);
|
|
return 1;
|
|
}
|
|
}
|
|
Distance += params[2];
|
|
}
|
|
|
|
|
|
return 0; // Couldn't be found
|
|
}
|
|
// Type: 131072 = DoT
|
|
// ns_takedamage
|
|
static cell AMX_NATIVE_CALL ns_takedamage(AMX *amx, cell *params)
|
|
{
|
|
// NASTY
|
|
// Reinterprets pvPrivateData as CBaseEntity, then calls TakeDamage with the entvar of Inflictor, and Attacker, with the float value and damage type
|
|
// The NS offset of TakeDamage hasn't changed from the HLSDK fortunately, so no offset digging is necessary
|
|
return (reinterpret_cast<CBaseEntity *>(INDEXENT_NEW(params[1])->pvPrivateData))->TakeDamage(&(INDEXENT_NEW(params[2])->v),&(INDEXENT_NEW(params[3])->v),amx_ctof2(params[4]),static_cast<int>(params[5]));
|
|
}
|
|
|
|
static cell AMX_NATIVE_CALL ns_get_gameplay(AMX* amx, cell* params)
|
|
{
|
|
if (avhgameplay == NULL)
|
|
{
|
|
avhgameplay = FIND_ENTITY_BY_CLASSNAME(NULL, "avhgameplay");
|
|
}
|
|
if (avhgameplay == NULL ||
|
|
avhgameplay->pvPrivateData == NULL) // Still null? Get out of here
|
|
{
|
|
return NSGame_CantTell;
|
|
}
|
|
|
|
int ATeam /* i pity da foo */ = *reinterpret_cast<int*>(reinterpret_cast<char*>(avhgameplay->pvPrivateData) + MAKE_OFFSET(GAMEPLAY_TEAMA));
|
|
int BTeam = *reinterpret_cast<int*>(reinterpret_cast<char*>(avhgameplay->pvPrivateData) + MAKE_OFFSET(GAMEPLAY_TEAMB));
|
|
|
|
if (ATeam == 2 && // alien
|
|
BTeam == 2) // alien
|
|
{
|
|
return NSGame_AlienVAlien;
|
|
}
|
|
if (ATeam == 1 && // marine
|
|
BTeam == 1) // marine
|
|
{
|
|
return NSGame_MarineVMarine;
|
|
}
|
|
if (ATeam == 1 && // marine
|
|
BTeam == 2) // alien
|
|
{
|
|
return NSGame_MarineVAlien;
|
|
}
|
|
return NSGame_Unknown;
|
|
|
|
}
|
|
|
|
#ifdef DEVELOPER_BUILD
|
|
static cell AMX_NATIVE_CALL refmem(AMX *amx, cell *params)
|
|
{
|
|
return *(reinterpret_cast<int *>(params[1]));
|
|
};
|
|
static cell AMX_NATIVE_CALL setmem(AMX *amx, cell *params)
|
|
{
|
|
int *ptr=reinterpret_cast<int *>(params[1]);
|
|
|
|
*ptr=params[2];
|
|
|
|
return 1;
|
|
};
|
|
#endif
|
|
|
|
AMX_NATIVE_INFO general_natives[] = {
|
|
|
|
{ "user_kill", ns_user_kill }, // replacement natives
|
|
{ "user_slap", ns_user_slap }, // since ClientKill is changed in NS
|
|
|
|
{ "ns_get_locationname", ns_get_locationname },
|
|
{ "ns_lookup_title", ns_lookup_title },
|
|
{ "ns_round_in_progress", ns_round_in_progress },
|
|
{ "ns_get_spawn", ns_get_spawn },
|
|
{ "ns_get_mask", ns_get_mask },
|
|
{ "ns_set_mask", ns_set_mask },
|
|
{ "ns_is_combat", ns_is_combat },
|
|
{ "ns_unstick_player", ns_unstick_player },
|
|
|
|
{ "ns_popup", ns_popup },
|
|
|
|
{ "ns_takedamage", ns_takedamage},
|
|
|
|
{ "ns_get_gameplay", ns_get_gameplay },
|
|
|
|
#ifdef DEVELOPER_BUILD
|
|
{ "refmem", refmem },
|
|
{ "setmem", setmem },
|
|
#endif
|
|
|
|
{ NULL, NULL }
|
|
|
|
};
|
|
void AddNatives_General()
|
|
{
|
|
MF_AddNatives(general_natives);
|
|
}
|