Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 39 additions & 35 deletions cleo_plugins/Text/Text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,28 +152,35 @@ class Text
return result;
}

static bool CanTextInThisSlotBeAddedToBrief(const char* slot)
static bool CanThisTextBeAddedToBrief(const char* text)
{
// content dedup: exits early if another entry already holds the same text.
for (size_t i = 0; i < BriefSize; i++)
if (!CTheScripts::bAddNextMessageToPreviousBriefs) return false;

for (const auto& brief : CMessages::PreviousBriefs)
{
const auto pText = CMessages::PreviousBriefs[i].m_pText;
if (pText == nullptr) break; // end of used entries
if (pText == slot) continue; // stale self-reference, skip to avoid a false positive duplicate
if (strcmp(pText, slot) == 0) return false; // content duplicate, exit
const auto briefText = brief.m_pText;
if (briefText && strcmp(briefText, text) == 0) return false;
}
return true;
}

static char* PrepareThisTextForBrief(const char* text)
{
briefIdx = (briefIdx + 1) % BriefSize;
char* msgSlot = briefs[briefIdx];

// stale pointer eviction: prevents the game's pointer-based dedup from rejecting the insertion
for (size_t i = 0; i < BriefSize; i++)
for (auto& brief : CMessages::PreviousBriefs)
{
auto& pText = CMessages::PreviousBriefs[i].m_pText;
if (pText == slot)
{
pText = nullptr;
break;
}
// stale pointer eviction: prevents the game's pointer-based brief dedup from rejecting the insertion
const auto briefText = brief.m_pText;
if (briefText == msgSlot) brief.m_pText = nullptr;
}
return true;

// put the message into a free slot in the static buffer to get a persistent pointer
const auto msgSlotSize = sizeof(briefs[briefIdx]);
strncpy_s(msgSlot, msgSlotSize, text, msgSlotSize - 1);

return msgSlot;
}

static void PrintHelp(CLEO::CRunningScript* thread, const char* text)
Expand All @@ -182,17 +189,9 @@ class Text

if (IsLegacyScript(thread)) return;

if (CTheScripts::bAddNextMessageToPreviousBriefs)
if (CanThisTextBeAddedToBrief(text))
{
briefIdx = (briefIdx + 1) % BriefSize;
auto& briefSlot = briefs[briefIdx];

strncpy_s(briefSlot, text, sizeof(briefSlot) - 1);

if (CanTextInThisSlotBeAddedToBrief(briefSlot))
{
CMessages::AddToPreviousBriefArray(briefSlot, -1, -1, -1, -1, -1, -1, 0);
}
CMessages::AddToPreviousBriefArray(PrepareThisTextForBrief(text), -1, -1, -1, -1, -1, -1, 0);
}

CTheScripts::bAddNextMessageToPreviousBriefs = true;
Expand All @@ -201,10 +200,10 @@ class Text
static void AddToMessageQueue(CLEO::CRunningScript* pScript, const char* text, int time, bool now)
{
/*
CLEO 4: always show messages, no brief change, no subtitle suppression (~z~)
CLEO 5: show messages conditionally based on user preference, update brief
CLEO 4: show subtitle ~z~ regarless of "Show Subtitles"; no brief update ever
CLEO 5: show subtitle ~z~ only if "Show Subtitles" is ON; update brief if needed

Both: if message queue is full, don't show the message, unless it's a NOW message.
Both: Queue messages; if the queue is full, skip the next message, unless it's a NOW message.
*/

const auto isLegacy = IsLegacyScript(pScript);
Expand All @@ -231,15 +230,20 @@ class Text

if (display)
{
// put the message into a free slot in our static buffer to get a persistent pointer
queueIdx = (queueIdx + 1) % MessageQueueSize;
auto& messageSlot = messageQueue[queueIdx];
strncpy_s(messageSlot, text, sizeof(messageSlot) - 1);
// put the message into a free slot in the static buffer to get a persistent pointer
queueIdx = (queueIdx + 1) % MessageQueueSize;
char* messageSlot = messageQueue[queueIdx];
const auto bufSize = sizeof(messageQueue[queueIdx]);
strncpy_s(messageSlot, bufSize, text, bufSize - 1);

// check game brief and decide whether this message can be added to it.
// Note: legacy scripts never modify the game brief.
const auto addToBrief = !isLegacy && CTheScripts::bAddNextMessageToPreviousBriefs &&
CanTextInThisSlotBeAddedToBrief(messageSlot);
const auto addToBrief = !isLegacy && CanThisTextBeAddedToBrief(text);

if (addToBrief)
{
messageSlot = PrepareThisTextForBrief(text);
}

if (now)
{
Expand Down
18 changes: 17 additions & 1 deletion tests/cleo_tests/Text/0ACC.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function tests
it_cs5("should not display message starting with ~z~ with subtitles off", test9_cs5)
it_cs4("should display message starting with ~z~ with subtitles off", test9_cs4)
it("should not display message when game queue is full", test10_queue_full)
it_cs5("should dedup brief messages in loop", test11_dedup_loop_cs5)
return

:cleanup
Expand Down Expand Up @@ -256,10 +257,25 @@ function tests
// Queue is full; print_string must be silently dropped
print_string {text} "Queue Full Str" {time} 900
wait 0

// current subtitle must still be the first fill message, not the dropped one
assert_subtitle("Fill Q 1")
end

function test11_dedup_loop_cs5
print_string "STAT001" 100
wait 500
TIMERA = 0
while TIMERA < 1000
wait 0
print_string "STAT002" 100
end

assert_brief_at(0, "STAT002")
assert_brief_at(1, "STAT001")

int brief = getBriefTextAt(2)
assert_eq(brief, 0)
end
end

{$INCLUDE_ONCE helpers.inc}
Expand Down
19 changes: 17 additions & 2 deletions tests/cleo_tests/Text/0ACD.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function tests
it_cs5("should not display message starting with ~z~ with subtitles off", test7_cs5)
it_cs4("should display message starting with ~z~ with subtitles off", test7_cs4)
it("should display NOW message even when game queue is full", test8_queue_full_now)

it_cs5("should dedup brief messages in loop", test9_dedup_loop_cs5)
return

:cleanup
Expand Down Expand Up @@ -179,10 +179,25 @@ function tests
// Queue is full; print_string_now must still display (bypasses full-queue check)
print_string_now {text} "Now Bypasses Q" {time} 100
wait 0

// NOW message must jump to front despite full queue
assert_subtitle("Now Bypasses Q")
end

function test9_dedup_loop_cs5
print_string_now "STAT001" 1000
TIMERA = 0
while TIMERA < 1000
wait 0
print_string_now "STAT002" 100
end

assert_brief_at(0, "STAT002")
assert_brief_at(1, "STAT001")

int brief = getBriefTextAt(2)
assert_eq(brief, 0)
end
end

{$INCLUDE_ONCE helpers.inc}
Expand Down
19 changes: 18 additions & 1 deletion tests/cleo_tests/Text/0AD0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ function tests
it_cs5("should not add duplicate text to brief history", test8_queue_dedup)
it_cs4("should not add any text to brief history in legacy mode", test8_queue_dedup_cs4)
it_cs5("should not display message starting with ~z~ with subtitles off", test9_cs5)
it_cs4("should display message starting with ~z~ with subtitles off", test9_cs4)
it_cs4("should display message starting with ~z~ with subtitles off", test9_cs4)
it("should not display message when game queue is full", test10_queue_full)
it_cs5("should dedup brief messages in loop", test11_dedup_loop_cs5)
return

:cleanup
Expand Down Expand Up @@ -243,6 +244,22 @@ function tests
// current subtitle must still be the first fill message, not the dropped one
assert_subtitle("Fill Q 1")
end

function test11_dedup_loop_cs5
print_formatted "STAT001" 100
wait 500
TIMERA = 0
while TIMERA < 1000
wait 0
print_formatted "STAT002" 100
end

assert_brief_at(0, "STAT002")
assert_brief_at(1, "STAT001")

int brief = getBriefTextAt(2)
assert_eq(brief, 0)
end
end

{$INCLUDE_ONCE helpers.inc}
Expand Down
20 changes: 17 additions & 3 deletions tests/cleo_tests/Text/0AD1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function tests
it_cs5("should not display message starting with ~z~ with subtitles off", test7_cs5)
it_cs4("should display message starting with ~z~ with subtitles off", test7_cs4)
it("should display NOW message even when game queue is full", test8_queue_full_now)

it_cs5("should dedup brief messages in loop", test9_dedup_loop_cs5)
return

:cleanup
Expand Down Expand Up @@ -117,7 +117,7 @@ function tests
print_formatted_now {format} "Flag test AD1 cs4" {time} 100
assert_brief_flag(false) // flag must NOT be reset for legacy scripts
end

function test7_cs5
toggle_subtitles(true)
print_formatted_now {text} "~z~Subtitle 1" {time} 100
Expand Down Expand Up @@ -167,6 +167,21 @@ function tests
// NOW message must jump to front despite full queue
assert_subtitle("Now Bypasses Q")
end

function test9_dedup_loop_cs5
print_formatted_now "STAT001" 1000
TIMERA = 0
while TIMERA < 1000
wait 0
print_formatted_now "STAT002" 100
end

assert_brief_at(0, "STAT002")
assert_brief_at(1, "STAT001")

int brief = getBriefTextAt(2)
assert_eq(brief, 0)
end
end

{$INCLUDE_ONCE helpers.inc}
Expand Down Expand Up @@ -200,4 +215,3 @@ function print_brief_messages
msg2 = 0
msg3 = 0
end

Loading