feat(cursor): add cursor module that stores and updates various cursor images

This commit is contained in:
superp00t 2024-09-06 12:22:01 -04:00
parent 7a2376741d
commit 29bc21a242
4 changed files with 317 additions and 0 deletions

17
src/cursor/CMakeLists.txt Normal file
View File

@ -0,0 +1,17 @@
file(GLOB PRIVATE_SOURCES "*.cpp")
add_library(cursor STATIC
${PRIVATE_SOURCES}
)
target_include_directories(cursor
PRIVATE
${CMAKE_SOURCE_DIR}/src
)
target_link_libraries(cursor
PRIVATE
storm
gx
ui
)

218
src/cursor/Cursor.cpp Normal file
View File

@ -0,0 +1,218 @@
#include "cursor/Cursor.hpp"
#include "ui/FrameScript.hpp"
#include "gx/Texture.hpp"
#include "gx/Device.hpp"
#include <cstdint>
#include <cstring>
#include <storm/String.hpp>
static CURSORMODE s_cursorMode;
static uint32_t s_cursorResetMode;
static CURSORITEMTYPE s_cursorItemType;
static uint32_t s_cursorItemMips;
static uint32_t s_cursorItemMipsHeight;
static uint32_t s_cursorItemMipsWidth;
static int8_t s_cursorFile;
static uint32_t s_cursorItem[CURSOR_IMAGE_SIZE];
static uint32_t s_cursorImages[NUM_CURSOR_MODES][CURSOR_IMAGE_SIZE];
static const char* s_cursorNames[NUM_CURSOR_MODES] = {
nullptr, /* NO_CURSOR */
"Point", /* POINT_CURSOR */
"Cast", /* CAST_CURSOR */
"Buy", /* BUY_CURSOR */
"Attack", /* ATTACK_CURSOR */
"Interact", /* INTERACT_CURSOR */
"Speak", /* SPEAK_CURSOR */
"Inspect", /* INSPECT_CURSOR */
"Pickup", /* PICKUP_CURSOR */
"Taxi", /* TAXI_CURSOR */
"Trainer", /* TRAINER_CURSOR */
"Mine", /* MINE_CURSOR */
"Skin", /* SKIN_CURSOR */
"GatherHerbs", /* GATHER_CURSOR */
"PickLock", /* LOCK_CURSOR */
"Mail", /* MAIL_CURSOR */
"LootAll", /* LOOT_ALL_CURSOR */
"Repair", /* REPAIR_CURSOR */
"RepairNPC", /* REPAIRNPC_CURSOR */
"Item", /* ITEM_CURSOR */
"SkinHorde", /* SKIN_HORDE_CURSOR */
"SkinAlliance", /* SKIN_ALLIANCE_CURSOR */
"Innkeeper", /* INNKEEPER_CURSOR */
"Quest", /* QUEST_CURSOR */
"QuestRepeatable", /* QUEST_REPEATABLE_CURSOR */
"QuestTurnIn", /* QUEST_TURNIN_CURSOR */
"vehichleCursor", /* VEHICLE_CURSOR */
"UnablePoint", /* POINT_ERROR_CURSOR */
"UnableCast", /* CAST_ERROR_CURSOR */
"UnableBuy", /* BUY_ERROR_CURSOR */
"UnableAttack", /* ATTACK_ERROR_CURSOR */
"UnableInteract", /* INTERACT_ERROR_CURSOR */
"UnableSpeak", /* SPEAK_ERROR_CURSOR */
"UnableInspect", /* INSPECT_ERROR_CURSOR */
"UnablePickup", /* PICKUP_ERROR_CURSOR */
"UnableTaxi", /* TAXI_ERROR_CURSOR */
"UnableTrainer", /* TRAINER_ERROR_CURSOR */
"UnableMine", /* MINE_ERROR_CURSOR */
"UnableSkin", /* SKIN_ERROR_CURSOR */
"UnableGatherHerbs", /* GATHER_ERROR_CURSOR */
"UnablePickLock", /* LOCK_ERROR_CURSOR */
"UnableMail", /* MAIL_ERROR_CURSOR */
"UnableLootAll", /* LOOT_ALL_ERROR_CURSOR */
"UnableRepair", /* REPAIR_ERROR_CURSOR */
"UnableRepairNPC", /* REPAIRNPC_ERROR_CURSOR */
"UnableItem", /* ITEM_ERROR_CURSOR */
"UnableSkinHorde", /* SKIN_HORDE_ERROR_CURSOR */
"UnableSkinAlliance", /* SKIN_ALLIANCE_ERROR_CURSOR */
"UnableInnkeeper", /* INNKEEPER_ERROR_CURSOR */
"UnableQuest", /* QUEST_ERROR_CURSOR */
"UnableQuestRepeatable", /* QUEST_REPEATABLE_ERROR_CURSOR */
"UnableQuestTurnIn", /* QUEST_TURNIN_ERROR_CURSOR */
"UnablevehichleCursor" /* VEHICLE_ERROR_CURSOR */
};
int32_t CopyCursorImage(uint32_t* image, MipBits* mipImages, uint32_t width, uint32_t height) {
if (mipImages) {
if (width == 32 && height == 32) {
memcpy(image, mipImages->mip[0], CURSOR_IMAGE_BYTES);
return 1;
}
if (width == 64 && height == 64) {
// Downsample /2
auto end = image + 1024;
auto topRow = mipImages->mip[0];
auto dest = image;
while (dest < end) {
auto bottomRow = topRow + 64;
auto topPixels = topRow;
auto bottomPixels = bottomRow;
// Merge 4 pixels into one
while (bottomPixels < bottomRow) {
*dest++ = ((topPixels[0].b + topPixels[1].b + bottomPixels[0].b + bottomPixels[1].b) / 4) |
(((topPixels[0].g + topPixels[1].g + bottomPixels[0].g + bottomPixels[1].g) / 4) << 8) |
(((topPixels[0].r + topPixels[1].r + bottomPixels[0].r + bottomPixels[1].r) / 4) << 16) |
(((topPixels[0].a + topPixels[1].a + bottomPixels[0].a + bottomPixels[1].a) / 4) << 24);
topPixels += 2;
bottomPixels += 2;
}
// Advance two rows
topRow += 128;
}
return 1;
}
}
// failure
memset(image, 0, CURSOR_IMAGE_BYTES);
return 0;
}
void UpdateCursor() {
auto cursor = g_theGxDevicePtr->CursorLock();
if (s_cursorItemType == CURSOR_EMPTY) {
// update the cursor to the current mode's image
memcpy(cursor, s_cursorImages[s_cursorMode], CURSOR_IMAGE_BYTES);
} else {
if (s_cursorItemMips == 0) {
// merge the icon with the cursor bitmap
// so the pointer in the top left remains
int32_t i = 0;
int32_t j = CURSOR_IMAGE_SIZE/4;
while (j) {
auto pixel1 = s_cursorImages[CURSOR_ITEM][i];
if (!(pixel1 & 0xFF000000)) {
pixel1 = s_cursorItem[i];
}
cursor[i] = pixel1;
auto pixel2 = s_cursorImages[CURSOR_ITEM][i + 1];
if (!(pixel2 & 0xFF000000)) {
pixel2 = s_cursorItem[i + 1];
}
cursor[i + 1] = pixel2;
auto pixel3 = s_cursorImages[CURSOR_ITEM][i + 2];
if (!(pixel3 & 0xFF000000)) {
pixel3 = s_cursorItem[i + 2];
}
cursor[i + 2] = pixel3;
auto pixel4 = s_cursorImages[CURSOR_ITEM][i + 3];
if (!(pixel4 & 0xFF000000)) {
pixel4 = s_cursorItem[i + 3];
}
cursor[i + 3] = pixel4;
j--;
}
} else {
memcpy(cursor, s_cursorImages[CURSOR_ITEM], CURSOR_IMAGE_BYTES);
}
}
g_theGxDevicePtr->CursorUnlock(0, 0);
}
void CursorInitialize() {
s_cursorMode = POINT_CURSOR;
s_cursorResetMode = 1;
s_cursorItemType = CURSOR_EMPTY;
s_cursorItemMips = 0;
s_cursorItemMipsHeight = 0;
s_cursorItemMipsWidth = 0;
char cursorFilename[STORM_MAX_PATH];
for (uint32_t mode = 0; mode < NUM_CURSOR_MODES; mode++) {
auto cursorName = s_cursorNames[mode];
auto cursorImage = s_cursorImages[mode];
if (cursorName) {
SStrPrintf(cursorFilename, STORM_MAX_PATH, "Interface\\Cursor\\%s.blp", cursorName);
uint32_t width;
uint32_t height;
PIXEL_FORMAT dataFormat = PIXEL_ARGB8888;
auto mipImages = TextureLoadImage(cursorFilename, &width, &height, &dataFormat, nullptr, nullptr, nullptr, 0);
if (mipImages) {
CopyCursorImage(cursorImage, mipImages, width, height);
TextureFreeMippedImg(mipImages, dataFormat, width, height);
} else {
memset(cursorImage, 0, CURSOR_IMAGE_BYTES);
}
} else {
memset(cursorImage, 0, CURSOR_IMAGE_BYTES);
}
}
s_cursorFile = 0;
UpdateCursor();
}
void CursorSetMode(CURSORMODE mode) {
if (mode != NUM_CURSOR_MODES && s_cursorMode != mode) {
s_cursorMode = mode;
UpdateCursor();
FrameScript_SignalEvent(275, nullptr);
}
}

13
src/cursor/Cursor.hpp Normal file
View File

@ -0,0 +1,13 @@
#ifndef CURSOR_CURSOR_HPP
#define CURSOR_CURSOR_HPP
#define CURSOR_IMAGE_SIZE (32 * 32)
#define CURSOR_IMAGE_BYTES (CURSOR_IMAGE_SIZE * 4)
#include "cursor/Types.hpp"
void CursorInitialize();
void CursorSetMode(CURSORMODE mode);
#endif

69
src/cursor/Types.hpp Normal file
View File

@ -0,0 +1,69 @@
#ifndef CURSOR_TYPES_HPP
#define CURSOR_TYPES_HPP
enum CURSORITEMTYPE {
CURSOR_EMPTY = 0,
CURSOR_ITEM = 1,
CURSOR_MONEY = 2,
CURSOR_SPELL = 3,
NUM_CURSOR_ITEM_TYPES = 4
};
enum CURSORMODE {
NO_CURSOR = 0,
POINT_CURSOR = 1,
CAST_CURSOR = 2,
BUY_CURSOR = 3,
ATTACK_CURSOR = 4,
INTERACT_CURSOR = 5,
SPEAK_CURSOR = 6,
INSPECT_CURSOR = 7,
PICKUP_CURSOR = 8,
TAXI_CURSOR = 9,
TRAINER_CURSOR = 10,
MINE_CURSOR = 11,
SKIN_CURSOR = 12,
GATHER_CURSOR = 13,
LOCK_CURSOR = 14,
MAIL_CURSOR = 15,
LOOT_ALL_CURSOR = 16,
REPAIR_CURSOR = 17,
REPAIRNPC_CURSOR = 18,
ITEM_CURSOR = 19,
SKIN_HORDE_CURSOR = 20,
SKIN_ALLIANCE_CURSOR = 21,
INNKEEPER_CURSOR = 22,
QUEST_CURSOR = 23,
QUEST_REPEATABLE_CURSOR = 24,
QUEST_TURNIN_CURSOR = 25,
VEHICLE_CURSOR = 26,
POINT_ERROR_CURSOR = 27,
CAST_ERROR_CURSOR = 28,
BUY_ERROR_CURSOR = 29,
ATTACK_ERROR_CURSOR = 30,
INTERACT_ERROR_CURSOR = 31,
SPEAK_ERROR_CURSOR = 32,
INSPECT_ERROR_CURSOR = 33,
PICKUP_ERROR_CURSOR = 34,
TAXI_ERROR_CURSOR = 35,
TRAINER_ERROR_CURSOR = 36,
MINE_ERROR_CURSOR = 37,
SKIN_ERROR_CURSOR = 38,
GATHER_ERROR_CURSOR = 39,
LOCK_ERROR_CURSOR = 40,
MAIL_ERROR_CURSOR = 41,
LOOT_ALL_ERROR_CURSOR = 42,
REPAIR_ERROR_CURSOR = 43,
REPAIRNPC_ERROR_CURSOR = 44,
ITEM_ERROR_CURSOR = 45,
SKIN_HORDE_ERROR_CURSOR = 46,
SKIN_ALLIANCE_ERROR_CURSOR = 47,
INNKEEPER_ERROR_CURSOR = 48,
QUEST_ERROR_CURSOR = 49,
QUEST_REPEATABLE_ERROR_CURSOR = 50,
QUEST_TURNIN_ERROR_CURSOR = 51,
VEHICLE_ERROR_CURSOR = 52,
NUM_CURSOR_MODES = 53
};
#endif