Irrlicht only: added support for height computation for items
(and other models loaded for a track), items now do rotate and can be collected (though no icons are displayed yet, nor do attachments work). git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/trunk/supertuxkart@3196 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
parent
a9b0a695d4
commit
253f495021
@ -435,6 +435,7 @@ void RaceGUI::drawPowerupIcons ( Kart* player_kart, int offset_x,
|
||||
int y1 = (int)(user_config->m_height*5/6 * ratio_y) + offset_y;
|
||||
|
||||
int nSize=(int)(64.0f*std::min(ratio_x, ratio_y));
|
||||
#ifndef HAVE_IRRLICHT
|
||||
powerup->getIcon()->apply();
|
||||
|
||||
int n = player_kart->getNumPowerup() ;
|
||||
@ -453,7 +454,7 @@ void RaceGUI::drawPowerupIcons ( Kart* player_kart, int offset_x,
|
||||
glTexCoord2f(0, 1); glVertex2i( i*30 + x1 , y1+nSize);
|
||||
} // for i
|
||||
glEnd () ;
|
||||
|
||||
#endif
|
||||
} // drawPowerupIcons
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -198,6 +198,45 @@ int XMLNode::get(core::vector3df *value) const
|
||||
if(get("z", &f)) { value->Z = f; bits |= 4; }
|
||||
if(get("r", &f)) { value->Z = f; bits |= 4; }
|
||||
return bits;
|
||||
}
|
||||
} // core::vector3df
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Interprets the attributes 'x', 'y', 'z' as a 3d vector and set the
|
||||
* corresponding elements of value. Not all values need to be defined as
|
||||
* attributes (and the correspnding elements of the vector will not be
|
||||
* changed). It returns a bit field for each defined value, i.e. if x
|
||||
* and y are defined, 3 is returned.
|
||||
* \param value Vector to return the values in.
|
||||
*/
|
||||
int XMLNode::getXYZ(core::vector3df *value) const
|
||||
{
|
||||
float f;
|
||||
int bits=0;
|
||||
core::vector3df result = *value;
|
||||
if(get("x", &f)) { value->X = f; bits |= 1; }
|
||||
if(get("y", &f)) { value->Y = f; bits |= 2; }
|
||||
if(get("z", &f)) { value->Z = f; bits |= 4; }
|
||||
return bits;
|
||||
} // getXYZ
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Interprets the attributes 'h', 'p', 'r' as a 3d vector and set the
|
||||
* corresponding elements of value. Not all values need to be defined as
|
||||
* attributes (and the correspnding elements of the vector will not be
|
||||
* changed). It returns a bit field for each defined value, i.e. if x and y
|
||||
* are defined, 3 is returned.
|
||||
* \param value Vector to return the values in.
|
||||
*/
|
||||
int XMLNode::getHPR(core::vector3df *value) const
|
||||
{
|
||||
float f;
|
||||
int bits=0;
|
||||
core::vector3df result = *value;
|
||||
if(get("h", &f)) { value->X = f; bits |= 1; }
|
||||
if(get("p", &f)) { value->Y = f; bits |= 2; }
|
||||
if(get("r", &f)) { value->Z = f; bits |= 4; }
|
||||
return bits;
|
||||
} // getHPR
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
#endif
|
||||
|
@ -50,6 +50,8 @@ public:
|
||||
int get(const std::string &attribute, std::vector<float> *value) const;
|
||||
int get(const std::string &attribute, std::vector<int> *value) const;
|
||||
int get(core::vector3df *value) const;
|
||||
int getXYZ(core::vector3df *value) const;
|
||||
int getHPR(core::vector3df *value) const;
|
||||
/** Handy functions to test the bit pattern returned by get(vector3df*).*/
|
||||
static bool hasX(int b) { return (b&1)==1; }
|
||||
static bool hasY(int b) { return (b&2)==2; }
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "items/item.hpp"
|
||||
|
||||
#include "graphics/irr_driver.hpp"
|
||||
#include "graphics/scene.hpp"
|
||||
#include "karts/kart.hpp"
|
||||
#include "utils/coord.hpp"
|
||||
@ -43,6 +44,9 @@ Item::Item(ItemType type, const Vec3& xyz, const Vec3& normal,
|
||||
m_collected = false;
|
||||
m_time_till_return = 0.0f; // not strictly necessary, see isCollected()
|
||||
#ifdef HAVE_IRRLICHT
|
||||
m_root = irr_driver->addMesh(mesh);
|
||||
m_root->setPosition(xyz.toIrrVector());
|
||||
m_root->grab();
|
||||
#else
|
||||
m_root = new ssgTransform();
|
||||
m_root->ref();
|
||||
@ -56,6 +60,8 @@ Item::Item(ItemType type, const Vec3& xyz, const Vec3& normal,
|
||||
Item::~Item()
|
||||
{
|
||||
#ifdef HAVE_IRRLICHT
|
||||
|
||||
m_root->drop();
|
||||
#else
|
||||
stk_scene->remove(m_root);
|
||||
ssgDeRefDelete(m_root);
|
||||
@ -94,14 +100,18 @@ void Item::update(float delta)
|
||||
|
||||
hell.setZ( (m_time_till_return>1.0f) ? -1000000.0f
|
||||
: m_coord.getXYZ().getZ() - m_time_till_return / 2.0f);
|
||||
#ifndef HAVE_IRRLICHT
|
||||
#ifdef HAVE_IRRLICHT
|
||||
m_root->setPosition(hell.toIrrVector());
|
||||
#else
|
||||
m_root->setTransform(hell.toFloat());
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
m_collected = false;
|
||||
#ifndef HAVE_IRRLICHT
|
||||
#ifdef HAVE_IRRLICHT
|
||||
|
||||
#else
|
||||
m_root->setTransform(const_cast<sgCoord*>(&m_coord.toSgCoord()));
|
||||
#endif
|
||||
} // T>0
|
||||
@ -112,10 +122,12 @@ void Item::update(float delta)
|
||||
|
||||
if(!m_rotate) return;
|
||||
// have it rotate
|
||||
#ifdef HAVE_IRRLICHT
|
||||
#else
|
||||
Vec3 rotation(delta*M_PI, 0, 0);
|
||||
m_coord.setHPR(m_coord.getHPR()+rotation);
|
||||
#ifdef HAVE_IRRLICHT
|
||||
m_root->setRotation(m_coord.getHPR().toIrrHPR());
|
||||
m_root->setPosition(m_coord.getXYZ().toIrrVector());
|
||||
#else
|
||||
m_root->setTransform(const_cast<sgCoord*>(&m_coord.toSgCoord()));
|
||||
#endif
|
||||
}
|
||||
|
@ -39,15 +39,13 @@ class Item
|
||||
public:
|
||||
enum ItemType
|
||||
{
|
||||
ITEM_FIRST = -1,
|
||||
|
||||
ITEM_BONUS_BOX = 0,
|
||||
ITEM_FIRST,
|
||||
ITEM_BONUS_BOX = ITEM_FIRST,
|
||||
ITEM_BANANA,
|
||||
ITEM_GOLD_COIN,
|
||||
ITEM_SILVER_COIN,
|
||||
ITEM_BUBBLEGUM,
|
||||
|
||||
ITEM_LAST
|
||||
ITEM_LAST = ITEM_BUBBLEGUM
|
||||
};
|
||||
|
||||
private:
|
||||
@ -57,6 +55,8 @@ private:
|
||||
Coord m_coord; // Original coordinates, used mainly when
|
||||
// collected items reappear.
|
||||
#ifdef HAVE_IRRLICHT
|
||||
/** Scene node of this item. */
|
||||
scene::ISceneNode *m_root;
|
||||
#else
|
||||
ssgTransform* m_root; // The actual root of the item
|
||||
#endif
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "loader.hpp"
|
||||
#include "material_manager.hpp"
|
||||
#include "material.hpp"
|
||||
#include "graphics/irr_driver.hpp"
|
||||
#include "items/item_manager.hpp"
|
||||
#include "items/bubblegumitem.hpp"
|
||||
#include "karts/kart.hpp"
|
||||
@ -160,6 +161,13 @@ void ItemManager::loadDefaultItems()
|
||||
i != files.end(); ++i)
|
||||
{
|
||||
#ifdef HAVE_IRRLICHT
|
||||
// FIXME: We should try to check the extension,
|
||||
// i.e. load only .3ds files
|
||||
scene::IMesh *mesh = irr_driver->getAnimatedMesh(*i);
|
||||
if(!mesh) continue;
|
||||
std::string shortName = StringUtils::basename(StringUtils::without_extension(*i));
|
||||
m_all_meshes[shortName] = mesh;
|
||||
mesh->grab();
|
||||
#else
|
||||
if(!StringUtils::has_suffix(*i, ".ac")) continue;
|
||||
ssgEntity* h = loader->load(*i, CB_ITEM,
|
||||
@ -179,7 +187,7 @@ void ItemManager::loadDefaultItems()
|
||||
void ItemManager::setDefaultItemStyle()
|
||||
{
|
||||
// FIXME - This should go in an internal, system wide configuration file
|
||||
std::string DEFAULT_NAMES[Item::ITEM_LAST - Item::ITEM_FIRST - 1];
|
||||
std::string DEFAULT_NAMES[Item::ITEM_LAST - Item::ITEM_FIRST +1];
|
||||
DEFAULT_NAMES[Item::ITEM_BONUS_BOX] = "gift-box";
|
||||
DEFAULT_NAMES[Item::ITEM_BANANA] = "banana";
|
||||
DEFAULT_NAMES[Item::ITEM_GOLD_COIN] = "nitrotank-big";
|
||||
@ -188,7 +196,7 @@ void ItemManager::setDefaultItemStyle()
|
||||
|
||||
bool bError=0;
|
||||
std::ostringstream msg;
|
||||
for(int i=Item::ITEM_FIRST+1; i<Item::ITEM_LAST; i++)
|
||||
for(int i=Item::ITEM_FIRST; i<=Item::ITEM_LAST; i++)
|
||||
{
|
||||
#ifdef HAVE_IRRLICHT
|
||||
m_item_mesh[i] = m_all_meshes[DEFAULT_NAMES[i]];
|
||||
|
@ -40,7 +40,7 @@ private:
|
||||
// This stores all item models
|
||||
#ifdef HAVE_IRRLICHT
|
||||
// FIXME: why ITEM_SILVER_COINT+1 in plib??
|
||||
scene::IMesh *m_item_mesh[Item::ITEM_LAST];
|
||||
scene::IMesh *m_item_mesh[Item::ITEM_LAST-Item::ITEM_FIRST+1];
|
||||
std::map<std::string,scene::IMesh*> m_all_meshes;
|
||||
#else
|
||||
ssgEntity *m_item_model[Item::ITEM_SILVER_COIN+1];
|
||||
|
@ -76,3 +76,16 @@ void TriangleMesh::createBody(btCollisionObject::CollisionFlags flags)
|
||||
} // createBody
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
/** Removes the created body from the physics world. This is used when creating
|
||||
* a temporary rigid body of the main track to get bullet raycasts. Then the
|
||||
* main track is removed, and the track (main track including all additional
|
||||
* objects which were loaded later) is converted again.
|
||||
*/
|
||||
void TriangleMesh::removeBody()
|
||||
{
|
||||
RaceManager::getWorld()->getPhysics()->removeBody(m_body);
|
||||
delete m_body;
|
||||
m_body = 0;
|
||||
} // removeBody
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -17,8 +17,8 @@
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
#ifndef HEADER_TRIANGLE_MESH_H
|
||||
#define HEADER_TRIANGLE_MESH_H
|
||||
#ifndef HEADER_TRIANGLE_MESH_HPP
|
||||
#define HEADER_TRIANGLE_MESH_HPP
|
||||
|
||||
#include <vector>
|
||||
#include "user_pointer.hpp"
|
||||
@ -45,6 +45,7 @@ public:
|
||||
const btVector3 &t3, const Material* m);
|
||||
void createBody(btCollisionObject::CollisionFlags flags=
|
||||
(btCollisionObject::CollisionFlags)0);
|
||||
void removeBody();
|
||||
const Material* getMaterial(int n) const {return m_triangleIndex2Material[n];}
|
||||
};
|
||||
#endif
|
||||
|
@ -58,16 +58,18 @@ const int Track::UNKNOWN_SECTOR = -1;
|
||||
// ----------------------------------------------------------------------------
|
||||
Track::Track( std::string filename_, float w, float h, bool stretch )
|
||||
{
|
||||
m_filename = filename_;
|
||||
m_item_style = "";
|
||||
m_track_2d_width = w;
|
||||
m_track_2d_height = h;
|
||||
m_do_stretch = stretch;
|
||||
m_description = "";
|
||||
m_designer = "";
|
||||
m_screenshot = "";
|
||||
m_top_view = "";
|
||||
m_version = 0;
|
||||
m_filename = filename_;
|
||||
m_item_style = "";
|
||||
m_track_2d_width = w;
|
||||
m_track_2d_height = h;
|
||||
m_do_stretch = stretch;
|
||||
m_description = "";
|
||||
m_designer = "";
|
||||
m_screenshot = "";
|
||||
m_top_view = "";
|
||||
m_version = 0;
|
||||
m_track_mesh = new TriangleMesh();
|
||||
m_non_collision_mesh = new TriangleMesh();
|
||||
#ifdef HAVE_IRRLICHT
|
||||
m_all_nodes.clear();
|
||||
m_all_meshes.clear();
|
||||
@ -1147,11 +1149,20 @@ void Track::createPhysicsModel()
|
||||
if(!m_model) return;
|
||||
#endif
|
||||
|
||||
m_track_mesh = new TriangleMesh();
|
||||
m_non_collision_mesh = new TriangleMesh();
|
||||
|
||||
#ifdef HAVE_IRRLICHT
|
||||
convertTrackToBullet();
|
||||
// Remove the temporary track rigid body, and then convert all objects
|
||||
// (i.e. the track and all additional objects) into a new rigid body
|
||||
// and convert this again. So this way we have an optimised track
|
||||
// rigid body which includes all track objects.
|
||||
// Note that removing the rigid body does not remove the already collected
|
||||
// triangle information, so there is no need to convert the actual track
|
||||
// (first element in m_track_mesh) again!
|
||||
m_track_mesh->removeBody();
|
||||
for(unsigned int i=1; i<m_all_meshes.size(); i++)
|
||||
{
|
||||
convertTrackToBullet(m_all_meshes[i]);
|
||||
}
|
||||
#else
|
||||
// Collect all triangles in the track_mesh
|
||||
sgMat4 mat;
|
||||
@ -1166,19 +1177,10 @@ void Track::createPhysicsModel()
|
||||
// -----------------------------------------------------------------------------
|
||||
//* Convert the ssg track tree into its physics equivalents.
|
||||
#ifdef HAVE_IRRLICHT
|
||||
void Track::convertTrackToBullet()
|
||||
{
|
||||
// 0: start line
|
||||
// 1: left/right drivelines or so
|
||||
// 2: road
|
||||
// 3: zipper or collectables??
|
||||
// 4: barriers
|
||||
// 5: plane
|
||||
for(unsigned int i=0; i<m_all_meshes.size(); i++)
|
||||
{
|
||||
const scene::IMesh *track = m_all_meshes[i];
|
||||
for(unsigned int i=0; i<track->getMeshBufferCount(); i++) {
|
||||
scene::IMeshBuffer *mb = track->getMeshBuffer(i);
|
||||
void Track::convertTrackToBullet(const scene::IMesh *mesh)
|
||||
{
|
||||
for(unsigned int i=0; i<mesh->getMeshBufferCount(); i++) {
|
||||
scene::IMeshBuffer *mb = mesh->getMeshBuffer(i);
|
||||
// FIXME: take translation/rotation into accou
|
||||
if(mb->getVertexType()!=video::EVT_STANDARD) {
|
||||
fprintf(stderr, "WARNING: Physics::convertTrack: Ignoring type '%d'!",
|
||||
@ -1195,7 +1197,7 @@ void Track::convertTrackToBullet()
|
||||
}
|
||||
|
||||
u16 *mbIndices = mb->getIndices();
|
||||
btVector3 vertices[3];
|
||||
Vec3 vertices[3];
|
||||
irr::video::S3DVertex* mbVertices=(video::S3DVertex*)mb->getVertices();
|
||||
for(unsigned int j=0; j<mb->getIndexCount(); j+=3) {
|
||||
for(unsigned int k=0; k<3; k++) {
|
||||
@ -1204,15 +1206,14 @@ void Track::convertTrackToBullet()
|
||||
// (STK: Z up, irrlicht: Y up). We might want to change
|
||||
// this as well, makes it easier to work with bullet and
|
||||
// irrlicht together, without having to swap indices.
|
||||
vertices[k] = btVector3(mbVertices[indx].Pos.X,
|
||||
mbVertices[indx].Pos.Z,
|
||||
mbVertices[indx].Pos.Y );
|
||||
vertices[k] = Vec3(mbVertices[indx].Pos.X,
|
||||
mbVertices[indx].Pos.Z,
|
||||
mbVertices[indx].Pos.Y );
|
||||
} // for k
|
||||
m_track_mesh->addTriangle(vertices[0], vertices[1],
|
||||
vertices[2], material );
|
||||
} // for j
|
||||
} // for i<getMeshBufferCount
|
||||
} // for obj in children
|
||||
|
||||
} // convertTrackToBullet
|
||||
|
||||
@ -1284,6 +1285,46 @@ void Track::convertTrackToBullet(ssgEntity *track, sgMat4 m)
|
||||
} // convertTrackToBullet
|
||||
#endif
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Loads the main track model (i.e. all other objects contained in the
|
||||
* scene might use raycast on this track model to determine the actual
|
||||
* height of the terrain.
|
||||
*/
|
||||
#ifdef HAVE_IRRLICHT
|
||||
bool Track::loadMainTrack(const XMLNode &node)
|
||||
{
|
||||
std::string model_name;
|
||||
node.get("model", &model_name);
|
||||
std::string full_path = file_manager->getTrackFile(model_name,
|
||||
getIdent());
|
||||
scene::IMesh *mesh = irr_driver->getAnimatedMesh(full_path);
|
||||
if(!mesh)
|
||||
{
|
||||
fprintf(stderr, "Warning: Main track model '%s' in '%s' not found, aborting.\n",
|
||||
node.getName().c_str(), model_name.c_str());
|
||||
exit(-1);
|
||||
}
|
||||
m_all_meshes.push_back(mesh);
|
||||
scene::ISceneNode *scene_node = irr_driver->addOctTree(mesh);
|
||||
core::vector3df xyz(0,0,0);
|
||||
node.getXYZ(&xyz);
|
||||
core::vector3df hpr(0,0,0);
|
||||
node.getHPR(&hpr);
|
||||
scene_node->setPosition(xyz);
|
||||
scene_node->setRotation(hpr);
|
||||
m_all_nodes.push_back(scene_node);
|
||||
scene_node->setMaterialFlag(video::EMF_LIGHTING, false);
|
||||
|
||||
Vec3 min, max;
|
||||
MeshTools::minMax3D(mesh, &min, &max);
|
||||
RaceManager::getWorld()->getPhysics()->init(min, max);
|
||||
|
||||
// This will (at this stage) only convert the main track model.
|
||||
convertTrackToBullet(mesh);
|
||||
m_track_mesh->createBody();
|
||||
return true;
|
||||
} // loadMainTrack
|
||||
#endif
|
||||
// ----------------------------------------------------------------------------
|
||||
void Track::loadTrackModel()
|
||||
{
|
||||
// Add the track directory to the texture search path
|
||||
@ -1308,19 +1349,22 @@ void Track::loadTrackModel()
|
||||
|
||||
// Make sure that we have a track (which is used for raycasts to
|
||||
// place other objects).
|
||||
const XMLNode *node = xml->getNode(0);
|
||||
if(!node || node->getName()!="track")
|
||||
const XMLNode *node = xml->getNode("track");
|
||||
if(!node)
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg<< "No track model defined in '"<<path
|
||||
<<"' (it must be the first element).";
|
||||
<<"', aborting.";
|
||||
throw std::runtime_error(msg.str());
|
||||
}
|
||||
loadMainTrack(*node);
|
||||
for(unsigned int i=0; i<xml->getNumNodes(); i++)
|
||||
{
|
||||
const XMLNode *node = xml->getNode(i);
|
||||
const std::string name = node->getName();
|
||||
if(name=="track" || name=="object")
|
||||
// The track object was already converted before the loop
|
||||
if(name=="track") continue;
|
||||
if(name=="object")
|
||||
{
|
||||
std::string model_name;
|
||||
node->get("model", &model_name);
|
||||
@ -1607,19 +1651,17 @@ void Track::loadTrackModel()
|
||||
file_manager->popTextureSearchPath();
|
||||
file_manager->popModelSearchPath ();
|
||||
|
||||
Vec3 min, max;
|
||||
#ifdef HAVE_IRRLICHT
|
||||
// FIXME: for now assume that mesh 0 is the actual track.
|
||||
MeshTools::minMax3D(m_all_meshes[0], &min, &max);
|
||||
const core::vector3df &sun_pos = getSunPos();
|
||||
m_light = irr_driver->getSceneManager()->addLightSceneNode(0, sun_pos);
|
||||
video::SLight light;
|
||||
m_light->setLightData(light);
|
||||
|
||||
// Note: the physics world for irrlicht is created in loadMainTrack
|
||||
#else
|
||||
Vec3 min, max;
|
||||
SSGHelp::MinMax(m_model, &min, &max);
|
||||
#endif
|
||||
RaceManager::getWorld()->getPhysics()->init(min, max);
|
||||
#endif
|
||||
createPhysicsModel();
|
||||
} // loadTrack
|
||||
|
||||
@ -1634,7 +1676,16 @@ void Track::itemCommand(core::vector3df *xyz, Item::ItemType type,
|
||||
return;
|
||||
|
||||
// if only 2d coordinates are given, let the item fall from very high
|
||||
if(bNeedHeight) xyz->Z = 1000000.0f;
|
||||
if(bNeedHeight)
|
||||
{
|
||||
Vec3 pos(*xyz);
|
||||
float m_HoT;
|
||||
Vec3 m_normal;
|
||||
const Material *material;
|
||||
pos.setZ(1000);
|
||||
getTerrainInfo(pos, &m_HoT, &m_normal, &material);
|
||||
xyz->Z = m_HoT;
|
||||
}
|
||||
|
||||
// Even if 3d data are given, make sure that the item is on the ground
|
||||
//xyz->Z = irr_dirver->getHeight( m_model, *xyz ) + 0.06f;
|
||||
|
@ -42,6 +42,7 @@ using namespace irr;
|
||||
#include "utils/vec3.hpp"
|
||||
|
||||
class TriangleMesh;
|
||||
class XMLNode;
|
||||
|
||||
class Track
|
||||
{
|
||||
@ -71,7 +72,9 @@ private:
|
||||
Vec3 m_camera_final_hpr;
|
||||
bool m_is_arena;
|
||||
int m_version;
|
||||
|
||||
#ifdef HAVE_IRRLICHT
|
||||
bool loadMainTrack(const XMLNode &node);
|
||||
#endif
|
||||
public:
|
||||
enum RoadSide{ RS_DONT_KNOW = -1, RS_LEFT = 0, RS_RIGHT = 1 };
|
||||
|
||||
@ -239,7 +242,7 @@ private:
|
||||
void readDrivelineFromFile(std::vector<Vec3>& line,
|
||||
const std::string& file_ext);
|
||||
#ifdef HAVE_IRRLICHT
|
||||
void convertTrackToBullet();
|
||||
void convertTrackToBullet(const scene::IMesh *mesh);
|
||||
#else
|
||||
void convertTrackToBullet(ssgEntity *track, sgMat4 m);
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user