mirror of
https://github.com/libretro/scummvm.git
synced 2025-03-04 01:07:22 +00:00
TETRAEDGE: Fix a lot of small rendering bugs.
This commit is contained in:
parent
4a8f9984d4
commit
769d8d9b42
@ -91,6 +91,7 @@ void Application::create() {
|
||||
_mainWindow.setSize(TeVector3f32(winWidth, winHeight, 0.0));
|
||||
_mainWindow.setSizeType(TeILayout::ABSOLUTE);
|
||||
_mainWindow.setPositionType(TeILayout::ABSOLUTE);
|
||||
_mainWindow.setPosition(TeVector3f32(0.0f, 0.0f ,0.0f));
|
||||
|
||||
TeResourceManager *resmgr = g_engine->getResourceManager();
|
||||
_fontComic = resmgr->getResourceNoSearch<TeFont3>("Common/Fonts/ComicRelief.ttf");
|
||||
|
@ -387,6 +387,8 @@ void Game::finishGame() {
|
||||
|
||||
void Game::initLoadedBackupData() {
|
||||
bool warpFlag = true;
|
||||
Application *app = g_engine->getApplication();
|
||||
Common::String firstWarpPath;
|
||||
if (!_loadName.empty()) {
|
||||
warpFlag = false;
|
||||
Common::InSaveFile *saveFile = g_engine->getSaveFileManager()->openForLoading(_loadName);
|
||||
@ -396,19 +398,19 @@ void Game::initLoadedBackupData() {
|
||||
if (MetaEngine::readSavegameHeader(saveFile, &header))
|
||||
g_engine->setTotalPlayTime(header.playtime);
|
||||
}
|
||||
} else {
|
||||
firstWarpPath = app->_firstWarpPath;
|
||||
_currentScene = app->_firstScene;
|
||||
_currentZone = app->_firstZone;
|
||||
_playedTimer.start();
|
||||
_objectsTakenVal = 0;
|
||||
for (int i = 0; i < ARRAYSIZE(_objectsTakenBits); i++) {
|
||||
_objectsTakenBits[i] = 0;
|
||||
}
|
||||
_dialogsTold = 0;
|
||||
if (_loadName == "NO_OWNER")
|
||||
_luaShowOwnerError = true;
|
||||
}
|
||||
Application *app = g_engine->getApplication();
|
||||
const Common::String firstWarpPath = app->_firstWarpPath;
|
||||
_currentScene = app->_firstScene;
|
||||
_currentZone = app->_firstZone;
|
||||
_playedTimer.start();
|
||||
_objectsTakenVal = 0;
|
||||
for (int i = 0; i < ARRAYSIZE(_objectsTakenBits); i++) {
|
||||
_objectsTakenBits[i] = 0;
|
||||
}
|
||||
_dialogsTold = 0;
|
||||
if (_loadName == "NO_OWNER")
|
||||
_luaShowOwnerError = true;
|
||||
_gameLoadState = 0;
|
||||
app->showLoadingIcon(false);
|
||||
_loadName.clear();
|
||||
@ -798,13 +800,13 @@ bool Game::onCallNumber(Common::String val) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Game::onCharacterAnimationFinished(const Common::String &val) {
|
||||
bool Game::onCharacterAnimationFinished(const Common::String &charName) {
|
||||
if (!_scene._character)
|
||||
return false;
|
||||
|
||||
for (unsigned int i = 0; i < _yieldedCallbacks.size(); i++) {
|
||||
YieldedCallback &cb = _yieldedCallbacks[i];
|
||||
if (cb._luaFnName == "OnCharacterAnimationFinished" && cb._luaParam == val) {
|
||||
if (cb._luaFnName == "OnCharacterAnimationFinished" && cb._luaParam == charName) {
|
||||
TeLuaThread *lua = cb._luaThread;
|
||||
_yieldedCallbacks.remove_at(i);
|
||||
if (lua) {
|
||||
@ -814,7 +816,7 @@ bool Game::onCharacterAnimationFinished(const Common::String &val) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_luaScript.execute("OnCharacterAnimationFinished", val);
|
||||
_luaScript.execute("OnCharacterAnimationFinished", charName);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -105,8 +105,8 @@ bool InGameScene::addMarker(const Common::String &markerName, const Common::Stri
|
||||
newPos.x() = frontLayoutSize.x() * (x / 100.0);
|
||||
newPos.y() = frontLayoutSize.y() * (y / 100.0);
|
||||
} else {
|
||||
newPos.x() = x / 800.0;
|
||||
newPos.y() = y / 600.0;
|
||||
newPos.x() = x / g_engine->getDefaultScreenWidth();
|
||||
newPos.y() = y / g_engine->getDefaultScreenHeight();
|
||||
}
|
||||
markerSprite->setPosition(newPos);
|
||||
|
||||
@ -865,9 +865,11 @@ void InGameScene::loadInteractions(const Common::Path &path) {
|
||||
Game *game = g_engine->getGame();
|
||||
TeSpriteLayout *root = game->findSpriteLayoutByName(bgbackground, "root");
|
||||
TeLayout *background = _hitObjectGui.layoutChecked("background");
|
||||
// TODO: For all TeButtonLayout childen of background, call
|
||||
// setDoubleValidationProtectionEnabled(false)
|
||||
// For now our button doesn't implement that.
|
||||
for (auto *child : background->childList()) {
|
||||
TeButtonLayout *btn = dynamic_cast<TeButtonLayout *>(child);
|
||||
if (btn)
|
||||
btn->setDoubleValidationProtectionEnabled(false);
|
||||
}
|
||||
background->setRatioMode(TeILayout::RATIO_MODE_NONE);
|
||||
root->addChild(background);
|
||||
}
|
||||
|
@ -498,25 +498,43 @@ bool Inventory::updateLayout() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#define DEBUG_SAVELOAD 1
|
||||
|
||||
Common::Error Inventory::syncState(Common::Serializer &s) {
|
||||
unsigned int nitems = _invObjects.size();
|
||||
s.syncAsUint32LE(nitems);
|
||||
if (s.isLoading()) {
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("Inventory::syncState: --- Loading %d inventory items: ---", nitems);
|
||||
#endif
|
||||
for (unsigned int i = 0; i < nitems; i++) {
|
||||
Common::String objname;
|
||||
s.syncString(objname);
|
||||
addObject(objname);
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("Inventory::syncState: %s", objname.c_str());
|
||||
#endif
|
||||
}
|
||||
} else if (nitems) {
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("Inventory::syncState: --- Saving %d inventory items: --- ", _invObjects.size());
|
||||
#endif
|
||||
// Add items in reverse order as the "addObject" on load will
|
||||
// add to front of list.InventoryObject
|
||||
auto iter = _invObjects.end();
|
||||
while (iter != _invObjects.begin()) {
|
||||
iter--;
|
||||
Common::String objname = (*iter)->name();
|
||||
s.syncString(objname);
|
||||
iter--;
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("Inventory::syncState: %s", objname.c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("Inventory::syncState: -------- end --------");
|
||||
#endif
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
|
@ -1716,9 +1716,13 @@ static int tolua_ExportedFunctions_PlayRandomSound00(lua_State *L) {
|
||||
|
||||
static void PlayMusic(const Common::String &path) {
|
||||
TeMusic &music = g_engine->getApplication()->music();
|
||||
// Note: stop and set repeat before starting,
|
||||
// very slightly different to original because we can't
|
||||
// change repeat value after starting.
|
||||
music.stop();
|
||||
music.repeat(false);
|
||||
music.load(path);
|
||||
music.play();
|
||||
music.repeat(false);
|
||||
music.volume(1.0);
|
||||
}
|
||||
|
||||
|
@ -202,7 +202,17 @@ bool Te3DTexture::load(const TeImage &img) {
|
||||
|
||||
/*static*/
|
||||
TeVector2s32 Te3DTexture::optimisedSize(const TeVector2s32 &size) {
|
||||
/* The maths here is a bit funky but it just picks the nearest power of 2 (up) */
|
||||
//
|
||||
// Note: When we enabled optimized sizes it leaves artifacts around movies
|
||||
// etc unless the render size is exactly 800x600.
|
||||
//
|
||||
// This probably means there is a rounding error somewhere else, just leave
|
||||
// off for now.
|
||||
//
|
||||
if (g_engine->getDefaultScreenWidth() != 800)
|
||||
return size;
|
||||
|
||||
// The maths here is a bit funky but it just picks the nearest power of 2 (up)
|
||||
int xsize = size._x - 1;
|
||||
int ysize = size._y - 1;
|
||||
|
||||
@ -224,6 +234,7 @@ TeVector2s32 Te3DTexture::optimisedSize(const TeVector2s32 &size) {
|
||||
v2 = 8;
|
||||
}
|
||||
return TeVector2s32(v1, v2);
|
||||
|
||||
}
|
||||
|
||||
/*static*/
|
||||
|
@ -86,7 +86,7 @@ Graphics::Font *TeFont3::getAtSize(unsigned int size) {
|
||||
error("TeFont3::: Couldn't open font file %s.", getAccessName().toString().c_str());
|
||||
|
||||
_fontFile.seek(0);
|
||||
Graphics::Font *newFont = Graphics::loadTTFFont(_fontFile, size);
|
||||
Graphics::Font *newFont = Graphics::loadTTFFont(_fontFile, size, Graphics::kTTFSizeModeCharacter, 0, Graphics::kTTFRenderModeNormal);
|
||||
if (!newFont) {
|
||||
error("TeFont3::: Couldn't load font %s at size %d.", _loadedPath.toString().c_str(), size);
|
||||
}
|
||||
|
@ -81,8 +81,26 @@ float TeFreeMoveZone::bordersDistance() const {
|
||||
return _graph->_bordersDistance;
|
||||
}
|
||||
|
||||
TeVector2s32 TeFreeMoveZone::aStarResolution() const {
|
||||
TeVector2f32 diff = (_someGridVec2 - _someGridVec1);
|
||||
TeVector2s32 retval = TeVector2s32(diff / _gridOffsetSomething) + TeVector2s32(1, 1);
|
||||
if (retval._x > 2000)
|
||||
retval._x = 200;
|
||||
if (retval._y > 2000)
|
||||
retval._y = 200;
|
||||
return retval;
|
||||
}
|
||||
|
||||
void TeFreeMoveZone::buildAStar() {
|
||||
error("TODO: Implement TeFreeMoveZone::buildAStar");
|
||||
preUpdateGrid();
|
||||
TeVector2s32 resolution = aStarResolution();
|
||||
_graph->setSize(resolution);
|
||||
if (!_loadedFromBin) {
|
||||
error("TODO: Implement TeFreeMoveZone::buildAStar for *not* loaded from bin case");
|
||||
} else {
|
||||
// Loaded from bin..
|
||||
error("TODO: Implement TeFreeMoveZone::buildAStar for loaded from bin case");
|
||||
}
|
||||
}
|
||||
|
||||
void TeFreeMoveZone::calcGridMatrix() {
|
||||
|
@ -115,6 +115,8 @@ public:
|
||||
Common::Array<TePickMesh2*> &pickMeshes, TeVector3f32 *outloc, bool lastHitFirst);
|
||||
|
||||
private:
|
||||
TeVector2s32 aStarResolution() const;
|
||||
|
||||
Common::Array<TeActZone> *_actzones;
|
||||
Common::Array<TeBlocker> *_blockers;
|
||||
Common::Array<TeRectBlocker> *_rectBlockers;
|
||||
|
@ -123,11 +123,17 @@ enum TeLuaSaveVarType {
|
||||
String = 4
|
||||
};
|
||||
|
||||
#define DEBUG_SAVELOAD 1
|
||||
|
||||
Common::Error TeLuaContext::syncState(Common::Serializer &s) {
|
||||
// Save/Load globals. The format of saving is:
|
||||
// [type][name][val] [type][name][val]...
|
||||
// Vals can be string, number (uint32), or boolean (byte)
|
||||
// The type of "None" (0) is the end of the list (and has no name/val).
|
||||
if (s.isSaving()) {
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("TeLuaContext::syncState: --- Saving globals: ---");
|
||||
#endif
|
||||
lua_pushvalue(_luaState, LUA_GLOBALSINDEX);
|
||||
lua_pushnil(_luaState);
|
||||
int nextresult = lua_next(_luaState, -2);
|
||||
@ -139,66 +145,88 @@ Common::Error TeLuaContext::syncState(Common::Serializer &s) {
|
||||
break;
|
||||
}
|
||||
unsigned int vtype = lua_type(_luaState, -1);
|
||||
Common::String name = lua_tolstring(_luaState, -2, nullptr);
|
||||
if (vtype == LUA_TBOOLEAN) {
|
||||
TeLuaSaveVarType stype = Boolean;
|
||||
Common::String name = lua_tolstring(_luaState, -2, nullptr);
|
||||
s.syncAsUint32LE(stype);
|
||||
s.syncString(name);
|
||||
bool val = lua_toboolean(_luaState, -1);
|
||||
s.syncAsByte(val);
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("TeLuaContext::syncState: bool %s = %s", name.c_str(), val ? "true" : "false");
|
||||
#endif
|
||||
} else if (vtype == LUA_TNUMBER) {
|
||||
TeLuaSaveVarType stype = Number;
|
||||
Common::String name = lua_tolstring(_luaState, -2, nullptr);
|
||||
s.syncAsUint32LE(stype);
|
||||
s.syncString(name);
|
||||
double val = lua_tonumber(_luaState, -1);
|
||||
s.syncAsDoubleLE(val);
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("TeLuaContext::syncState: num %s = %f", name.c_str(), val);
|
||||
#endif
|
||||
} else if (vtype == LUA_TSTRING) {
|
||||
TeLuaSaveVarType stype = String;
|
||||
Common::String name = lua_tolstring(_luaState, -2, nullptr);
|
||||
s.syncAsUint32LE(stype);
|
||||
s.syncString(name);
|
||||
Common::String val = lua_tostring(_luaState, -1);
|
||||
s.syncString(val);
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("TeLuaContext::syncState: str %s = '%s'", name.c_str(), val.c_str());
|
||||
#endif
|
||||
}
|
||||
lua_settop(_luaState, -2);
|
||||
nextresult = lua_next(_luaState, -2);
|
||||
}
|
||||
} else {
|
||||
warning("Confirm loading in TeLuaContext::syncState");
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("TeLuaContext::syncState: --- Loading globals: --- ");
|
||||
#endif
|
||||
// loading
|
||||
TeLuaSaveVarType vtype = None;
|
||||
s.syncAsUint32LE(vtype);
|
||||
while (vtype != None) {
|
||||
Common::String name;
|
||||
s.syncString(name);
|
||||
switch (vtype) {
|
||||
case Boolean: {
|
||||
byte b;
|
||||
s.syncAsByte(b);
|
||||
lua_pushboolean(_luaState, b);
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("TeLuaContext::syncState: bool %s = %s", name.c_str(), b ? "true" : "false");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case Number: {
|
||||
float d;
|
||||
s.syncAsDoubleLE(d);
|
||||
lua_pushnumber(_luaState, d);
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("TeLuaContext::syncState: num %s = %f", name.c_str(), d);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case String: {
|
||||
Common::String str;
|
||||
s.syncString(str);
|
||||
lua_pushstring(_luaState, str.c_str());
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("TeLuaContext::syncState: str %s = '%s'", name.c_str(), str.c_str());
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
error("Unexpected lua type on load %d", (int)vtype);
|
||||
}
|
||||
Common::String name;
|
||||
s.syncString(name);
|
||||
lua_setglobal(_luaState, name.c_str());
|
||||
s.syncAsUint32LE(vtype);
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG_SAVELOAD
|
||||
debug("TeLuaContext::syncState: -------- end --------");
|
||||
#endif
|
||||
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
|
@ -80,6 +80,8 @@ void TeMesh::draw() {
|
||||
/*
|
||||
debug("Draw mesh %p (%s, %d verts %d norms %d indexes %d materials %d updated)", this, name().empty() ? "no name" : name().c_str(), _verticies.size(), _normals.size(), _indexes.size(), _materials.size(), _updatedVerticies.size());
|
||||
debug(" renderMatrix %s", renderer->currentMatrix().toString().c_str());
|
||||
if (!_materials.empty())
|
||||
debug(" material %s", _materials[0].dump().c_str());
|
||||
debug(" position %s", position().dump().c_str());
|
||||
debug(" worldPos %s", worldPosition().dump().c_str());
|
||||
debug(" scale %s", scale().dump().c_str());
|
||||
|
@ -103,7 +103,7 @@ void TeModel::draw() {
|
||||
renderer->pushMatrix();
|
||||
renderer->multiplyMatrix(transform);
|
||||
/*
|
||||
if (name().contains("Kate")) {
|
||||
if (name().contains("DEPLIANT")) {
|
||||
debug("Draw model %p (%s, %d meshes)", this, name().empty() ? "no name" : name().c_str(), _meshes.size());
|
||||
debug(" renderMatrix %s", renderer->currentMatrix().toString().c_str());
|
||||
//debug(" position %s", position().dump().c_str());
|
||||
@ -194,6 +194,7 @@ void TeModel::update() {
|
||||
float complete = blender->coef();
|
||||
TeTRS endTRS = getBone(blender->_anim, b);
|
||||
if (complete == 1.0f) {
|
||||
_modelAnim = blender->_anim;
|
||||
delete blender;
|
||||
_boneBlenders.remove_at(i);
|
||||
trs = endTRS;
|
||||
|
@ -46,7 +46,8 @@ void TeSpriteLayout::draw() {
|
||||
if (!worldVisible())
|
||||
return;
|
||||
|
||||
/*if (parent() && parent()->name() == "inventoryButton")
|
||||
/*
|
||||
if (name() == "DEPLIANT")
|
||||
debug("Draw SpriteLayout %p (%s, surf %s, size %.01fx%.01f, surf %.01fx%.01f, %s)", this,
|
||||
name().empty() ? "no name" : name().c_str(), _tiledSurfacePtr->getAccessName().toString().c_str(),
|
||||
size().x(), size().y(),
|
||||
|
@ -18,6 +18,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//#define DUMP_RENDERED_FONTS 1
|
||||
|
||||
#ifdef DUMP_RENDERED_FONTS
|
||||
#include "image/png.h"
|
||||
@ -73,7 +75,11 @@ void TeTextBase2::build() {
|
||||
lineoffsets.push_back(height);
|
||||
height += lineHeight + _interLine;
|
||||
}
|
||||
_size._y = (int)ceilf(height);
|
||||
|
||||
// Round up to the nearest 2 pixels so it centres in the
|
||||
// area without a half pixel offset.
|
||||
_size._y = (((int)ceilf(height) + 1) / 2) * 2;
|
||||
_size._x = ((_size._x + 1) / 2) * 2;
|
||||
|
||||
TeImage img;
|
||||
Common::SharedPtr<TePalette> nullpal;
|
||||
@ -96,8 +102,7 @@ void TeTextBase2::build() {
|
||||
|
||||
_mesh.setConf(4, 4, TeMesh::MeshMode_TriangleStrip, 0, 0);
|
||||
_mesh.defaultMaterial(texture);
|
||||
// FIXME: Original uses BLEND, but we need MODULATE to get right colors?
|
||||
//_mesh.setglTexEnv(GL_MODULATE);
|
||||
_mesh.setglTexEnv(GL_BLEND);
|
||||
_mesh.setShouldDraw(true);
|
||||
_mesh.setColor(_globalColor);
|
||||
_mesh.setVertex(0, TeVector3f32(_size._x * -0.5f, _size._y * -0.5f, 0.0f));
|
||||
@ -200,10 +205,7 @@ void TeTextBase2::drawEmptyChar(unsigned int offset) {
|
||||
void TeTextBase2::drawLine(TeImage &img, const Common::String &str, int yoffset) {
|
||||
TeIntrusivePtr<TeFont3> font = _fonts[0];
|
||||
|
||||
// TODO: Add multi-color support if needed?
|
||||
// We set black here as the color is set on the mesh and we use
|
||||
// MODULATE blend in build()
|
||||
font->draw(img, str, _fontSize, yoffset, TeColor(0, 0, 0, 255), _alignStyle);
|
||||
font->draw(img, str, _fontSize, yoffset, _globalColor, _alignStyle);
|
||||
}
|
||||
|
||||
unsigned int TeTextBase2::endOfWord(unsigned int offset) const {
|
||||
|
@ -34,4 +34,8 @@ void TeVector2s32::deserialize(Common::ReadStream &stream, TeVector2s32 &dest) {
|
||||
dest._y = stream.readSint32LE();
|
||||
}
|
||||
|
||||
TeVector2s32 operator+(const TeVector2s32 &left, const TeVector2s32 &right) {
|
||||
return TeVector2s32(left._x + right._x, left._y + right._y);
|
||||
}
|
||||
|
||||
} // end namespace Tetraedge
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include "common/rect.h"
|
||||
#include "common/stream.h"
|
||||
#include "math/vector2d.h"
|
||||
|
||||
namespace Tetraedge {
|
||||
|
||||
@ -32,6 +33,7 @@ public:
|
||||
TeVector2s32();
|
||||
TeVector2s32(int x_, int y_) : _x(x_), _y(y_) {};
|
||||
TeVector2s32(const Common::Point &pt) : _x(pt.x), _y(pt.y) {};
|
||||
explicit TeVector2s32(const Math::Vector2d &pt) : _x(pt.getX()), _y(pt.getY()) {};
|
||||
|
||||
bool operator!=(const TeVector2s32 &other) const {
|
||||
return _x != other._x || _y != other._y;
|
||||
@ -62,6 +64,8 @@ public:
|
||||
|
||||
};
|
||||
|
||||
TeVector2s32 operator+(const TeVector2s32 &left, const TeVector2s32 &right);
|
||||
|
||||
} // end namespace Tetraedge
|
||||
|
||||
#endif // TETRAEDGE_TE_TE_VECTOR2S32_H
|
||||
|
@ -107,7 +107,9 @@ void TeVisualFade::init() {
|
||||
// create an image the size of the window, no palette, format 6.
|
||||
Common::SharedPtr<TePalette> nullpal;
|
||||
_image.destroy();
|
||||
_image.create(1024, 768, nullpal, TeImage::RGBA8);
|
||||
// TODO: should this get actual window size instead of default?
|
||||
_image.create(g_engine->getDefaultScreenWidth(),
|
||||
g_engine->getDefaultScreenHeight(), nullpal, TeImage::RGBA8);
|
||||
_texturePtr->load(_image);
|
||||
g_engine->getRenderer()->enableTexture();
|
||||
_texturePtr->load(_image);
|
||||
|
@ -165,11 +165,11 @@ void TetraedgeEngine::configureSearchPaths() {
|
||||
}
|
||||
|
||||
int TetraedgeEngine::getDefaultScreenWidth() const {
|
||||
return 1024;
|
||||
return 800;
|
||||
}
|
||||
|
||||
int TetraedgeEngine::getDefaultScreenHeight() const {
|
||||
return 768;
|
||||
return 600;
|
||||
}
|
||||
|
||||
Common::Error TetraedgeEngine::run() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user