fallout2-ce/src/text_object.c

451 lines
13 KiB
C
Raw Normal View History

2022-05-19 01:51:26 -07:00
#include "text_object.h"
#include "core.h"
#include "debug.h"
#include "draw.h"
#include "game_config.h"
#include "memory.h"
#include "object.h"
#include "text_font.h"
#include "tile.h"
#include "word_wrap.h"
static_assert(sizeof(TextObject) == 48, "wrong size");
// 0x51D944
int gTextObjectsCount = 0;
// 0x51D948
unsigned int gTextObjectsBaseDelay = 3500;
// 0x51D94C
unsigned int gTextObjectsLineDelay = 1399;
// 0x6681C0
TextObject* gTextObjects[TEXT_OBJECTS_MAX_COUNT];
// 0x668210
int gTextObjectsWindowWidth;
// 0x668214
int gTextObjectsWindowHeight;
// 0x668218
unsigned char* gTextObjectsWindowBuffer;
// 0x66821C
bool gTextObjectsEnabled;
// 0x668220
bool gTextObjectsInitialized;
// 0x4B0130
int textObjectsInit(unsigned char* windowBuffer, int width, int height)
{
if (gTextObjectsInitialized) {
return -1;
}
gTextObjectsWindowBuffer = windowBuffer;
gTextObjectsWindowWidth = width;
gTextObjectsWindowHeight = height;
gTextObjectsCount = 0;
tickersAdd(textObjectsTicker);
double textBaseDelay;
if (!configGetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, &textBaseDelay)) {
textBaseDelay = 3.5;
}
double textLineDelay;
if (!configGetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, &textLineDelay)) {
textLineDelay = 1.399993896484375;
}
gTextObjectsBaseDelay = (unsigned int)(textBaseDelay * 1000.0);
gTextObjectsLineDelay = (unsigned int)(textLineDelay * 1000.0);
gTextObjectsEnabled = true;
gTextObjectsInitialized = true;
return 0;
}
// 0x4B021C
int textObjectsReset()
{
if (!gTextObjectsInitialized) {
return -1;
}
for (int index = 0; index < gTextObjectsCount; index++) {
internal_free(gTextObjects[index]->data);
internal_free(gTextObjects[index]);
}
gTextObjectsCount = 0;
tickersAdd(textObjectsTicker);
return 0;
}
// 0x4B0280
void textObjectsFree()
{
if (gTextObjectsInitialized) {
textObjectsReset();
tickersRemove(textObjectsTicker);
gTextObjectsInitialized = false;
}
}
// 0x4B02A4
void textObjectsDisable()
{
gTextObjectsEnabled = false;
}
// 0x4B02B0
void textObjectsEnable()
{
gTextObjectsEnabled = true;
}
// 0x4B02C4
void textObjectsSetBaseDelay(double value)
{
if (value < 1.0) {
value = 1.0;
}
gTextObjectsBaseDelay = (int)(value * 1000.0);
}
// 0x4B031C
void textObjectsSetLineDelay(double value)
{
if (value < 0.0) {
value = 0.0;
}
gTextObjectsLineDelay = (int)(value * 1000.0);
}
// text_object_create
// 0x4B036C
int textObjectAdd(Object* object, char* string, int font, int color, int a5, Rect* rect)
{
if (!gTextObjectsInitialized) {
return -1;
}
if (gTextObjectsCount >= TEXT_OBJECTS_MAX_COUNT - 1) {
return -1;
}
if (string == NULL) {
return -1;
}
if (*string == '\0') {
return -1;
}
TextObject* textObject = internal_malloc(sizeof(*textObject));
if (textObject == NULL) {
return -1;
}
memset(textObject, 0, sizeof(*textObject));
int oldFont = fontGetCurrent();
fontSetCurrent(font);
short beginnings[WORD_WRAP_MAX_COUNT];
short count;
if (wordWrap(string, 200, beginnings, &count) != 0) {
fontSetCurrent(oldFont);
return -1;
}
textObject->linesCount = count - 1;
if (textObject->linesCount < 1) {
debugPrint("**Error in text_object_create()\n");
}
textObject->width = 0;
for (int index = 0; index < textObject->linesCount; index++) {
char* ending = string + beginnings[index + 1];
char* beginning = string + beginnings[index];
if (ending[-1] == ' ') {
--ending;
}
char c = *ending;
*ending = '\0';
// NOTE: Calls [fontGetStringWidth] twice, probably result of using min/max macro
int width = fontGetStringWidth(beginning);
if (width >= textObject->width) {
textObject->width = width;
}
*ending = c;
}
textObject->height = (fontGetLineHeight() + 1) * textObject->linesCount;
if (a5 != -1) {
textObject->width += 2;
textObject->height += 2;
}
int size = textObject->width * textObject->height;
textObject->data = internal_malloc(size);
if (textObject->data == NULL) {
fontSetCurrent(oldFont);
return -1;
}
memset(textObject->data, 0, size);
unsigned char* dest = textObject->data;
int skip = textObject->width * (fontGetLineHeight() + 1);
if (a5 != -1) {
dest += textObject->width;
}
for (int index = 0; index < textObject->linesCount; index++) {
char* beginning = string + beginnings[index];
char* ending = string + beginnings[index + 1];
if (ending[-1] == ' ') {
--ending;
}
char c = *ending;
*ending = '\0';
int width = fontGetStringWidth(beginning);
fontDrawText(dest + (textObject->width - width) / 2, beginning, textObject->width, textObject->width, color);
*ending = c;
dest += skip;
}
if (a5 != -1) {
bufferOutline(textObject->data, textObject->width, textObject->height, textObject->width, a5);
}
if (object != NULL) {
textObject->tile = object->tile;
} else {
textObject->flags |= TEXT_OBJECT_UNBOUNDED;
textObject->tile = gCenterTile;
}
textObjectFindPlacement(textObject);
if (rect != NULL) {
rect->left = textObject->x;
rect->top = textObject->y;
rect->right = textObject->x + textObject->width - 1;
rect->bottom = textObject->y + textObject->height - 1;
}
textObjectsRemoveByOwner(object);
textObject->owner = object;
textObject->time = _get_bk_time();
gTextObjects[gTextObjectsCount] = textObject;
gTextObjectsCount++;
fontSetCurrent(oldFont);
return 0;
}
// 0x4B06E8
void textObjectsRenderInRect(Rect* rect)
{
if (!gTextObjectsInitialized) {
return;
}
for (int index = 0; index < gTextObjectsCount; index++) {
TextObject* textObject = gTextObjects[index];
tileToScreenXY(textObject->tile, &(textObject->x), &(textObject->y), gElevation);
textObject->x += textObject->sx;
textObject->y += textObject->sy;
Rect textObjectRect;
textObjectRect.left = textObject->x;
textObjectRect.top = textObject->y;
textObjectRect.right = textObject->width + textObject->x - 1;
textObjectRect.bottom = textObject->height + textObject->y - 1;
if (rectIntersection(&textObjectRect, rect, &textObjectRect) == 0) {
blitBufferToBufferTrans(textObject->data + textObject->width * (textObjectRect.top - textObject->y) + (textObjectRect.left - textObject->x),
textObjectRect.right - textObjectRect.left + 1,
textObjectRect.bottom - textObjectRect.top + 1,
textObject->width,
gTextObjectsWindowBuffer + gTextObjectsWindowWidth * textObjectRect.top + textObjectRect.left,
gTextObjectsWindowWidth);
}
}
}
// 0x4B07F0
int textObjectsGetCount()
{
return gTextObjectsCount;
}
// 0x4B07F8
void textObjectsTicker()
{
if (!gTextObjectsEnabled) {
return;
}
bool textObjectsRemoved = false;
Rect dirtyRect;
for (int index = 0; index < gTextObjectsCount; index++) {
TextObject* textObject = gTextObjects[index];
unsigned int delay = gTextObjectsLineDelay * textObject->linesCount + gTextObjectsBaseDelay;
if ((textObject->flags & TEXT_OBJECT_MARKED_FOR_REMOVAL) != 0 || (getTicksBetween(_get_bk_time(), textObject->time) > delay)) {
tileToScreenXY(textObject->tile, &(textObject->x), &(textObject->y), gElevation);
textObject->x += textObject->sx;
textObject->y += textObject->sy;
Rect textObjectRect;
textObjectRect.left = textObject->x;
textObjectRect.top = textObject->y;
textObjectRect.right = textObject->width + textObject->x - 1;
textObjectRect.bottom = textObject->height + textObject->y - 1;
if (textObjectsRemoved) {
rectUnion(&dirtyRect, &textObjectRect, &dirtyRect);
} else {
rectCopy(&dirtyRect, &textObjectRect);
textObjectsRemoved = true;
}
internal_free(textObject->data);
internal_free(textObject);
memmove(&(gTextObjects[index]), &(gTextObjects[index + 1]), sizeof(*gTextObjects) * (gTextObjectsCount - index - 1));
gTextObjectsCount--;
index--;
}
}
if (textObjectsRemoved) {
tileWindowRefreshRect(&dirtyRect, gElevation);
}
}
// Finds best position for placing text object.
//
// 0x4B0954
void textObjectFindPlacement(TextObject* textObject)
{
int tileScreenX;
int tileScreenY;
tileToScreenXY(textObject->tile, &tileScreenX, &tileScreenY, gElevation);
textObject->x = tileScreenX + 16 - textObject->width / 2;
textObject->y = tileScreenY;
if ((textObject->flags & TEXT_OBJECT_UNBOUNDED) == 0) {
textObject->y -= textObject->height + 60;
}
if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < gTextObjectsWindowWidth)
&& (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) {
textObject->sx = textObject->x - tileScreenX;
textObject->sy = textObject->y - tileScreenY;
return;
}
textObject->x -= textObject->width / 2;
if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < gTextObjectsWindowWidth)
&& (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) {
textObject->sx = textObject->x - tileScreenX;
textObject->sy = textObject->y - tileScreenY;
return;
}
textObject->x += textObject->width;
if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < gTextObjectsWindowWidth)
&& (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) {
textObject->sx = textObject->x - tileScreenX;
textObject->sy = textObject->y - tileScreenY;
return;
}
textObject->x = tileScreenX - 16 - textObject->width;
textObject->y = tileScreenY - 16 - textObject->height;
if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < gTextObjectsWindowWidth)
&& (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) {
textObject->sx = textObject->x - tileScreenX;
textObject->sy = textObject->y - tileScreenY;
return;
}
textObject->x += textObject->width + 64;
if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < gTextObjectsWindowWidth)
&& (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) {
textObject->sx = textObject->x - tileScreenX;
textObject->sy = textObject->y - tileScreenY;
return;
}
textObject->x = tileScreenX + 16 - textObject->width / 2;
textObject->y = tileScreenY;
if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < gTextObjectsWindowWidth)
&& (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) {
textObject->sx = textObject->x - tileScreenX;
textObject->sy = textObject->y - tileScreenY;
return;
}
textObject->x -= textObject->width / 2;
if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < gTextObjectsWindowWidth)
&& (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) {
textObject->sx = textObject->x - tileScreenX;
textObject->sy = textObject->y - tileScreenY;
return;
}
textObject->x += textObject->width;
if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < gTextObjectsWindowWidth)
&& (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) {
textObject->sx = textObject->x - tileScreenX;
textObject->sy = textObject->y - tileScreenY;
return;
}
textObject->x = tileScreenX + 16 - textObject->width / 2;
textObject->y = tileScreenY - (textObject->height + 60);
textObject->sx = textObject->x - tileScreenX;
textObject->sy = textObject->y - tileScreenY;
}
// Marks text objects attached to [object] for removal.
//
// 0x4B0C00
void textObjectsRemoveByOwner(Object* object)
{
for (int index = 0; index < gTextObjectsCount; index++) {
if (gTextObjects[index]->owner == object) {
gTextObjects[index]->flags |= TEXT_OBJECT_MARKED_FOR_REMOVAL;
}
}
}