Committed hopefully final version
This commit is contained in:
parent
b1718bc334
commit
4687ecd99b
@ -1,4 +1,5 @@
|
||||
#include "Binary.h"
|
||||
#include "amxxmodule.h"
|
||||
|
||||
BinaryWriter::BinaryWriter(FILE *fp)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include "compat.h"
|
||||
#include "amxxmodule.h"
|
||||
|
||||
class BinaryReader
|
||||
{
|
||||
|
126
dlls/nvault/CQueue.h
Executable file
126
dlls/nvault/CQueue.h
Executable file
@ -0,0 +1,126 @@
|
||||
/* AMX Mod X
|
||||
*
|
||||
* by the AMX Mod X Development Team
|
||||
* originally developed by OLO
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* In addition, as a special exception, the author gives permission to
|
||||
* link the code of this program with the Half-Life Game Engine ("HL
|
||||
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
|
||||
* L.L.C ("Valve"). You must obey the GNU General Public License in all
|
||||
* respects for all of the code used other than the HL Engine and MODs
|
||||
* from Valve. If you modify this file, you may extend this exception
|
||||
* to your version of the file, but you are not obligated to do so. If
|
||||
* you do not wish to do so, delete this exception statement from your
|
||||
* version.
|
||||
*/
|
||||
|
||||
//by David "BAILOPAN" Anderson
|
||||
#ifndef _INCLUDE_CQUEUE_H
|
||||
#define _INCLUDE_CQUEUE_H
|
||||
|
||||
template <class T>
|
||||
class CQueue
|
||||
{
|
||||
public:
|
||||
class CQueueItem
|
||||
{
|
||||
public:
|
||||
CQueueItem(const T &i, CQueueItem *n)
|
||||
{
|
||||
item = i;
|
||||
next = n;
|
||||
}
|
||||
CQueueItem *GetNext()
|
||||
{
|
||||
return next;
|
||||
}
|
||||
T & GetItem()
|
||||
{
|
||||
return item;
|
||||
}
|
||||
void SetNext(CQueueItem *n)
|
||||
{
|
||||
next = n;
|
||||
}
|
||||
private:
|
||||
T item;
|
||||
CQueueItem *next;
|
||||
};
|
||||
public:
|
||||
CQueue()
|
||||
{
|
||||
mSize = 0;
|
||||
mFirst = NULL;
|
||||
mLast = NULL;
|
||||
}
|
||||
|
||||
bool empty()
|
||||
{
|
||||
return ((mSize==0)?true:false);
|
||||
}
|
||||
|
||||
void push(const T &v)
|
||||
{
|
||||
CQueueItem *p = new CQueueItem(v, NULL);
|
||||
if (empty())
|
||||
{
|
||||
mFirst = p;
|
||||
} else {
|
||||
mLast->SetNext(p);
|
||||
}
|
||||
mLast = p;
|
||||
mSize++;
|
||||
}
|
||||
|
||||
void pop()
|
||||
{
|
||||
if (mFirst == mLast)
|
||||
{
|
||||
delete mFirst;
|
||||
mFirst = NULL;
|
||||
mLast = NULL;
|
||||
} else {
|
||||
CQueueItem *p = mFirst->GetNext();
|
||||
delete mFirst;
|
||||
mFirst = p;
|
||||
}
|
||||
mSize--;
|
||||
}
|
||||
|
||||
T & front()
|
||||
{
|
||||
return mFirst->GetItem();
|
||||
}
|
||||
|
||||
T & back()
|
||||
{
|
||||
return mLast->GetItem();
|
||||
}
|
||||
|
||||
unsigned int size()
|
||||
{
|
||||
return mSize;
|
||||
}
|
||||
private:
|
||||
CQueueItem *mFirst;
|
||||
CQueueItem *mLast;
|
||||
unsigned int mSize;
|
||||
};
|
||||
|
||||
#endif //_INCLUDE_CQUEUE_H
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "NVault.h"
|
||||
#include "Binary.h"
|
||||
#include "amxxmodule.h"
|
||||
|
||||
template <class K>
|
||||
int HashFunction<String>(const K & k)
|
||||
@ -154,6 +155,17 @@ bool NVault::_SaveToFile()
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *NVault::GetValue(const char *key)
|
||||
{
|
||||
String sKey(key);
|
||||
if (!m_Hash.Exists(sKey))
|
||||
{
|
||||
return "";
|
||||
} else {
|
||||
return m_Hash.Retrieve(sKey).c_str();
|
||||
}
|
||||
}
|
||||
|
||||
bool NVault::Open()
|
||||
{
|
||||
_ReadFromFile();
|
||||
@ -189,6 +201,7 @@ bool NVault::Close()
|
||||
_SaveToFile();
|
||||
m_Journal->End();
|
||||
m_Journal->Erase();
|
||||
m_Open = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -260,3 +273,10 @@ bool NVault::GetValue(const char *key, time_t &stamp, char buffer[], size_t len)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IVault *VaultMngr::OpenVault(const char *file)
|
||||
{
|
||||
NVault *pVault = new NVault(file);
|
||||
|
||||
return static_cast<IVault *>(pVault);
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ public:
|
||||
~NVault();
|
||||
public:
|
||||
bool GetValue(const char *key, time_t &stamp, char buffer[], size_t len);
|
||||
const char *GetValue(const char *key);
|
||||
void SetValue(const char *key, const char *val);
|
||||
void SetValue(const char *key, const char *val, time_t stamp);
|
||||
size_t Prune(time_t start, time_t end);
|
||||
@ -44,6 +45,7 @@ public:
|
||||
bool Open();
|
||||
bool Close();
|
||||
size_t Items();
|
||||
const char *GetFilename() { return m_File.c_str(); }
|
||||
private:
|
||||
VaultError _ReadFromFile();
|
||||
bool _SaveToFile();
|
||||
@ -54,7 +56,7 @@ private:
|
||||
bool m_Open;
|
||||
};
|
||||
|
||||
class VaultMngr
|
||||
class VaultMngr : public IVaultMngr
|
||||
{
|
||||
public:
|
||||
//when you delete it, it will be closed+saved automatically
|
||||
|
@ -1,5 +1,13 @@
|
||||
#include <stdlib.h>
|
||||
#include "amxxapi.h"
|
||||
#include "NVault.h"
|
||||
#include "CQueue.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#define MKDIR(p) mkdir(p)
|
||||
#else
|
||||
#define MKDIR(p) mkdir(p, 0755)
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <unistd.h>
|
||||
@ -7,29 +15,37 @@
|
||||
#include <direct.h>
|
||||
#endif
|
||||
|
||||
CVector<Vault *> Vaults;
|
||||
CVector<NVault *> g_Vaults;
|
||||
CQueue<int> g_OldVaults;
|
||||
|
||||
VaultMngr g_VaultMngr;
|
||||
|
||||
static cell nvault_open(AMX *amx, cell *params)
|
||||
{
|
||||
int len, id=-1;
|
||||
char *name = MF_GetAmxString(amx, params[1], 0, &len);
|
||||
char *file = MF_BuildPathname("%s/nvault/%s", LOCALINFO("amxx_datadir"), name);
|
||||
for (size_t i=0; i<Vaults.size(); i++)
|
||||
char path[255], file[255];
|
||||
MF_BuildPathnameR(path, sizeof(path)-1, "%s/vault", LOCALINFO("amxx_datadir"));
|
||||
sprintf(file, "%s/%s.vault", path, file);
|
||||
for (size_t i=0; i<g_Vaults.size(); i++)
|
||||
{
|
||||
if (!Vaults.at(i))
|
||||
if (strcmp(g_Vaults.at(i)->GetFilename(), file) == 0)
|
||||
{
|
||||
id = i;
|
||||
} else if (strcmp(Vaults.at(i)->GetFileName(), file) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
Vault *v = new Vault(file);
|
||||
NVault *v = new NVault(file);
|
||||
if (!g_OldVaults.empty())
|
||||
{
|
||||
id = g_OldVaults.front();
|
||||
g_OldVaults.pop();
|
||||
}
|
||||
if (id != -1)
|
||||
{
|
||||
Vaults[id] = v;
|
||||
g_Vaults[id] = v;
|
||||
} else {
|
||||
Vaults.push_back(v);
|
||||
id = (int)Vaults.size()-1;
|
||||
g_Vaults.push_back(v);
|
||||
id = (int)g_Vaults.size()-1;
|
||||
}
|
||||
|
||||
return id;
|
||||
@ -38,15 +54,16 @@ static cell nvault_open(AMX *amx, cell *params)
|
||||
static cell nvault_get(AMX *amx, cell *params)
|
||||
{
|
||||
unsigned int id = params[1];
|
||||
if (id > Vaults.size() || !Vaults.at(id))
|
||||
if (id >= g_Vaults.size() || !g_Vaults.at(id))
|
||||
{
|
||||
MF_LogError(amx, AMX_ERR_NATIVE, "Invalid vault id: %d\n", id);
|
||||
return 0;
|
||||
}
|
||||
NVault *pVault = g_Vaults.at(id);
|
||||
unsigned int numParams = (*params)/sizeof(cell);
|
||||
int len;
|
||||
char *key = MF_GetAmxString(amx, params[2], 0, &len);
|
||||
const char *val = Vaults.at(id)->Find(key)->val.c_str();
|
||||
const char *val = pVault->GetValue(key);
|
||||
switch (numParams)
|
||||
{
|
||||
case 2:
|
||||
@ -72,61 +89,45 @@ static cell nvault_get(AMX *amx, cell *params)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static cell nvault_timeget(AMX *amx, cell *params)
|
||||
static cell nvault_lookup(AMX *amx, cell *params)
|
||||
{
|
||||
unsigned int id = params[1];
|
||||
if (id > Vaults.size() || !Vaults.at(id))
|
||||
if (id >= g_Vaults.size() || !g_Vaults.at(id))
|
||||
{
|
||||
MF_LogError(amx, AMX_ERR_NATIVE, "Invalid vault id: %d\n", id);
|
||||
return 0;
|
||||
}
|
||||
unsigned int numParams = (*params)/sizeof(cell);
|
||||
NVault *pVault = g_Vaults.at(id);
|
||||
int len;
|
||||
HashTable::htNode *node;
|
||||
char *key = MF_GetAmxString(amx, params[2], 0, &len);
|
||||
node = Vaults.at(id)->Find(key);
|
||||
const char *val = node->val.c_str();
|
||||
cell *t_addr = MF_GetAmxAddr(amx, params[3]);
|
||||
*t_addr = (cell)(node->stamp);
|
||||
switch (numParams)
|
||||
time_t stamp;
|
||||
char *key = MF_GetAmxString(amx, params[1], 0, &len);
|
||||
char *buffer = new char[params[3]+1];
|
||||
if (!pVault->GetValue(key, stamp, buffer, params[3]))
|
||||
{
|
||||
case 3:
|
||||
{
|
||||
return atoi(val);
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
cell *fAddr = MF_GetAmxAddr(amx, params[4]);
|
||||
*fAddr = amx_ftoc((REAL)atof(val));
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
len = *(MF_GetAmxAddr(amx, params[5]));
|
||||
return MF_SetAmxString(amx, params[4], val, len);
|
||||
break;
|
||||
}
|
||||
delete [] buffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
MF_SetAmxString(amx, params[2], buffer, params[3]);
|
||||
cell *addr = MF_GetAmxAddr(amx, params[4]);
|
||||
addr[0] = (cell)stamp;
|
||||
delete [] buffer;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static cell nvault_set(AMX *amx, cell *params)
|
||||
{
|
||||
unsigned int id = params[1];
|
||||
if (id > Vaults.size() || !Vaults.at(id))
|
||||
if (id >= g_Vaults.size() || !g_Vaults.at(id))
|
||||
{
|
||||
MF_LogError(amx, AMX_ERR_NATIVE, "Invalid vault id: %d\n", id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
NVault *pVault = g_Vaults.at(id);
|
||||
int len;
|
||||
char *key = MF_GetAmxString(amx, params[2], 0, &len);
|
||||
char *val = MF_FormatAmxString(amx, params, 3, &len);
|
||||
|
||||
Vaults.at(id)->Store(key, val);
|
||||
pVault->SetValue(key, val);
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -134,27 +135,57 @@ static cell nvault_set(AMX *amx, cell *params)
|
||||
static cell nvault_pset(AMX *amx, cell *params)
|
||||
{
|
||||
unsigned int id = params[1];
|
||||
if (id > Vaults.size() || !Vaults.at(id))
|
||||
if (id >= g_Vaults.size() || !g_Vaults.at(id))
|
||||
{
|
||||
MF_LogError(amx, AMX_ERR_NATIVE, "Invalid vault id: %d\n", id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
NVault *pVault = g_Vaults.at(id);
|
||||
int len;
|
||||
char *key = MF_GetAmxString(amx, params[2], 0, &len);
|
||||
char *val = MF_FormatAmxString(amx, params, 3, &len);
|
||||
|
||||
Vaults.at(id)->Store(key, val, false);
|
||||
pVault->SetValue(key, val, 0);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static cell nvault_close(AMX *amx, cell *params)
|
||||
{
|
||||
unsigned int id = params[1];
|
||||
if (id >= g_Vaults.size() || !g_Vaults.at(id))
|
||||
{
|
||||
MF_LogError(amx, AMX_ERR_NATIVE, "Invalid vault id: %d\n", id);
|
||||
return 0;
|
||||
}
|
||||
NVault *pVault = g_Vaults.at(id);
|
||||
pVault->Close();
|
||||
delete pVault;
|
||||
g_Vaults[id] = NULL;
|
||||
g_OldVaults.push(id);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
IVaultMngr *GetVaultMngr()
|
||||
{
|
||||
return static_cast<IVaultMngr *>(&g_VaultMngr);
|
||||
}
|
||||
|
||||
void OnAmxxAttach()
|
||||
{
|
||||
//create the dir if it doesn't exist
|
||||
#ifdef __linux__
|
||||
mkdir(MF_BuildPathname("%s/nvault", LOCALINFO("amxx_datadir")), 0700);
|
||||
#else
|
||||
mkdir(MF_BuildPathname("%s/nvault", LOCALINFO("amxx_datadir")));
|
||||
#endif
|
||||
MKDIR(MF_BuildPathname("%s/vault", LOCALINFO("amxx_datadir")));
|
||||
MF_AddNatives(nVault_natives);
|
||||
MF_RegisterFunction(GetVaultMngr, "GetVaultMngr");
|
||||
}
|
||||
|
||||
AMX_NATIVE_INFO nVault_natives[] = {
|
||||
{"nvault_open", nvault_open},
|
||||
{"nvault_get", nvault_get},
|
||||
{"nvault_lookup", nvault_lookup},
|
||||
{"nvault_set", nvault_set},
|
||||
{"nvault_pset", nvault_pset},
|
||||
{"nvault_close", nvault_close},
|
||||
{NULL, NULL},
|
||||
};
|
@ -2,10 +2,9 @@
|
||||
#define _INCLUDE_AMXXAPI_H
|
||||
|
||||
#include "CVector.h"
|
||||
#include "hash.h"
|
||||
#include "nvault.h"
|
||||
#include "journal.h"
|
||||
#include "sdk/amxxmodule.h"
|
||||
#include "CQueue.h"
|
||||
#include "CString.h"
|
||||
#include "amxxmodule.h"
|
||||
|
||||
AMX_NATIVE_INFO nVault_natives[];
|
||||
|
||||
|
@ -2502,6 +2502,7 @@ PFN_GET_PLAYER_EDICT g_fn_GetPlayerEdict;
|
||||
PFN_FORMAT g_fn_Format;
|
||||
PFN_REGISTERFUNCTION g_fn_RegisterFunction;
|
||||
PFN_REQ_FNPTR g_fn_RequestFunction;
|
||||
PFN_AMX_PUSH g_fn_AmxPush;
|
||||
|
||||
// *** Exports ***
|
||||
C_DLLEXPORT int AMXX_Query(int *interfaceVersion, amxx_module_info_s *moduleInfo)
|
||||
@ -2610,6 +2611,7 @@ C_DLLEXPORT int AMXX_Attach(PFN_REQ_FNPTR reqFnptrFunc)
|
||||
REQFUNC("GetPlayerHealth", g_fn_GetPlayerHealth, PFN_GET_PLAYER_HEALTH);
|
||||
REQFUNC("GetPlayerFlags", g_fn_GetPlayerFlags, PFN_GETPLAYERFLAGS);
|
||||
REQFUNC("GetPlayerEdict", g_fn_GetPlayerEdict, PFN_GET_PLAYER_EDICT);
|
||||
REQFUNC("amx_Push", g_fn_AmxPush, PFN_AMX_PUSH);
|
||||
|
||||
// Memory
|
||||
REQFUNC_OPT("Allocator", g_fn_Allocator, PFN_ALLOCATOR);
|
||||
@ -2717,7 +2719,7 @@ void ValidateMacros_DontCallThis_Smiley()
|
||||
MF_IsPlayerHLTV(0);
|
||||
MF_GetPlayerArmor(0);
|
||||
MF_GetPlayerHealth(0);
|
||||
MF_AmxExec(0, 0, 0, 0);
|
||||
MF_AmxExec(0, 0, 0);
|
||||
MF_AmxExecv(0, 0, 0, 0, 0);
|
||||
MF_AmxFindPublic(0, 0, 0);
|
||||
MF_AmxAllot(0, 0, 0, 0);
|
@ -1987,7 +1987,7 @@ typedef void * (*PFN_REALLOCATOR) (const char* /*filename*/, const unsigned
|
||||
const unsigned int /*type*/, const size_t /*size*/, void* /*addr*/ );
|
||||
typedef void (*PFN_DEALLOCATOR) (const char* /*filename*/, const unsigned int /*line*/, const char* /*func*/,
|
||||
const unsigned int /*type*/, const void* /*addr*/ );
|
||||
typedef int (*PFN_AMX_EXEC) (AMX* /*amx*/, cell* /*return val*/, int /*index*/, int /*numparams*/, ... /*params*/);
|
||||
typedef int (*PFN_AMX_EXEC) (AMX* /*amx*/, cell* /*return val*/, int /*index*/);
|
||||
typedef int (*PFN_AMX_EXECV) (AMX* /*amx*/, cell* /*return val*/, int /*index*/, int /*numparams*/, cell[] /*params*/);
|
||||
typedef int (*PFN_AMX_ALLOT) (AMX* /*amx*/, int /*length*/, cell* /*amx_addr*/, cell** /*phys_addr*/);
|
||||
typedef int (*PFN_AMX_FINDPUBLIC) (AMX* /*amx*/, char* /*func name*/, int* /*index*/);
|
||||
@ -2002,6 +2002,7 @@ typedef void (*PFN_UNREGISTER_SPFORWARD) (int /*id*/);
|
||||
typedef void (*PFN_MERGEDEFINITION_FILE) (const char * /*filename*/);
|
||||
typedef const char * (*PFN_FORMAT) (const char * /*fmt*/, ... /*params*/);
|
||||
typedef void (*PFN_REGISTERFUNCTION) (void * /*pfn*/, const char * /*desc*/);
|
||||
typedef int (*PFN_AMX_PUSH) (AMX * /*amx*/, cell /*value*/);
|
||||
|
||||
extern PFN_ADD_NATIVES g_fn_AddNatives;
|
||||
extern PFN_BUILD_PATHNAME g_fn_BuildPathname;
|
||||
@ -2065,6 +2066,7 @@ extern PFN_FORMAT g_fn_Format;
|
||||
extern PFN_GET_PLAYER_TEAM g_fn_GetPlayerTeam;
|
||||
extern PFN_REGISTERFUNCTION g_fn_RegisterFunction;
|
||||
extern PFN_REQ_FNPTR g_fn_RequestFunction;
|
||||
extern PFN_AMX_PUSH g_fn_AmxPush;
|
||||
|
||||
#ifdef MAY_NEVER_BE_DEFINED
|
||||
// Function prototypes for intellisense and similar systems
|
||||
@ -2123,6 +2125,8 @@ edict_t* MF_GetPlayerEdict (int id) { }
|
||||
const char * MF_Format (const char *fmt, ...) { }
|
||||
void MF_RegisterFunction (void *pfn, const char *description) { }
|
||||
void * MF_RequestFunction (const char *description) { }
|
||||
int MF_AmxPush (AMX *amx, cell *params) { }
|
||||
int MF_AmxExec (AMX *amx, cell *retval, int idx) { }
|
||||
#endif // MAY_NEVER_BE_DEFINED
|
||||
|
||||
#define MF_AddNatives g_fn_AddNatives
|
||||
@ -2187,6 +2191,7 @@ void MF_LogError(AMX *amx, int err, const char *fmt, ...);
|
||||
#define MF_Format g_fn_Format
|
||||
#define MF_RegisterFunction g_fn_RegisterFunction
|
||||
#define MF_RequestFunction g_fn_RequestFunction;
|
||||
#define MF_AmxPush g_fn_AmxPush
|
||||
|
||||
/*** Memory ***/
|
||||
void *operator new(size_t reportedSize);
|
@ -2,8 +2,6 @@
|
||||
#define _INCLUDE_COMPAT_H
|
||||
|
||||
#ifdef WIN32
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef __int8 int8_t;
|
||||
|
@ -152,6 +152,9 @@
|
||||
<File
|
||||
RelativePath=".\compat.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\CQueue.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\IVault.h">
|
||||
</File>
|
||||
@ -174,10 +177,10 @@
|
||||
Name="SDK"
|
||||
Filter="">
|
||||
<File
|
||||
RelativePath=".\sdk\amxxmodule.cpp">
|
||||
RelativePath=".\amxxmodule.cpp">
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\sdk\amxxmodule.h">
|
||||
RelativePath=".\amxxmodule.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\CString.h">
|
||||
@ -185,6 +188,9 @@
|
||||
<File
|
||||
RelativePath=".\sdk\moduleconfig.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\moduleconfig.h">
|
||||
</File>
|
||||
</Filter>
|
||||
</Files>
|
||||
<Globals>
|
||||
|
Loading…
Reference in New Issue
Block a user