mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53ebe3740c |
@@ -1,189 +0,0 @@
|
||||
#include "constants.h"
|
||||
#include "headers/define_extra.h" // For sfall functions if not in constants
|
||||
|
||||
procedure start;
|
||||
procedure description_proc; // For visual checks during game
|
||||
|
||||
variable new_tag1_id = -1;
|
||||
variable new_tag2_id = -1;
|
||||
variable max_tags_test_id = -1;
|
||||
|
||||
procedure start begin
|
||||
// Test Predefined Tags
|
||||
debug_msg("Testing Predefined Interface Tags...");
|
||||
|
||||
// SNEAK (0)
|
||||
debug_msg("Activating SNEAK (0)...");
|
||||
show_iface_tag(0);
|
||||
if (is_iface_tag_active(0)) then
|
||||
debug_msg("SNEAK is active. (Visual check needed)");
|
||||
else
|
||||
debug_msg("ERROR: SNEAK failed to activate or is_iface_tag_active(0) failed.");
|
||||
hide_iface_tag(0);
|
||||
if (not is_iface_tag_active(0)) then
|
||||
debug_msg("SNEAK is inactive.");
|
||||
else
|
||||
debug_msg("ERROR: SNEAK failed to deactivate or is_iface_tag_active(0) failed after hide.");
|
||||
|
||||
// POISONED (1) - Test requires making the player poisoned by other means to truly verify show/hide effect on game state
|
||||
// For now, just test is_iface_tag_active if it reflects direct calls or underlying state.
|
||||
// The opcodes show_iface_tag/hide_iface_tag are not meant to directly poison/cure the player for tags 1 and 2.
|
||||
// They are for custom tags or the specific PC flags (0,3,4). is_iface_tag_active *should* work for 1 and 2.
|
||||
debug_msg("Checking POISONED (1) (depends on game state): " + is_iface_tag_active(1));
|
||||
// Similar for RADIATED (2)
|
||||
debug_msg("Checking RADIATED (2) (depends on game state): " + is_iface_tag_active(2));
|
||||
|
||||
|
||||
// LEVEL (3)
|
||||
debug_msg("Activating LEVEL (3)...");
|
||||
show_iface_tag(3);
|
||||
if (is_iface_tag_active(3)) then
|
||||
debug_msg("LEVEL is active. (Visual check needed)");
|
||||
else
|
||||
debug_msg("ERROR: LEVEL failed to activate or is_iface_tag_active(3) failed.");
|
||||
hide_iface_tag(3);
|
||||
if (not is_iface_tag_active(3)) then
|
||||
debug_msg("LEVEL is inactive.");
|
||||
else
|
||||
debug_msg("ERROR: LEVEL failed to deactivate or is_iface_tag_active(3) failed after hide.");
|
||||
|
||||
// ADDICT (4)
|
||||
debug_msg("Activating ADDICT (4)...");
|
||||
show_iface_tag(4);
|
||||
if (is_iface_tag_active(4)) then
|
||||
debug_msg("ADDICT is active. (Visual check needed)");
|
||||
else
|
||||
debug_msg("ERROR: ADDICT failed to activate or is_iface_tag_active(4) failed.");
|
||||
hide_iface_tag(4);
|
||||
if (not is_iface_tag_active(4)) then
|
||||
debug_msg("ADDICT is inactive.");
|
||||
else
|
||||
debug_msg("ERROR: ADDICT failed to deactivate or is_iface_tag_active(4) failed after hide.");
|
||||
|
||||
debug_msg("Predefined tag tests complete.");
|
||||
debug_msg("---");
|
||||
|
||||
// Test Custom Tags
|
||||
debug_msg("Testing Custom Interface Tags...");
|
||||
|
||||
// Add first custom tag
|
||||
new_tag1_id = add_iface_tag;
|
||||
if (new_tag1_id != -1) then
|
||||
debug_msg("Added custom tag 1 with ID: " + new_tag1_id + " (expected 5 if first)");
|
||||
else
|
||||
debug_msg("ERROR: Failed to add custom tag 1.");
|
||||
return; // Stop if first add fails
|
||||
|
||||
// Show it
|
||||
show_iface_tag(new_tag1_id);
|
||||
if (is_iface_tag_active(new_tag1_id)) then
|
||||
debug_msg("Custom tag 1 is active. (Visual check: should show 'NEW' in green)");
|
||||
else
|
||||
debug_msg("ERROR: Custom tag 1 failed to activate or is_iface_tag_active failed.");
|
||||
|
||||
// Set its text and color
|
||||
set_iface_tag_text(new_tag1_id, "HELLO TAG1", 2); // Color 2 (White from issue description's reference)
|
||||
debug_msg("Set text for custom tag 1 to 'HELLO TAG1' (color white). (Visual check needed)");
|
||||
// Verify text length truncation (19 chars)
|
||||
set_iface_tag_text(new_tag1_id, "ThisIsAVeryLongTextStringIndeed", 3); // Color 3 (Yellow)
|
||||
debug_msg("Set text for custom tag 1 to 'ThisIsAVeryLongTextStringIndeed' (color yellow) - should be truncated. (Visual check needed)");
|
||||
|
||||
|
||||
// Add second custom tag
|
||||
new_tag2_id = add_iface_tag;
|
||||
if (new_tag2_id != -1) then
|
||||
debug_msg("Added custom tag 2 with ID: " + new_tag2_id + " (expected 6 if second)");
|
||||
else
|
||||
debug_msg("ERROR: Failed to add custom tag 2.");
|
||||
// Continue to test tag1 hide
|
||||
|
||||
if (new_tag2_id != -1) then
|
||||
show_iface_tag(new_tag2_id);
|
||||
set_iface_tag_text(new_tag2_id, "AWESOME", 6); // Color 6 (GoodColor/Green)
|
||||
debug_msg("Custom tag 2 ('AWESOME', green) should be active. (Visual check needed)");
|
||||
end
|
||||
|
||||
// Hide first custom tag
|
||||
hide_iface_tag(new_tag1_id);
|
||||
if (not is_iface_tag_active(new_tag1_id)) then
|
||||
debug_msg("Custom tag 1 is now inactive.");
|
||||
else
|
||||
debug_msg("ERROR: Custom tag 1 failed to deactivate.");
|
||||
|
||||
if (new_tag2_id != -1 and is_iface_tag_active(new_tag2_id)) then
|
||||
debug_msg("Custom tag 2 should still be active. (Visual check needed)");
|
||||
else if (new_tag2_id != -1) then
|
||||
debug_msg("ERROR: Custom tag 2 became inactive or failed check.");
|
||||
end
|
||||
|
||||
// Test max tags
|
||||
debug_msg("Attempting to add maximum custom tags (up to 126)...");
|
||||
variable i;
|
||||
variable current_tags = 2; // Assuming new_tag1_id and new_tag2_id were successful (IDs 5 and 6)
|
||||
if (new_tag1_id == -1) then current_tags = 0;
|
||||
if (new_tag1_id != -1 and new_tag2_id == -1) then current_tags = 1;
|
||||
|
||||
|
||||
for (i = current_tags; i < 126; i++) begin
|
||||
max_tags_test_id = add_iface_tag;
|
||||
if (max_tags_test_id == -1) then begin
|
||||
debug_msg("ERROR: Failed to add tag at iteration " + i + ". Max tags might be less than 126 or error in add_iface_tag.");
|
||||
break;
|
||||
end
|
||||
// Minimal show/set_text to ensure they are processed
|
||||
show_iface_tag(max_tags_test_id);
|
||||
set_iface_tag_text(max_tags_test_id, "Tag " + max_tags_test_id, 0); // Default color
|
||||
end
|
||||
if (i == 126) then
|
||||
debug_msg("Successfully added up to 126 custom tags (IDs 5 through 130). Last ID: " + max_tags_test_id);
|
||||
else
|
||||
debug_msg("Stopped adding tags at count " + i + ". Last successful ID: " + max_tags_test_id);
|
||||
end
|
||||
|
||||
// Test adding one more tag (should fail if 126 was the limit)
|
||||
max_tags_test_id = add_iface_tag;
|
||||
if (max_tags_test_id == -1) then
|
||||
debug_msg("Correctly failed to add tag beyond limit (127th attempt).");
|
||||
else
|
||||
debug_msg("ERROR: Added tag beyond 126 limit! ID: " + max_tags_test_id);
|
||||
end
|
||||
|
||||
// Test invalid IDs
|
||||
debug_msg("Testing invalid tag IDs...");
|
||||
show_iface_tag(-1); // Invalid
|
||||
show_iface_tag(1000); // Likely invalid (beyond max added)
|
||||
hide_iface_tag(-2);
|
||||
hide_iface_tag(1001);
|
||||
debug_msg("is_iface_tag_active(-3): " + is_iface_tag_active(-3));
|
||||
debug_msg("is_iface_tag_active(1002): " + is_iface_tag_active(1002));
|
||||
set_iface_tag_text(-4, "INVALID", 0);
|
||||
set_iface_tag_text(1003, "INVALID", 0);
|
||||
debug_msg("Invalid ID tests complete (check console for debug prints from C++).");
|
||||
|
||||
debug_msg("---");
|
||||
debug_msg("Interface Tag testing complete. Check console and game screen.");
|
||||
debug_msg("Will keep some tags active for 10 seconds for visual check then clear...");
|
||||
|
||||
// Keep some tags visible for a moment if run in game
|
||||
// This part is for easier visual checking if you load a game with this script.
|
||||
// For automated testing, the debug_msg output is key.
|
||||
if (game_loaded) then begin
|
||||
if (new_tag1_id != -1) then hide_iface_tag(new_tag1_id); // ensure it's hidden from previous test
|
||||
if (new_tag2_id != -1) then show_iface_tag(new_tag2_id); // ensure tag2 is visible
|
||||
set_iface_tag_text(new_tag2_id, "Testing Done!", 4); // Peanut butter color
|
||||
game_time_advance(game_ticks(10)); // Wait 10 seconds
|
||||
if (new_tag2_id != -1) then hide_iface_tag(new_tag2_id);
|
||||
debug_msg("Visual check period ended.");
|
||||
end
|
||||
end
|
||||
|
||||
// This can be used with `debug_map_scripts` or similar to run on map enter
|
||||
// procedure map_enter_p_proc begin call start; end
|
||||
// procedure map_update_p_proc begin call start; end // If you want it to run repeatedly (not recommended for this test script)
|
||||
|
||||
// For `description_proc` usage with `debug(F5)`:
|
||||
// Create a global script that calls `start` from `description_proc`
|
||||
// Example:
|
||||
// procedure description_proc begin
|
||||
// if (game_loaded) then call start;
|
||||
// end
|
||||
@@ -0,0 +1,79 @@
|
||||
#include "test_utils.h"
|
||||
|
||||
procedure start;
|
||||
|
||||
variable x, y, areaID;
|
||||
variable name, title;
|
||||
|
||||
procedure start begin
|
||||
// Tests for set_terrain_name and get_terrain_name
|
||||
// Valid coordinates
|
||||
x := 10;
|
||||
y := 20;
|
||||
name := "MyCustomTerrain";
|
||||
sfall_func3("set_terrain_name", x, y, name);
|
||||
debug_assert_string_equal(sfall_func2("get_terrain_name", x, y), name, "get_terrain_name valid coords");
|
||||
|
||||
// Test with different valid coordinates and name
|
||||
x := 0;
|
||||
y := 0;
|
||||
name := "AnotherTerrain";
|
||||
sfall_func3("set_terrain_name", x, y, name);
|
||||
debug_assert_string_equal(sfall_func2("get_terrain_name", x, y), name, "get_terrain_name valid coords 2");
|
||||
|
||||
// Test overwriting a terrain name
|
||||
name := "OverwrittenTerrain";
|
||||
sfall_func3("set_terrain_name", x, y, name);
|
||||
debug_assert_string_equal(sfall_func2("get_terrain_name", x, y), name, "get_terrain_name overwrite");
|
||||
|
||||
// Test get_terrain_name for coordinates not set - should rely on fallback in C++
|
||||
// This requires knowing what the C++ fallback returns or being able to mock it.
|
||||
// For now, we'll just check it doesn't crash and returns something.
|
||||
// A more robust test would involve setting up the game state for a known default terrain.
|
||||
debug_print("Terrain at (1000,1000): " + sfall_func2("get_terrain_name", 1000, 1000));
|
||||
|
||||
|
||||
// Test get_terrain_name without arguments (current player position)
|
||||
// This also relies on game state. We'll check it returns something.
|
||||
debug_print("Current terrain name: " + sfall_func0("get_terrain_name"));
|
||||
|
||||
|
||||
// Tests for set_town_title
|
||||
// Valid area ID
|
||||
areaID := 1; // Assuming Arroyo is Area 1 from city.txt
|
||||
title := "Arroyo Title";
|
||||
sfall_func2("set_town_title", areaID, title);
|
||||
// There's no direct sfall_func to get_town_title for assertion here.
|
||||
// This would typically be verified by observing game behavior (hovering over town).
|
||||
// For now, we ensure it doesn't crash.
|
||||
debug_print("Set town title for Area " + areaID + " to: " + title);
|
||||
|
||||
// Test clearing a town title
|
||||
title := ""; // Or pass a null equivalent if the C++ side handles it
|
||||
sfall_func2("set_town_title", areaID, title);
|
||||
debug_print("Cleared town title for Area " + areaID);
|
||||
|
||||
// Test with another area ID
|
||||
areaID := 2; // Assuming The Den
|
||||
title := "The Den's Custom Title";
|
||||
sfall_func2("set_town_title", areaID, title);
|
||||
debug_print("Set town title for Area " + areaID + " to: " + title);
|
||||
|
||||
|
||||
// Invalid coordinate tests for set_terrain_name (should trigger fatal error in C++)
|
||||
// These cannot be directly asserted in SSL if they cause a fatal error.
|
||||
// The C++ side should handle this. If it returned an error code, we could check that.
|
||||
// sfall_func3("set_terrain_name", -1, 0, "InvalidX");
|
||||
// sfall_func3("set_terrain_name", 0, -1, "InvalidY");
|
||||
|
||||
// Invalid coordinate tests for get_terrain_name (should return "Error" from C++)
|
||||
debug_assert_string_equal(sfall_func2("get_terrain_name", -1, 0), "Error", "get_terrain_name invalid X");
|
||||
debug_assert_string_equal(sfall_func2("get_terrain_name", 0, -1), "Error", "get_terrain_name invalid Y");
|
||||
|
||||
|
||||
// Invalid area ID for set_town_title (should trigger fatal error in C++)
|
||||
// sfall_func2("set_town_title", -1, "InvalidArea");
|
||||
|
||||
|
||||
debug_print("Worldmap tests completed.");
|
||||
end
|
||||
+8
-175
@@ -258,9 +258,6 @@ static unsigned char* gInterfaceWindowBuffer;
|
||||
// 0x59D40C
|
||||
static unsigned char gInterfaceActionPointsBarBackground[90 * 5];
|
||||
|
||||
CustomIndicatorBox gCustomIndicatorBoxes[MAX_CUSTOM_INDICATOR_BOXES];
|
||||
static int gActiveCustomTagsCount = 0;
|
||||
|
||||
// Should the game window stretch all the way to the bottom or sit at the top of the interface bar (default)
|
||||
bool gInterfaceBarMode = false;
|
||||
|
||||
@@ -584,13 +581,6 @@ int interfaceInit()
|
||||
// SFALL
|
||||
sidePanelsInit();
|
||||
|
||||
for (int i = 0; i < MAX_CUSTOM_INDICATOR_BOXES; ++i) {
|
||||
gCustomIndicatorBoxes[i].isActive = false;
|
||||
gCustomIndicatorBoxes[i].text[0] = '\0';
|
||||
gCustomIndicatorBoxes[i].color = 0;
|
||||
}
|
||||
gActiveCustomTagsCount = 0;
|
||||
|
||||
gInterfaceBarEnabled = true;
|
||||
gInterfaceBarInitialized = false;
|
||||
gInterfaceBarHidden = true;
|
||||
@@ -707,10 +697,6 @@ void interfaceFree()
|
||||
customInterfaceBarExit();
|
||||
|
||||
interfaceBarFree();
|
||||
// TODO: Consider if a dedicated function to free custom tag resources is needed.
|
||||
// For now, gCustomIndicatorBoxes is a global static array, so no dynamic memory to free.
|
||||
// Resetting active tags count.
|
||||
gActiveCustomTagsCount = 0;
|
||||
}
|
||||
|
||||
// 0x45E860
|
||||
@@ -2376,64 +2362,6 @@ int indicatorBarRefresh()
|
||||
windowRefresh(gIndicatorBarWindow);
|
||||
}
|
||||
|
||||
// START Custom Tag Handling in indicatorBarRefresh
|
||||
int customTagStartSlot = count; // Where to start adding custom tags in gIndicatorSlots
|
||||
for (int i = 0; i < gActiveCustomTagsCount && customTagStartSlot < INDICATOR_SLOTS_COUNT; ++i) {
|
||||
if (gCustomIndicatorBoxes[i].isActive) {
|
||||
// Using a convention: custom tag IDs are INDICATOR_COUNT + i
|
||||
gIndicatorSlots[customTagStartSlot++] = INDICATOR_COUNT + i;
|
||||
}
|
||||
}
|
||||
count = customTagStartSlot; // Update total count of displayed indicators
|
||||
|
||||
if (count > 1 && (gIndicatorSlots[0] < INDICATOR_COUNT || (count > 0 && gIndicatorSlots[0] >= INDICATOR_COUNT && gIndicatorSlots[1] < INDICATOR_COUNT))) { // Only sort if there are standard indicators or a mix
|
||||
// qsort might behave unexpectedly if all elements are custom tags due to how IDs are structured.
|
||||
// Custom tags are already ordered by their addition.
|
||||
// We only need to sort the standard part, or if custom tags are mixed in a way that needs sorting relative to standard ones.
|
||||
// For simplicity, if there are any standard tags, sort the whole array up to the original count.
|
||||
// Custom tags will be appended after this sorted list of standard tags.
|
||||
// This logic might need refinement if standard and custom tags need to interleave based on some criteria.
|
||||
// For now, custom tags are appended after standard ones.
|
||||
int standardIndicatorsCount = 0;
|
||||
for(int i=0; i < count; ++i) {
|
||||
if(gIndicatorSlots[i] < INDICATOR_COUNT) standardIndicatorsCount++;
|
||||
}
|
||||
if(standardIndicatorsCount > 1) {
|
||||
qsort(gIndicatorSlots, standardIndicatorsCount, sizeof(*gIndicatorSlots), indicatorBoxCompareByPosition);
|
||||
}
|
||||
// After sorting standard indicators, append custom ones.
|
||||
// This re-iterates the custom tag addition logic, but ensures they come after sorted standard tags.
|
||||
customTagStartSlot = standardIndicatorsCount;
|
||||
for (int i = 0; i < gActiveCustomTagsCount && customTagStartSlot < INDICATOR_SLOTS_COUNT; ++i) {
|
||||
if (gCustomIndicatorBoxes[i].isActive) {
|
||||
gIndicatorSlots[customTagStartSlot++] = INDICATOR_COUNT + i;
|
||||
}
|
||||
}
|
||||
count = customTagStartSlot;
|
||||
}
|
||||
|
||||
|
||||
if (gIndicatorBarWindow != -1 && count == 0) { // If no indicators (standard or custom), destroy window.
|
||||
windowDestroy(gIndicatorBarWindow);
|
||||
gIndicatorBarWindow = -1;
|
||||
} else if (count > 0) {
|
||||
if (gIndicatorBarWindow == -1) { // Create window if it doesn't exist and there are indicators
|
||||
Rect interfaceBarWindowRect;
|
||||
windowGetRect(gInterfaceBarWindow, &interfaceBarWindowRect);
|
||||
gIndicatorBarWindow = windowCreate(interfaceBarWindowRect.left,
|
||||
screenGetHeight() - INTERFACE_BAR_HEIGHT - INDICATOR_BOX_HEIGHT,
|
||||
(INDICATOR_BOX_WIDTH - INDICATOR_BOX_CONNECTOR_WIDTH) * count, // Adjust width based on actual count
|
||||
INDICATOR_BOX_HEIGHT,
|
||||
_colorTable[0],
|
||||
0);
|
||||
} else { // If window exists, adjust its width if necessary
|
||||
windowResize(gIndicatorBarWindow, (INDICATOR_BOX_WIDTH - INDICATOR_BOX_CONNECTOR_WIDTH) * count, INDICATOR_BOX_HEIGHT);
|
||||
}
|
||||
indicatorBarRender(count); // Render all (standard + custom)
|
||||
windowRefresh(gIndicatorBarWindow);
|
||||
}
|
||||
// END Custom Tag Handling in indicatorBarRefresh
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -2493,62 +2421,17 @@ static void indicatorBarRender(int count)
|
||||
int connectorWidthCompensation = INDICATOR_BOX_CONNECTOR_WIDTH;
|
||||
|
||||
for (int index = 0; index < count; index++) {
|
||||
int indicatorId = gIndicatorSlots[index];
|
||||
int indicator = gIndicatorSlots[index];
|
||||
IndicatorDescription* indicatorDescription = &(gIndicatorDescriptions[indicator]);
|
||||
|
||||
if (indicatorId >= INDICATOR_COUNT) { // This is a custom tag
|
||||
int customTagIndex = indicatorId - INDICATOR_COUNT;
|
||||
if (customTagIndex < gActiveCustomTagsCount && gCustomIndicatorBoxes[customTagIndex].isActive) {
|
||||
CustomIndicatorBox* customBox = &gCustomIndicatorBoxes[customTagIndex];
|
||||
|
||||
// Prepare a temporary IndicatorDescription-like structure or directly use customBox properties
|
||||
// For simplicity, let's assume a generic background for custom tags for now
|
||||
// and render text directly. A more advanced approach might involve
|
||||
// creating a temporary prerendered box similar to standard indicators.
|
||||
|
||||
// Fallback generic box (e.g. using SNEAK's background, but ideally a new generic one)
|
||||
// This is a placeholder. Proper handling would involve a generic custom box graphic.
|
||||
unsigned char* boxArt = gIndicatorDescriptions[INDICATOR_SNEAK].data; // Placeholder
|
||||
|
||||
// Create a temporary buffer for this custom tag's rendering
|
||||
unsigned char tempBoxData[INDICATOR_BOX_WIDTH * INDICATOR_BOX_HEIGHT];
|
||||
memcpy(tempBoxData, boxArt, INDICATOR_BOX_WIDTH * INDICATOR_BOX_HEIGHT);
|
||||
|
||||
// Render custom text onto this temporary buffer
|
||||
// Need to set font and color appropriately
|
||||
int oldFont = fontGetCurrent();
|
||||
fontSetCurrent(101); // Assuming same font as standard indicators
|
||||
|
||||
// Calculate text position (centering)
|
||||
int textY = (INDICATOR_BOX_HEIGHT - fontGetLineHeight()) / 2; // Adjusted for actual box height
|
||||
int textX = (INDICATOR_BOX_WIDTH - fontGetStringWidth(customBox->text)) / 2;
|
||||
|
||||
// Ensure textX is not negative if text is too long (it should be truncated already, but as a safeguard)
|
||||
if (textX < 0) textX = 0;
|
||||
|
||||
fontDrawText(tempBoxData + INDICATOR_BOX_WIDTH * textY + textX,
|
||||
customBox->text,
|
||||
INDICATOR_BOX_WIDTH, // Max width for text area
|
||||
INDICATOR_BOX_WIDTH, // Stride
|
||||
customBox->color); // Use color from custom tag
|
||||
|
||||
fontSetCurrent(oldFont); // Restore original font
|
||||
|
||||
blitBufferToBufferTrans(tempBoxData + connectorWidthCompensation,
|
||||
INDICATOR_BOX_WIDTH - connectorWidthCompensation,
|
||||
INDICATOR_BOX_HEIGHT,
|
||||
INDICATOR_BOX_WIDTH, // Source buffer stride
|
||||
windowBuffer + x, windowWidth);
|
||||
}
|
||||
} else { // This is a standard indicator
|
||||
IndicatorDescription* indicatorDescription = &(gIndicatorDescriptions[indicatorId]);
|
||||
blitBufferToBufferTrans(indicatorDescription->data + connectorWidthCompensation,
|
||||
INDICATOR_BOX_WIDTH - connectorWidthCompensation,
|
||||
INDICATOR_BOX_HEIGHT,
|
||||
INDICATOR_BOX_WIDTH,
|
||||
windowBuffer + x, windowWidth);
|
||||
}
|
||||
blitBufferToBufferTrans(indicatorDescription->data + connectorWidthCompensation,
|
||||
INDICATOR_BOX_WIDTH - connectorWidthCompensation,
|
||||
INDICATOR_BOX_HEIGHT,
|
||||
INDICATOR_BOX_WIDTH,
|
||||
windowBuffer + x, windowWidth);
|
||||
|
||||
connectorWidthCompensation = 0;
|
||||
|
||||
unconnectedIndicatorsWidth += INDICATOR_BOX_WIDTH;
|
||||
x = unconnectedIndicatorsWidth - INDICATOR_BOX_CONNECTOR_WIDTH * connections;
|
||||
connections++;
|
||||
@@ -2770,54 +2653,4 @@ bool interface_get_current_attack_mode(int* hit_mode)
|
||||
return true;
|
||||
}
|
||||
|
||||
int interfaceAddCustomTag() {
|
||||
if (gActiveCustomTagsCount >= MAX_CUSTOM_INDICATOR_BOXES) {
|
||||
return -1; // No slot available
|
||||
}
|
||||
// Find the first non-active slot conceptually, but since we use gActiveCustomTagsCount,
|
||||
// we just append to the current list of active ones.
|
||||
// The actual "slot" in gCustomIndicatorBoxes is gActiveCustomTagsCount.
|
||||
int tagId = gActiveCustomTagsCount;
|
||||
|
||||
gCustomIndicatorBoxes[tagId].isActive = true; // Mark as active conceptually
|
||||
// Default text/color can be set here if desired, e.g.:
|
||||
strncpy(gCustomIndicatorBoxes[tagId].text, "NEW", 19);
|
||||
gCustomIndicatorBoxes[tagId].text[19] = '\0';
|
||||
gCustomIndicatorBoxes[tagId].color = _colorTable[992]; // Default to green, like SNEAK
|
||||
|
||||
gActiveCustomTagsCount++;
|
||||
// Note: indicatorBarRefresh() is not called here;
|
||||
// it's typically called after a batch of changes or by show/hide.
|
||||
return tagId;
|
||||
}
|
||||
|
||||
void interfaceShowCustomTag(int tagId) {
|
||||
if (tagId < 0 || tagId >= gActiveCustomTagsCount) return;
|
||||
gCustomIndicatorBoxes[tagId].isActive = true;
|
||||
indicatorBarRefresh();
|
||||
}
|
||||
|
||||
void interfaceHideCustomTag(int tagId) {
|
||||
if (tagId < 0 || tagId >= gActiveCustomTagsCount) return;
|
||||
gCustomIndicatorBoxes[tagId].isActive = false;
|
||||
indicatorBarRefresh();
|
||||
}
|
||||
|
||||
bool interfaceIsCustomTagActive(int tagId) {
|
||||
if (tagId < 0 || tagId >= gActiveCustomTagsCount) return false;
|
||||
return gCustomIndicatorBoxes[tagId].isActive;
|
||||
}
|
||||
|
||||
void interfaceSetCustomTagText(int tagId, const char* text, int color) {
|
||||
if (tagId < 0 || tagId >= gActiveCustomTagsCount) return;
|
||||
|
||||
strncpy(gCustomIndicatorBoxes[tagId].text, text, 19);
|
||||
gCustomIndicatorBoxes[tagId].text[19] = '\0'; // Ensure null termination
|
||||
gCustomIndicatorBoxes[tagId].color = color;
|
||||
|
||||
if (gCustomIndicatorBoxes[tagId].isActive) {
|
||||
indicatorBarRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -12,22 +12,6 @@ namespace fallout {
|
||||
#define INTERFACE_BAR_WIDTH 640
|
||||
#define INTERFACE_BAR_HEIGHT 100
|
||||
|
||||
// Minimum radiation amount to display RADIATED indicator.
|
||||
#define RADATION_INDICATOR_THRESHOLD 65
|
||||
// Minimum poison amount to display POISONED indicator.
|
||||
#define POISON_INDICATOR_THRESHOLD 0
|
||||
|
||||
#define MAX_CUSTOM_INDICATOR_BOXES 126
|
||||
|
||||
typedef struct CustomIndicatorBox {
|
||||
char text[20]; // 19 chars + null terminator
|
||||
int color; // Could be an enum or int mapped to color values
|
||||
bool isActive;
|
||||
// Potentially add a specific ID if needed, though array index might suffice
|
||||
} CustomIndicatorBox;
|
||||
|
||||
extern CustomIndicatorBox gCustomIndicatorBoxes[MAX_CUSTOM_INDICATOR_BOXES];
|
||||
|
||||
typedef enum InterfaceItemAction {
|
||||
INTERFACE_ITEM_ACTION_DEFAULT = -1,
|
||||
INTERFACE_ITEM_ACTION_USE,
|
||||
@@ -79,12 +63,6 @@ bool indicatorBarShow();
|
||||
bool indicatorBarHide();
|
||||
bool interface_get_current_attack_mode(int* hit_mode);
|
||||
|
||||
int interfaceAddCustomTag();
|
||||
void interfaceShowCustomTag(int tagId);
|
||||
void interfaceHideCustomTag(int tagId);
|
||||
bool interfaceIsCustomTagActive(int tagId);
|
||||
void interfaceSetCustomTagText(int tagId, const char* text, int color);
|
||||
|
||||
unsigned char* customInterfaceBarGetBackgroundImageData();
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
+81
-31
@@ -56,13 +56,87 @@ static void mf_string_find(Program* program, int args);
|
||||
static void mf_string_to_case(Program* program, int args);
|
||||
static void mf_string_format(Program* program, int args);
|
||||
static void mf_floor2(Program* program, int args);
|
||||
static void mf_add_iface_tag(Program* program, int args);
|
||||
static void mf_set_iface_tag_text(Program* program, int args);
|
||||
|
||||
// New metarule function implementations
|
||||
static void mf_set_terrain_name(Program* program, int args) {
|
||||
// Args from script: x, y, name
|
||||
const char* name = programStackPopString(program);
|
||||
long y = programStackPopInteger(program);
|
||||
long x = programStackPopInteger(program);
|
||||
|
||||
if (x < 0 || y < 0) {
|
||||
programFatalError("set_terrain_name() - invalid x/y coordinates for the sub-tile. x=%ld, y=%ld", x, y);
|
||||
return; // programFatalError should halt, but return for safety
|
||||
}
|
||||
if (name == nullptr) {
|
||||
// Though programStackPopString might handle nulls, an explicit check after can be useful
|
||||
// Depending on how programStackPopString handles empty/null strings from script.
|
||||
// Assuming it can return nullptr if script provides a way to pass null.
|
||||
programFatalError("set_terrain_name() - name cannot be null.");
|
||||
return;
|
||||
}
|
||||
|
||||
wmSetTerrainTypeName(x, y, name);
|
||||
// No return value pushed to stack for void function
|
||||
}
|
||||
|
||||
static void mf_get_terrain_name(Program* program, int args) {
|
||||
const char* name = nullptr;
|
||||
if (args == 0) {
|
||||
name = wmGetCurrentTerrainName();
|
||||
if (name == nullptr) {
|
||||
programStackPushString(program, "Error");
|
||||
} else {
|
||||
programStackPushString(program, name);
|
||||
}
|
||||
} else if (args == 2) {
|
||||
// Args from script: x, y
|
||||
long y = programStackPopInteger(program);
|
||||
long x = programStackPopInteger(program);
|
||||
|
||||
if (x < 0 || y < 0) {
|
||||
programFatalError("get_terrain_name() - invalid x/y coordinates for the sub-tile. x=%ld, y=%ld", x, y);
|
||||
programStackPushString(program, "Error"); // Push "Error" as per requirement
|
||||
return;
|
||||
}
|
||||
name = wmGetTerrainTypeName(x, y);
|
||||
if (name == nullptr) {
|
||||
programStackPushString(program, "Error");
|
||||
} else {
|
||||
programStackPushString(program, name);
|
||||
}
|
||||
} else {
|
||||
// This case should ideally be caught by MetaruleInfo min/max args check in sfall_metarule dispatcher
|
||||
programFatalError("get_terrain_name: invalid number of arguments. Expected 0 or 2, got %d", args);
|
||||
programStackPushString(program, "Error"); // Push "Error" as per requirement
|
||||
}
|
||||
}
|
||||
|
||||
static void mf_set_town_title(Program* program, int args) {
|
||||
// Args from script: areaID, title
|
||||
const char* title = programStackPopString(program);
|
||||
long areaID = programStackPopInteger(program);
|
||||
|
||||
if (areaID < 0) {
|
||||
programFatalError("set_town_title: invalid area ID. areaID=%ld", areaID);
|
||||
return; // programFatalError should halt
|
||||
}
|
||||
// Allowing null title to be passed to wmSetCustomAreaTitle to potentially clear the title.
|
||||
// If null title itself is an error for this metarule, add:
|
||||
// if (title == nullptr) {
|
||||
// programFatalError("set_town_title: title cannot be null for areaID=%ld.", areaID);
|
||||
// return;
|
||||
// }
|
||||
|
||||
wmSetCustomAreaTitle(areaID, title);
|
||||
// No return value pushed to stack for void function
|
||||
}
|
||||
|
||||
|
||||
// ref. https://github.com/sfall-team/sfall/blob/42556141127895c27476cd5242a73739cbb0fade/sfall/Modules/Scripting/Handlers/Metarule.cpp#L72
|
||||
constexpr MetaruleInfo kMetarules[] = {
|
||||
// {"add_extra_msg_file", mf_add_extra_msg_file, 1, 2, -1, {ARG_STRING, ARG_INT}},
|
||||
{ "add_iface_tag", mf_add_iface_tag, 0, 0},
|
||||
// {"add_iface_tag", mf_add_iface_tag, 0, 0},
|
||||
// {"add_g_timer_event", mf_add_g_timer_event, 2, 2, -1, {ARG_INT, ARG_INT}},
|
||||
// {"add_trait", mf_add_trait, 1, 1, -1, {ARG_INT}},
|
||||
// {"art_cache_clear", mf_art_cache_flush, 0, 0},
|
||||
@@ -97,7 +171,7 @@ constexpr MetaruleInfo kMetarules[] = {
|
||||
// {"get_stat_max", mf_get_stat_max, 1, 2, 0, {ARG_INT, ARG_INT}},
|
||||
// {"get_stat_min", mf_get_stat_min, 1, 2, 0, {ARG_INT, ARG_INT}},
|
||||
// {"get_string_pointer", mf_get_string_pointer, 1, 1, 0, {ARG_STRING}}, // note: deprecated; do not implement
|
||||
// {"get_terrain_name", mf_get_terrain_name, 0, 2, -1, {ARG_INT, ARG_INT}},
|
||||
{ "get_terrain_name", mf_get_terrain_name, 0, 2 },
|
||||
{ "get_text_width", mf_get_text_width, 1, 1 },
|
||||
// {"get_window_attribute", mf_get_window_attribute, 1, 2, -1, {ARG_INT, ARG_INT}},
|
||||
// {"has_fake_perk_npc", mf_has_fake_perk_npc, 2, 2, 0, {ARG_OBJECT, ARG_STRING}},
|
||||
@@ -135,7 +209,7 @@ constexpr MetaruleInfo kMetarules[] = {
|
||||
// {"set_fake_perk_npc", mf_set_fake_perk_npc, 5, 5, -1, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}},
|
||||
// {"set_fake_trait_npc", mf_set_fake_trait_npc, 5, 5, -1, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}},
|
||||
{ "set_flags", mf_set_flags, 2, 2 },
|
||||
{ "set_iface_tag_text", mf_set_iface_tag_text, 3, 3 },
|
||||
// {"set_iface_tag_text", mf_set_iface_tag_text, 3, 3, -1, {ARG_INT, ARG_STRING, ARG_INT}},
|
||||
{ "set_ini_setting", mf_set_ini_setting, 2, 2 },
|
||||
// {"set_map_enter_position", mf_set_map_enter_position, 3, 3, -1, {ARG_INT, ARG_INT, ARG_INT}},
|
||||
// {"set_object_data", mf_set_object_data, 3, 3, -1, {ARG_OBJECT, ARG_INT, ARG_INT}},
|
||||
@@ -146,8 +220,8 @@ constexpr MetaruleInfo kMetarules[] = {
|
||||
// {"set_rest_mode", mf_set_rest_mode, 1, 1, -1, {ARG_INT}},
|
||||
// {"set_scr_name", mf_set_scr_name, 0, 1, -1, {ARG_STRING}},
|
||||
// {"set_selectable_perk_npc", mf_set_selectable_perk_npc, 5, 5, -1, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}},
|
||||
// {"set_terrain_name", mf_set_terrain_name, 3, 3, -1, {ARG_INT, ARG_INT, ARG_STRING}},
|
||||
// {"set_town_title", mf_set_town_title, 2, 2, -1, {ARG_INT, ARG_STRING}},
|
||||
{ "set_terrain_name", mf_set_terrain_name, 3, 3 },
|
||||
{ "set_town_title", mf_set_town_title, 2, 2 },
|
||||
// {"set_unique_id", mf_set_unique_id, 1, 2, -1, {ARG_OBJECT, ARG_INT}},
|
||||
// {"set_unjam_locks_time", mf_set_unjam_locks_time, 1, 1, -1, {ARG_INT}},
|
||||
// {"set_window_flag", mf_set_window_flag, 3, 3, -1, {ARG_INTSTR, ARG_INT, ARG_INT}},
|
||||
@@ -494,30 +568,6 @@ void mf_floor2(Program* program, int args)
|
||||
programStackPushInteger(program, static_cast<int>(floor(programValue.asFloat())));
|
||||
}
|
||||
|
||||
static void mf_add_iface_tag(Program* program, int args) {
|
||||
// No arguments are expected from the script for this function.
|
||||
// Ensure args == 0 if strict argument checking is desired.
|
||||
// The metarule registration already specifies minArgs=0 and maxArgs=0.
|
||||
int result = fallout::interfaceAddCustomTag();
|
||||
fallout::programStackPushInteger(program, result);
|
||||
}
|
||||
|
||||
static void mf_set_iface_tag_text(Program* program, int args) {
|
||||
// Expected arguments: color (int), text (string), tag_id (int)
|
||||
// Arguments are popped in reverse order of how they are pushed by the script.
|
||||
// So, if script calls: set_iface_tag_text(tag_id, "my text", 5)
|
||||
// Stack will have: 5 (color), "my text", tag_id
|
||||
|
||||
int color = fallout::programStackPopInteger(program);
|
||||
const char* text = fallout::programStackPopString(program);
|
||||
int tag_id = fallout::programStackPopInteger(program);
|
||||
|
||||
fallout::interfaceSetCustomTagText(tag_id, text, color);
|
||||
|
||||
// Following the pattern of other void metarules like mf_set_flags, push -1.
|
||||
fallout::programStackPushInteger(program, -1);
|
||||
}
|
||||
|
||||
void sprintf_lite(Program* program, int args, const char* infoOpcodeName)
|
||||
{
|
||||
auto format = programStackPopString(program); // Pop the format string
|
||||
|
||||
@@ -9,6 +9,11 @@ void sfall_metarule(Program* program, int args);
|
||||
|
||||
void sprintf_lite(Program* program, int args, const char* infoOpcodeName);
|
||||
|
||||
// New static function declarations for metarules
|
||||
static void mf_set_terrain_name(Program* program, int args);
|
||||
static void mf_get_terrain_name(Program* program, int args);
|
||||
static void mf_set_town_title(Program* program, int args);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* FALLOUT_SFALL_METARULES_H_ */
|
||||
|
||||
@@ -1629,172 +1629,6 @@ void sfallOpcodesInit()
|
||||
|
||||
// 0x827d - void register_hook_proc_spec(int hook, procedure proc)
|
||||
// 0x827e - void reg_anim_callback(procedure proc)
|
||||
|
||||
// 0x81dc - void show_iface_tag(int tag)
|
||||
interpreterRegisterOpcode(0x81DC, op_show_iface_tag);
|
||||
// 0x81dd - void hide_iface_tag(int tag)
|
||||
interpreterRegisterOpcode(0x81DD, op_hide_iface_tag);
|
||||
// 0x81de - int is_iface_tag_active(int tag)
|
||||
interpreterRegisterOpcode(0x81DE, op_is_iface_tag_active);
|
||||
}
|
||||
|
||||
static void op_show_iface_tag(fallout::Program* program) {
|
||||
int tag = fallout::programStackPopInteger(program);
|
||||
|
||||
// Tag values based on issue:
|
||||
// 0: SNEAK
|
||||
// 3: LEVEL
|
||||
// 4: ADDICT
|
||||
// 5 to (4 + BoxBarCount) or last custom box: Custom tags
|
||||
|
||||
if (tag == 0) { // SNEAK
|
||||
if (fallout::gDude) {
|
||||
// Assuming critterAddState adds the state if not present.
|
||||
// If it's a toggle or has other behavior, this might need adjustment.
|
||||
fallout::critterAddState(fallout::gDude, fallout::DUDE_STATE_SNEAKING);
|
||||
// indicatorBarRefresh is called by interfaceShowCustomTag for custom tags,
|
||||
// and should also be called after changing states for predefined tags.
|
||||
fallout::indicatorBarRefresh();
|
||||
}
|
||||
} else if (tag == 3) { // LEVEL
|
||||
if (fallout::gDude) {
|
||||
fallout::critterAddState(fallout::gDude, fallout::DUDE_STATE_LEVEL_UP_AVAILABLE);
|
||||
fallout::indicatorBarRefresh();
|
||||
}
|
||||
} else if (tag == 4) { // ADDICT
|
||||
if (fallout::gDude) {
|
||||
fallout::critterAddState(fallout::gDude, fallout::DUDE_STATE_ADDICTED);
|
||||
fallout::indicatorBarRefresh();
|
||||
}
|
||||
} else if (tag >= 5) {
|
||||
// Custom tags start from ID 0 in gCustomIndicatorBoxes.
|
||||
// The script tag ID is 5 + internal_id. So internal_id = tag - 5.
|
||||
fallout::interfaceShowCustomTag(tag - 5); // This already calls indicatorBarRefresh
|
||||
} else {
|
||||
// Log error for unhandled/invalid tags like 1, 2, or negative values.
|
||||
// Note: Negative values for tags are not explicitly handled by current logic
|
||||
// but would fall into this else block.
|
||||
fallout::debugPrint("op_show_iface_tag: Unhandled or invalid tag ID %d", tag);
|
||||
}
|
||||
// No return value for this opcode.
|
||||
}
|
||||
|
||||
static void op_hide_iface_tag(fallout::Program* program) {
|
||||
int tag = fallout::programStackPopInteger(program);
|
||||
|
||||
// Tag values:
|
||||
// 0: SNEAK
|
||||
// 3: LEVEL
|
||||
// 4: ADDICT
|
||||
// 5+: Custom tags
|
||||
|
||||
if (tag == 0) { // SNEAK
|
||||
if (fallout::gDude) {
|
||||
// Assuming critterClearState removes the state.
|
||||
fallout::critterClearState(fallout::gDude, fallout::DUDE_STATE_SNEAKING);
|
||||
fallout::indicatorBarRefresh();
|
||||
}
|
||||
} else if (tag == 3) { // LEVEL
|
||||
if (fallout::gDude) {
|
||||
fallout::critterClearState(fallout::gDude, fallout::DUDE_STATE_LEVEL_UP_AVAILABLE);
|
||||
fallout::indicatorBarRefresh();
|
||||
}
|
||||
} else if (tag == 4) { // ADDICT
|
||||
if (fallout::gDude) {
|
||||
fallout::critterClearState(fallout::gDude, fallout::DUDE_STATE_ADDICTED);
|
||||
fallout::indicatorBarRefresh();
|
||||
}
|
||||
} else if (tag >= 5) {
|
||||
// Custom tags start from ID 0 in gCustomIndicatorBoxes.
|
||||
// The script tag ID is 5 + internal_id. So internal_id = tag - 5.
|
||||
fallout::interfaceHideCustomTag(tag - 5); // This already calls indicatorBarRefresh
|
||||
} else {
|
||||
// Log error for unhandled/invalid tags like 1, 2, or negative values.
|
||||
fallout::debugPrint("op_hide_iface_tag: Unhandled or invalid tag ID %d", tag);
|
||||
}
|
||||
// No return value for this opcode.
|
||||
}
|
||||
|
||||
static void op_hide_iface_tag(fallout::Program* program) {
|
||||
int tag = fallout::programStackPopInteger(program);
|
||||
|
||||
// Tag values:
|
||||
// 0: SNEAK
|
||||
// 3: LEVEL
|
||||
// 4: ADDICT
|
||||
// 5+: Custom tags
|
||||
|
||||
if (tag == 0) { // SNEAK
|
||||
if (fallout::gDude) {
|
||||
// Assuming critterClearState removes the state.
|
||||
fallout::critterClearState(fallout::gDude, fallout::DUDE_STATE_SNEAKING);
|
||||
fallout::indicatorBarRefresh();
|
||||
}
|
||||
} else if (tag == 3) { // LEVEL
|
||||
if (fallout::gDude) {
|
||||
fallout::critterClearState(fallout::gDude, fallout::DUDE_STATE_LEVEL_UP_AVAILABLE);
|
||||
fallout::indicatorBarRefresh();
|
||||
}
|
||||
} else if (tag == 4) { // ADDICT
|
||||
if (fallout::gDude) {
|
||||
fallout::critterClearState(fallout::gDude, fallout::DUDE_STATE_ADDICTED);
|
||||
fallout::indicatorBarRefresh();
|
||||
}
|
||||
} else if (tag >= 5) {
|
||||
// Custom tags start from ID 0 in gCustomIndicatorBoxes.
|
||||
// The script tag ID is 5 + internal_id. So internal_id = tag - 5.
|
||||
fallout::interfaceHideCustomTag(tag - 5); // This already calls indicatorBarRefresh
|
||||
} else {
|
||||
// Log error for unhandled/invalid tags like 1, 2, or negative values.
|
||||
fallout::debugPrint("op_hide_iface_tag: Unhandled or invalid tag ID %d", tag);
|
||||
}
|
||||
// No return value for this opcode.
|
||||
}
|
||||
|
||||
static void op_is_iface_tag_active(fallout::Program* program) {
|
||||
int tag = fallout::programStackPopInteger(program);
|
||||
bool isActive = false;
|
||||
|
||||
// Tag values based on issue:
|
||||
// 0: SNEAK
|
||||
// 1: POISONED
|
||||
// 2: RADIATED
|
||||
// 3: LEVEL
|
||||
// 4: ADDICT
|
||||
// 5+: Custom tags
|
||||
|
||||
if (fallout::gDude) { // Ensure gDude is valid for checks
|
||||
switch (tag) {
|
||||
case 0: // SNEAK
|
||||
isActive = fallout::dudeHasState(fallout::gDude, fallout::DUDE_STATE_SNEAKING);
|
||||
break;
|
||||
case 1: // POISONED
|
||||
isActive = critterGetPoison(fallout::gDude) > fallout::POISON_INDICATOR_THRESHOLD;
|
||||
break;
|
||||
case 2: // RADIATED
|
||||
isActive = critterGetRadiation(fallout::gDude) > fallout::RADATION_INDICATOR_THRESHOLD;
|
||||
break;
|
||||
case 3: // LEVEL
|
||||
isActive = fallout::dudeHasState(fallout::gDude, fallout::DUDE_STATE_LEVEL_UP_AVAILABLE);
|
||||
break;
|
||||
case 4: // ADDICT
|
||||
isActive = fallout::dudeHasState(fallout::gDude, fallout::DUDE_STATE_ADDICTED);
|
||||
break;
|
||||
default:
|
||||
if (tag >= 5) {
|
||||
isActive = fallout::interfaceIsCustomTagActive(tag - 5); // Adjust tag ID
|
||||
} else {
|
||||
fallout::debugPrint("op_is_iface_tag_active: Unhandled or invalid tag ID %d", tag);
|
||||
// isActive remains false for unhandled tags
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
fallout::debugPrint("op_is_iface_tag_active: gDude is null, cannot check tag status for tag ID %d.", tag);
|
||||
// isActive remains false if gDude is null
|
||||
}
|
||||
|
||||
fallout::programStackPushInteger(program, isActive ? 1 : 0);
|
||||
}
|
||||
|
||||
void sfallOpcodesExit()
|
||||
|
||||
@@ -817,6 +817,10 @@ static int wmMaxEncBaseTypes;
|
||||
// 0x67303C
|
||||
static int wmMaxEncounterInfoTables;
|
||||
|
||||
// Definitions for new static global variables
|
||||
static std::vector<std::pair<long, std::string>> wmTerrainTypeNames;
|
||||
static std::unordered_map<long, std::string> wmAreaHotSpotTitle;
|
||||
|
||||
static bool gTownMapHotkeysFix;
|
||||
static double gGameTimeIncRemainder = 0.0;
|
||||
static FrmImage _backgroundFrmImage;
|
||||
@@ -1059,6 +1063,10 @@ int wmWorldMap_reset()
|
||||
wmWorldMapLoadTempData();
|
||||
wmMarkAllSubTiles(0);
|
||||
|
||||
// Clear new global variables
|
||||
wmTerrainTypeNames.clear();
|
||||
wmAreaHotSpotTitle.clear();
|
||||
|
||||
return wmGenDataReset();
|
||||
}
|
||||
|
||||
@@ -6672,4 +6680,87 @@ void wmForceEncounter(int map, unsigned int flags)
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of new functions
|
||||
|
||||
void wmSetTerrainTypeName(long x, long y, const char* name) {
|
||||
// x and y are subtile indices
|
||||
long subTileID = x + y * (wmNumHorizontalTiles * SUBTILE_GRID_WIDTH);
|
||||
wmTerrainTypeNames.push_back({subTileID, name});
|
||||
}
|
||||
|
||||
const char* wmGetTerrainTypeName(long x, long y) {
|
||||
// x and y are subtile indices
|
||||
long subTileID = x + y * (wmNumHorizontalTiles * SUBTILE_GRID_WIDTH);
|
||||
|
||||
for (auto it = wmTerrainTypeNames.crbegin(); it != wmTerrainTypeNames.crend(); ++it) {
|
||||
if (it->first == subTileID) {
|
||||
return it->second.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to default terrain name
|
||||
SubtileInfo* subtile = nullptr;
|
||||
// Convert subtile indices (x, y) to world coordinates for wmFindCurSubTileFromPos
|
||||
int worldX = x * WM_SUBTILE_SIZE;
|
||||
int worldY = y * WM_SUBTILE_SIZE;
|
||||
|
||||
// Need to find the correct tile first to pass to wmFindCurSubTileFromPos,
|
||||
// or rather, wmFindCurSubTileFromPos might be enough if its x and y are world coordinates.
|
||||
// wmFindCurSubTileFromPos expects world coordinates.
|
||||
if (wmFindCurSubTileFromPos(worldX, worldY, &subtile) == 0 && subtile != nullptr) {
|
||||
int terrainId = subtile->terrain;
|
||||
// Ensure gWorldmapMessageListItem is properly handled or declare a local one if needed for getmsg
|
||||
MessageListItem item; // Using a local item for safety if gWorldmapMessageListItem has shared state concerns
|
||||
return getmsg(&wmMsgFile, &item, 1000 + terrainId)->text;
|
||||
}
|
||||
return "Unknown Terrain"; // Absolute fallback
|
||||
}
|
||||
|
||||
const char* wmGetCurrentTerrainName() {
|
||||
long subTileX = wmGenData.worldPosX / WM_SUBTILE_SIZE;
|
||||
long subTileY = wmGenData.worldPosY / WM_SUBTILE_SIZE;
|
||||
long subTileID = subTileX + subTileY * (wmNumHorizontalTiles * SUBTILE_GRID_WIDTH);
|
||||
|
||||
for (auto it = wmTerrainTypeNames.crbegin(); it != wmTerrainTypeNames.crend(); ++it) {
|
||||
if (it->first == subTileID) {
|
||||
return it->second.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to default current terrain name
|
||||
int terrainId = -1;
|
||||
if (wmGenData.currentSubtile != nullptr) {
|
||||
terrainId = wmGenData.currentSubtile->terrain;
|
||||
} else {
|
||||
SubtileInfo* currentSubtilePtr = nullptr;
|
||||
if (wmFindCurSubTileFromPos(wmGenData.worldPosX, wmGenData.worldPosY, ¤tSubtilePtr) == 0 && currentSubtilePtr != nullptr) {
|
||||
wmGenData.currentSubtile = currentSubtilePtr; // Cache it
|
||||
terrainId = currentSubtilePtr->terrain;
|
||||
}
|
||||
}
|
||||
|
||||
if (terrainId != -1) {
|
||||
MessageListItem item; // Using a local item
|
||||
return getmsg(&wmMsgFile, &item, 1000 + terrainId)->text;
|
||||
}
|
||||
return "Unknown Current Terrain"; // Absolute fallback
|
||||
}
|
||||
|
||||
void wmSetCustomAreaTitle(long areaID, const char* title) {
|
||||
if (title != nullptr) {
|
||||
wmAreaHotSpotTitle[areaID] = title;
|
||||
} else {
|
||||
// Option: remove the entry if title is null, or store empty string
|
||||
wmAreaHotSpotTitle.erase(areaID);
|
||||
}
|
||||
}
|
||||
|
||||
const char* wmGetCustomAreaTitle(long areaID) {
|
||||
auto it = wmAreaHotSpotTitle.find(areaID);
|
||||
if (it != wmAreaHotSpotTitle.end()) {
|
||||
return it->second.c_str();
|
||||
}
|
||||
return nullptr; // Or an empty string
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#define WORLD_MAP_H
|
||||
|
||||
#include "db.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace fallout {
|
||||
|
||||
@@ -237,6 +240,10 @@ typedef enum Map {
|
||||
|
||||
extern unsigned char* circleBlendTable;
|
||||
|
||||
// Static global variables
|
||||
static std::vector<std::pair<long, std::string>> wmTerrainTypeNames;
|
||||
static std::unordered_map<long, std::string> wmAreaHotSpotTitle;
|
||||
|
||||
int wmWorldMap_init();
|
||||
void wmWorldMap_exit();
|
||||
int wmWorldMap_reset();
|
||||
@@ -287,6 +294,13 @@ void wmSetPartyWorldPos(int x, int y);
|
||||
void wmCarSetCurrentArea(int area);
|
||||
void wmForceEncounter(int map, unsigned int flags);
|
||||
|
||||
// New function declarations
|
||||
void wmSetTerrainTypeName(long x, long y, const char* name);
|
||||
const char* wmGetTerrainTypeName(long x, long y);
|
||||
const char* wmGetCurrentTerrainName();
|
||||
void wmSetCustomAreaTitle(long areaID, const char* title);
|
||||
const char* wmGetCustomAreaTitle(long areaID);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* WORLD_MAP_H */
|
||||
|
||||
Reference in New Issue
Block a user