Change logic that splits chat messages with new lines to support fonticons properly
This commit is contained in:
parent
624daa858e
commit
b36df5130b
@ -2,6 +2,10 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
Game::dvar_t** Chat::cg_chatHeight = reinterpret_cast<Game::dvar_t**>(0x7ED398);
|
||||
Dvar::Var Chat::cg_chatWidth;
|
||||
Game::dvar_t** Chat::cg_chatTime = reinterpret_cast<Game::dvar_t**>(0x9F5DE8);
|
||||
|
||||
bool Chat::SendChat;
|
||||
|
||||
const char* Chat::EvaluateSay(char* text, Game::gentity_t* player)
|
||||
@ -74,11 +78,128 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
void Chat::CheckChatLineEnd(const char*& inputBuffer, char*& lineBuffer, float& len, const int chatHeight, const float chatWidth, char*& lastSpacePos, char*& lastFontIconPos, const int lastColor)
|
||||
{
|
||||
if (len > chatWidth)
|
||||
{
|
||||
if (lastSpacePos && lastSpacePos > lastFontIconPos)
|
||||
{
|
||||
inputBuffer += lastSpacePos - lineBuffer + 1;
|
||||
lineBuffer = lastSpacePos;
|
||||
}
|
||||
else if (lastFontIconPos)
|
||||
{
|
||||
inputBuffer += lastFontIconPos - lineBuffer;
|
||||
lineBuffer = lastFontIconPos;
|
||||
}
|
||||
*lineBuffer = 0;
|
||||
len = 0.0f;
|
||||
Game::cgsArray[0].teamChatMsgTimes[Game::cgsArray[0].teamChatPos % chatHeight] = Game::cgArray[0].time;
|
||||
|
||||
Game::cgsArray[0].teamChatPos++;
|
||||
lineBuffer = Game::cgsArray[0].teamChatMsgs[Game::cgsArray[0].teamChatPos % chatHeight];
|
||||
lineBuffer[0] = '^';
|
||||
lineBuffer[1] = CharForColorIndex(lastColor);
|
||||
lineBuffer += 2;
|
||||
lastSpacePos = nullptr;
|
||||
lastFontIconPos = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Chat::CG_AddToTeamChat(const char* text)
|
||||
{
|
||||
// Text can only be 150 characters maximum. This is bigger than the teamChatMsgs buffers with 160 characters
|
||||
// Therefore it is not needed to check for buffer lengths
|
||||
|
||||
const auto chatHeight = (*cg_chatHeight)->current.integer;
|
||||
const auto chatWidth = static_cast<float>(cg_chatWidth.get<int>());
|
||||
const auto chatTime = (*cg_chatTime)->current.integer;
|
||||
if (chatHeight < 0 || static_cast<unsigned>(chatHeight) > std::extent_v<decltype(Game::cgs_t::teamChatMsgs)> || chatWidth <= 0 || chatTime <= 0)
|
||||
{
|
||||
Game::cgsArray[0].teamLastChatPos = 0;
|
||||
Game::cgsArray[0].teamChatPos = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
TextRenderer::FontIconInfo fontIconInfo{};
|
||||
auto len = 0.0f;
|
||||
auto lastColor = static_cast<int>(TEXT_COLOR_DEFAULT);
|
||||
char* lastSpace = nullptr;
|
||||
char* lastFontIcon = nullptr;
|
||||
char* p = Game::cgsArray[0].teamChatMsgs[Game::cgsArray[0].teamChatPos % chatHeight];
|
||||
p[0] = '\0';
|
||||
|
||||
while (*text)
|
||||
{
|
||||
CheckChatLineEnd(text, p, len, chatHeight, chatWidth, lastSpace, lastFontIcon, lastColor);
|
||||
|
||||
const char* fontIconEndPos = &text[1];
|
||||
if(text[0] == ':' && TextRenderer::IsFontIcon(fontIconEndPos, fontIconInfo))
|
||||
{
|
||||
// The game calculates width on a per character base. Since the width of a font icon is calculated based on the height of the font
|
||||
// which is roughly double as much as the average width of a character without an additional multiplier the calculated len of the font icon
|
||||
// would be less than it most likely would be rendered. Therefore apply a guessed 2.0f multiplier at this location which makes
|
||||
// the calculated width of a font icon roughly comparable to the width of an average character of the font.
|
||||
const auto normalizedFontIconWidth = TextRenderer::GetNormalizedFontIconWidth(fontIconInfo);
|
||||
const auto fontIconWidth = normalizedFontIconWidth * FONT_ICON_CHAT_WIDTH_CALCULATION_MULTIPLIER;
|
||||
len += fontIconWidth;
|
||||
|
||||
lastFontIcon = p;
|
||||
for(; text < fontIconEndPos; text++)
|
||||
{
|
||||
p[0] = text[0];
|
||||
p++;
|
||||
}
|
||||
|
||||
CheckChatLineEnd(text, p, len, chatHeight, chatWidth, lastSpace, lastFontIcon, lastColor);
|
||||
}
|
||||
else if (text[0] == '^' && text[1] != 0 && text[1] >= TextRenderer::COLOR_FIRST_CHAR && text[1] <= TextRenderer::COLOR_LAST_CHAR)
|
||||
{
|
||||
p[0] = '^';
|
||||
p[1] = text[1];
|
||||
lastColor = ColorIndexForChar(text[1]);
|
||||
p += 2;
|
||||
text += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (text[0] == ' ')
|
||||
lastSpace = p;
|
||||
*p++ = *text++;
|
||||
len += 1.0f;
|
||||
}
|
||||
}
|
||||
*p = 0;
|
||||
Game::cgsArray[0].teamChatMsgTimes[Game::cgsArray[0].teamChatPos % chatHeight] = Game::cgArray[0].time;
|
||||
if (Game::cgsArray[0].teamChatPos++ + 1 - Game::cgsArray[0].teamLastChatPos > chatHeight)
|
||||
Game::cgsArray[0].teamLastChatPos = Game::cgsArray[0].teamChatPos + 1 - chatHeight;
|
||||
}
|
||||
|
||||
__declspec(naked) void Chat::CG_AddToTeamChat_Stub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
|
||||
push ecx
|
||||
call CG_AddToTeamChat
|
||||
add esp, 4h
|
||||
|
||||
popad
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
Chat::Chat()
|
||||
{
|
||||
cg_chatWidth = Dvar::Register<int>("cg_chatWidth", 52, 1, INT_MAX, Game::DVAR_FLAG_SAVED, "The normalized maximum width of a chat message");
|
||||
|
||||
// Intercept chat sending
|
||||
Utils::Hook(0x4D000B, PreSayStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4D00D4, PostSayStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4D0110, PostSayStub, HOOK_CALL).install()->quick();
|
||||
|
||||
// Change logic that does word splitting with new lines for chat messages to support fonticons
|
||||
Utils::Hook(0x592E10, CG_AddToTeamChat_Stub, HOOK_JUMP).install()->quick();
|
||||
}
|
||||
}
|
||||
|
@ -4,15 +4,24 @@ namespace Components
|
||||
{
|
||||
class Chat : public Component
|
||||
{
|
||||
static constexpr auto FONT_ICON_CHAT_WIDTH_CALCULATION_MULTIPLIER = 2.0f;
|
||||
public:
|
||||
Chat();
|
||||
|
||||
private:
|
||||
static Game::dvar_t** cg_chatHeight;
|
||||
static Dvar::Var cg_chatWidth;
|
||||
static Game::dvar_t** cg_chatTime;
|
||||
|
||||
static bool SendChat;
|
||||
|
||||
static const char* EvaluateSay(char* text, Game::gentity_t* player);
|
||||
|
||||
static void PreSayStub();
|
||||
static void PostSayStub();
|
||||
|
||||
static void CheckChatLineEnd(const char*& inputBuffer, char*& lineBuffer, float& len, int chatHeight, float chatWidth, char*& lastSpacePos, char*& lastFontIconPos, int lastColor);
|
||||
static void CG_AddToTeamChat(const char* text);
|
||||
static void CG_AddToTeamChat_Stub();
|
||||
};
|
||||
}
|
||||
|
@ -636,6 +636,21 @@ namespace Components
|
||||
return true;
|
||||
}
|
||||
|
||||
float TextRenderer::GetNormalizedFontIconWidth(const FontIconInfo& fontIcon)
|
||||
{
|
||||
const auto* colorMap = GetFontIconColorMap(fontIcon.material);
|
||||
if (colorMap == nullptr)
|
||||
return 0;
|
||||
const auto sizeMultiplier = fontIcon.big ? 1.5f : 1.0f;
|
||||
auto colWidth = static_cast<float>(colorMap->width);
|
||||
auto colHeight = static_cast<float>(colorMap->height);
|
||||
if (fontIcon.material->info.textureAtlasColumnCount > 1)
|
||||
colWidth /= static_cast<float>(fontIcon.material->info.textureAtlasColumnCount);
|
||||
if (fontIcon.material->info.textureAtlasRowCount > 1)
|
||||
colHeight /= static_cast<float>(fontIcon.material->info.textureAtlasRowCount);
|
||||
return (colWidth / colHeight) * sizeMultiplier;
|
||||
}
|
||||
|
||||
float TextRenderer::GetFontIconWidth(const FontIconInfo& fontIcon, const Game::Font_s* font, const float xScale)
|
||||
{
|
||||
const auto* colorMap = GetFontIconColorMap(fontIcon.material);
|
||||
|
@ -49,14 +49,6 @@ namespace Components
|
||||
Game::Material* material;
|
||||
};
|
||||
|
||||
struct FontIconInfo
|
||||
{
|
||||
Game::Material* material;
|
||||
bool flipHorizontal;
|
||||
bool flipVertical;
|
||||
bool big;
|
||||
};
|
||||
|
||||
struct HsvColor
|
||||
{
|
||||
unsigned char h;
|
||||
@ -99,8 +91,6 @@ namespace Components
|
||||
float maxMaterialNameWidth;
|
||||
};
|
||||
|
||||
static constexpr char COLOR_FIRST_CHAR = '0';
|
||||
static constexpr char COLOR_LAST_CHAR = CharForColorIndex(TEXT_COLOR_COUNT - 1);
|
||||
static constexpr unsigned MY_ALTCOLOR_TWO = 0x0DCE6FFE6;
|
||||
static constexpr unsigned COLOR_MAP_HASH = 0xA0AB1041;
|
||||
static constexpr auto FONT_ICON_AUTOCOMPLETE_BOX_PADDING = 6.0f;
|
||||
@ -153,6 +143,17 @@ namespace Components
|
||||
static Game::dvar_t** con_inputBoxColor;
|
||||
|
||||
public:
|
||||
static constexpr char COLOR_FIRST_CHAR = '0';
|
||||
static constexpr char COLOR_LAST_CHAR = CharForColorIndex(TEXT_COLOR_COUNT - 1);
|
||||
|
||||
struct FontIconInfo
|
||||
{
|
||||
Game::Material* material;
|
||||
bool flipHorizontal;
|
||||
bool flipVertical;
|
||||
bool big;
|
||||
};
|
||||
|
||||
static void DrawText2D(const char* text, float x, float y, Game::Font_s* font, float xScale, float yScale, float sinAngle, float cosAngle, Game::GfxColor color, int maxLength, int renderFlags, int cursorPos, char cursorLetter, float padding, Game::GfxColor glowForcedColor, int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, Game::Material* fxMaterial, Game::Material* fxMaterialGlow);
|
||||
static int R_TextWidth_Hk(const char* text, int maxChars, Game::Font_s* font);
|
||||
static unsigned int ColorIndex(char index);
|
||||
@ -163,6 +164,10 @@ namespace Components
|
||||
static void StripAllTextIcons(const char* in, char* out, size_t max);
|
||||
static std::string StripAllTextIcons(const std::string& in);
|
||||
|
||||
static bool IsFontIcon(const char*& text, FontIconInfo& fontIcon);
|
||||
static float GetNormalizedFontIconWidth(const FontIconInfo& fontIcon);
|
||||
static float GetFontIconWidth(const FontIconInfo& fontIcon, const Game::Font_s* font, float xScale);
|
||||
|
||||
TextRenderer();
|
||||
|
||||
private:
|
||||
@ -193,8 +198,6 @@ namespace Components
|
||||
static void GetUnpackedColorByNameStub();
|
||||
|
||||
static Game::GfxImage* GetFontIconColorMap(const Game::Material* fontIconMaterial);
|
||||
static bool IsFontIcon(const char*& text, FontIconInfo& fontIcon);
|
||||
static float GetFontIconWidth(const FontIconInfo& fontIcon, const Game::Font_s* font, float xScale);
|
||||
static float DrawFontIcon(const FontIconInfo& fontIcon, float x, float y, float sinAngle, float cosAngle, const Game::Font_s* font, float xScale, float yScale, unsigned color);
|
||||
|
||||
static float GetMonospaceWidth(Game::Font_s* font, int rendererFlags);
|
||||
|
@ -474,6 +474,7 @@ namespace Game
|
||||
clientActive_t* clients = reinterpret_cast<clientActive_t*>(0xB2C698);
|
||||
|
||||
cg_s* cgArray = reinterpret_cast<cg_s*>(0x7F0F78);
|
||||
cgs_t* cgsArray = reinterpret_cast<cgs_t*>(0x7ED3B8);
|
||||
|
||||
PlayerKeyState* playerKeys = reinterpret_cast<PlayerKeyState*>(0xA1B7D0);
|
||||
kbutton_t* playersKb = reinterpret_cast<kbutton_t*>(0xA1A9A8);
|
||||
|
@ -974,6 +974,7 @@ namespace Game
|
||||
extern clientActive_t* clients;
|
||||
|
||||
extern cg_s* cgArray;
|
||||
extern cgs_t* cgsArray;
|
||||
|
||||
extern PlayerKeyState* playerKeys;
|
||||
extern kbutton_t* playersKb;
|
||||
|
@ -6380,7 +6380,10 @@ namespace Game
|
||||
void* nextSnap;
|
||||
char _pad1[0x673DC];
|
||||
int frametime; // + 0x6A754
|
||||
char _pad2[0x960C]; // + 0x6A758
|
||||
int time;
|
||||
int oldTime;
|
||||
int physicalsTime;
|
||||
char _pad2[0x9600]; // + 0x6A758
|
||||
float compassMapWorldSize[2]; // + 0x73D64
|
||||
char _pad3[0x74]; // + 0x73D6C
|
||||
float selectedLocation[2]; // + 0x73DE0
|
||||
@ -6391,6 +6394,8 @@ namespace Game
|
||||
char _pad4[0x89740];
|
||||
};
|
||||
|
||||
static_assert(sizeof(cg_s) == 0xFD540);
|
||||
|
||||
static constexpr auto MAX_GAMEPADS = 1;
|
||||
|
||||
static constexpr auto GPAD_VALUE_MASK = 0xFFFFFFFu;
|
||||
@ -6629,6 +6634,230 @@ namespace Game
|
||||
};
|
||||
#pragma warning(pop)
|
||||
|
||||
enum ShockViewTypes
|
||||
{
|
||||
SHELLSHOCK_VIEWTYPE_BLURRED = 0x0,
|
||||
SHELLSHOCK_VIEWTYPE_FLASHED = 0x1,
|
||||
SHELLSHOCK_VIEWTYPE_NONE = 0x2,
|
||||
};
|
||||
|
||||
struct shellshock_parms_t
|
||||
{
|
||||
struct
|
||||
{
|
||||
int blurredFadeTime;
|
||||
int blurredEffectTime;
|
||||
int flashWhiteFadeTime;
|
||||
int flashShotFadeTime;
|
||||
ShockViewTypes type;
|
||||
} screenBlend;
|
||||
|
||||
struct
|
||||
{
|
||||
int fadeTime;
|
||||
float kickRate;
|
||||
float kickRadius;
|
||||
} view;
|
||||
|
||||
struct
|
||||
{
|
||||
bool affect;
|
||||
char loop[64];
|
||||
char loopSilent[64];
|
||||
char end[64];
|
||||
char endAbort[64];
|
||||
int fadeInTime;
|
||||
int fadeOutTime;
|
||||
float drylevel;
|
||||
float wetlevel;
|
||||
char roomtype[16];
|
||||
float channelvolume[64];
|
||||
int modEndDelay;
|
||||
int loopFadeTime;
|
||||
int loopEndDelay;
|
||||
} sound;
|
||||
|
||||
struct
|
||||
{
|
||||
bool affect;
|
||||
int fadeTime;
|
||||
float mouseSensitivity;
|
||||
float maxPitchSpeed;
|
||||
float maxYawSpeed;
|
||||
} lookControl;
|
||||
|
||||
struct
|
||||
{
|
||||
bool affect;
|
||||
} movement;
|
||||
};
|
||||
|
||||
struct XAnimParent
|
||||
{
|
||||
unsigned short flags;
|
||||
unsigned short children;
|
||||
};
|
||||
|
||||
struct XAnimEntry
|
||||
{
|
||||
unsigned short numAnims;
|
||||
unsigned short parent;
|
||||
|
||||
union
|
||||
{
|
||||
XAnimParts* parts;
|
||||
XAnimParent animParent;
|
||||
};
|
||||
};
|
||||
|
||||
struct XAnim_s
|
||||
{
|
||||
unsigned int size;
|
||||
const char* debugName;
|
||||
const char** debugAnimNames;
|
||||
XAnimEntry entries[1];
|
||||
};
|
||||
|
||||
struct animation_s
|
||||
{
|
||||
char name[64];
|
||||
int initialLerp;
|
||||
float moveSpeed;
|
||||
int duration;
|
||||
int nameHash;
|
||||
int flags;
|
||||
int64_t movetype;
|
||||
int noteType;
|
||||
};
|
||||
|
||||
struct lerpFrame_t
|
||||
{
|
||||
float yawAngle;
|
||||
int yawing;
|
||||
float pitchAngle;
|
||||
int pitching;
|
||||
int animationNumber;
|
||||
animation_s* animation;
|
||||
int animationTime;
|
||||
float oldFramePos[3];
|
||||
float animSpeedScale;
|
||||
int oldFrameSnapshotTime;
|
||||
};
|
||||
|
||||
struct clientControllers_t
|
||||
{
|
||||
float angles[4][3];
|
||||
float tag_origin_angles[3];
|
||||
float tag_origin_offset[3];
|
||||
};
|
||||
|
||||
struct __declspec(align(4)) XAnimTree_s
|
||||
{
|
||||
XAnim_s* anims;
|
||||
int info_usage;
|
||||
volatile int calcRefCount;
|
||||
volatile int modifyRefCount;
|
||||
unsigned __int16 children;
|
||||
};
|
||||
|
||||
enum PlayerDiveState
|
||||
{
|
||||
DIVE_NONE = 0x0,
|
||||
DIVE_FORWARD = 0x1,
|
||||
DIVE_FORWARDLEFT = 0x2,
|
||||
DIVE_LEFT = 0x3,
|
||||
DIVE_BACKLEFT = 0x4,
|
||||
DIVE_BACK = 0x5,
|
||||
DIVE_BACKRIGHT = 0x6,
|
||||
DIVE_RIGHT = 0x7,
|
||||
DIVE_FORWARDRIGHT = 0x8,
|
||||
};
|
||||
|
||||
struct clientInfo_t
|
||||
{
|
||||
int infoValid;
|
||||
int nextValid;
|
||||
int clientNum;
|
||||
char name[16];
|
||||
team_t team;
|
||||
team_t oldteam;
|
||||
int rank;
|
||||
int prestige;
|
||||
unsigned int perks[2];
|
||||
int score;
|
||||
int location;
|
||||
int health;
|
||||
char model[64];
|
||||
char attachModelNames[6][64];
|
||||
char attachTagNames[6][64];
|
||||
unsigned int partBits[6];
|
||||
lerpFrame_t legs;
|
||||
lerpFrame_t torso;
|
||||
float lerpMoveDir;
|
||||
float lerpLean;
|
||||
float playerAngles[3];
|
||||
int legsAnim;
|
||||
int torsoAnim;
|
||||
float fTorsoPitch;
|
||||
float fWaistPitch;
|
||||
int leftHandGun;
|
||||
int dobjDirty;
|
||||
clientControllers_t control;
|
||||
unsigned int clientConditions[18][2];
|
||||
XAnimTree_s* pXAnimTree;
|
||||
int iDObjWeapon;
|
||||
char weaponModel;
|
||||
int stanceTransitionTime;
|
||||
int turnAnimEndTime;
|
||||
char turnAnimType;
|
||||
bool hideWeapon;
|
||||
bool usingKnife;
|
||||
int dualWielding;
|
||||
PlayerDiveState diveState;
|
||||
int riotShieldNext;
|
||||
unsigned int playerCardIcon;
|
||||
unsigned int playerCardTitle;
|
||||
unsigned int playerCardNameplate;
|
||||
};
|
||||
|
||||
struct cgs_t
|
||||
{
|
||||
int viewX;
|
||||
int viewY;
|
||||
int viewWidth;
|
||||
int viewHeight;
|
||||
float viewAspect;
|
||||
int serverCommandSequence;
|
||||
int processedSnapshotNum;
|
||||
int localServer;
|
||||
char gametype[32];
|
||||
char szHostName[256];
|
||||
bool hardcore;
|
||||
int maxclients;
|
||||
int privateClients;
|
||||
char mapname[64];
|
||||
int gameEndTime;
|
||||
int voteTime;
|
||||
int voteYes;
|
||||
int voteNo;
|
||||
char voteString[256];
|
||||
XModel* gameModels[512];
|
||||
FxEffectDef* smokeGrenadeFx;
|
||||
shellshock_parms_t holdBreathParams;
|
||||
char teamChatMsgs[8][160];
|
||||
int teamChatMsgTimes[8];
|
||||
int teamChatPos;
|
||||
int teamLastChatPos;
|
||||
float compassWidth;
|
||||
float compassHeight;
|
||||
float compassY;
|
||||
clientInfo_t corpseinfo[8];
|
||||
bool entUpdateToggleContextKey;
|
||||
XAnim_s* helicopterAnims;
|
||||
};
|
||||
|
||||
static_assert(sizeof(cgs_t) == 0x3BA4);
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#ifndef IDA
|
||||
|
Loading…
Reference in New Issue
Block a user