Four string literals carry the bytes A3 A5, which is GBK for U+FF05, the
fullwidth percent sign. All four sit in g_lang == i18n::Lang::zh branches
added by "Chinese specific update (by zww from tbs)", and each has an ASCII
sibling in the else branch using an ordinary percent.
clang-cl decodes source as UTF-8, where A3 is not a valid lead byte:
error: illegal character encoding in string literal
A raw high byte in a literal means whatever the compiler's source charset
says it means, so this text has never been portable. Asking the cl we build
with what it makes of L"%d%<A3><A5>%%" gives a 7-character string whose
fourth character is 163 -- it reads the bytes as Latin-1 and produces two
characters, £ and ¥, not the one the author wrote. The same probe on
L"%d%%%%" gives 6 characters with U+FF05 in that position.
So the Chinese text has been rendering as garbage in any build not made on a
CP936 machine, this one included. Writing the character as % says
exactly which character is meant regardless of the compiler's charset, and
restores what the branch was written to display.
The format grammar is untouched. In each of these strings the character is
preceded by a percent, making %<fullwidth percent> an unknown conversion
specification that prints the character following it -- the same trick the
else branches spell as %%. Where a string is formatted twice, once into a
buffer and again as the format argument of mprintf, the doubling in the
sibling literal (%%%%) is what survives two passes; that pairing is
unchanged.
Both files are now pure ASCII:
python3 -c "print(sum(1 for b in open(F,'rb').read() if b>0x7f))"
The parse sweep drops from 5 error sites in 3 files to 1 in 1 -- the inline
assembly in sgp/vobject_blitters.cpp is all that is left.
std::uniform_int_distribution::operator() takes its engine by non-const
reference, and the call passes a freshly constructed std::mt19937 temporary.
MSVC binds it anyway; clang-cl reports
error: no matching function for call to object of type
'std::uniform_int_distribution<>'
note: candidate function not viable: expects an lvalue for 1st argument
Naming the engine is enough. Behaviour is unchanged: the engine is still
seeded from std::random_device and still discarded after the single draw.
DIRECTIVE_TEXT builds four enumerator names from one directive name:
#define DIRECTIVE_TEXT(id) RCDT_##id##, RCDT_##id##_EFFECT, ...
The last ## on the first name has nothing to paste onto but the comma that
follows, and RCDT_GATHER_SUPPLIES, is not a token. The other three pastes are
well formed; only this one runs off the end of the name.
MSVC concatenates the text and re-lexes it, so it recovers the identifier and
the comma and produces the right enumerators; clang diagnoses it. Removing the
stray ## gives the same ten enumerations, and MISSION_TEXT just below already
spells it this way.
Verification:
grep -rn '##[A-Za-z_]*##[,)]' --include=*.h --include=*.cpp . # nothing
ninja -C build parse # token-paste class gone: 55 -> 45 sites
ninja -C build -k 0 # Release, four applications, green
ninja -C build-debug -k 0 # Debug, four applications, green
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A braced initialiser does not allow a narrowing conversion unless the value
is a constant expression the compiler can prove fits. Every site here holds a
runtime value: WORLD_COLS is the global guiWorldCols, the laptop screen
coordinates are built from iScreenWidthOffset, the sector limits come from the
externalised options. MSVC accepts all of it silently; clang rejects it, and
it is ill-formed.
Nothing about the generated code changes. MSVC was already performing these
conversions; the casts only say so out loud, at the 73 places where it was
happening implicitly.
The invariant: each site is wrapped in a static_cast to the element type the
initialiser already had, and nothing else is touched. No type is widened, no
expression is reassociated, no value is clamped or checked. static_cast rather
than a C-style cast so that a later reader can grep for the narrowings, and so
that none of these can quietly become a reinterpret_cast if a type changes.
Verification:
# every changed line differs only by inserted casts and their parentheses
git show HEAD -U0 | grep '^[+-][^+-]' |
sed -E 's/static_cast<[A-Za-z0-9_]+>//g; s/[()]//g; s/^[+-]//' |
sort | uniq -c | awk '$1 % 2' # no output
git show HEAD -U0 | grep -c '^+[^+]' # 38 lines, 73 casts
ninja -C build parse # narrowing class gone: 129 -> 91 sites
ninja -C build -k 0 # Release, four applications, green
ninja -C build-debug -k 0 # Debug, four applications, green
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
POPUP_OPTION copies the name it is given -- `this->name = *newName` into a
std::wstring member -- but its constructor took a std::wstring*, so 64 call
sites spelled the argument `&std::wstring( pStr )`. Taking the address of a
temporary is ill-formed, and clang refuses it outright:
error: taking the address of a temporary object of type 'std::wstring'
It happens to work under MSVC because the temporary outlives the call, dying
at the end of the full expression rather than before the copy. Nothing was
corrupt; the code was just spelling "pass me a string" in a way the language
does not allow.
A const reference says what these functions actually want, so the call sites
lose the &, and the two places that allocated a string purely to have an
address to pass -- POPUP_SUB_POPUP_OPTION's default constructor, which carried
a "TODO: possible memmory leak!" saying as much, and a "Dummy generator"
option -- stop leaking one.
Converted: POPUP_OPTION's constructor and setName, POPUP::addOption and
addSubMenuOption, and both POPUP_SUB_POPUP_OPTION constructors. The popupDef
family in popup_definition.* keeps its std::wstring* because it owns what it
is handed and stores the pointer; that is a different design and a different
change. The three calls it makes into the converted API now dereference.
Verification:
grep -rn '&std::wstring' --include=*.cpp --include=*.h . # nothing
ninja -C build parse # no popup_* or SkillMenu sites remain
ninja -C build -k 0 # Release, four applications, green
ninja -C build-debug -k 0 # Debug, four applications, green
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
squashed into a single commit because doing it in steps that all
compiled involved a lot of boilerplate that is pointless to commit to
the repository. GH still has the PR for reference.
g_lang, MAX_MESSAGES_ON_MAP_BOTTOM, and GetLanguagePrefix() were
compile-time constants selected by the ENGLISH/GERMAN/... build
define. They're now runtime, defaulting to the same per-exe value the
define used to pick, and overridable at startup from [Ja2 Settings]
LANGUAGE in Ja2.ini.
-
XMLTacticalMessages is filled at runtime from NewTacticalMessages.xml,
never from compiled-in per-language data: all 8 per-language definitions
were the identical all-zero { L"" }. Delete them and define one shared
buffer in Utils/XML_Language.cpp (the loader) instead of pointer-rebinding
it like the static tables — this drops 9 dead 800KB zero-buffers (8
namespaced copies in LanguageStrings.cpp plus the standalone one) and
leaves no bind-ordering hazard for the XML load path.
-
ExportStrings.cpp privately recompiled one language's full text table
by #including the raw _<LANG>Text.cpp inside namespace Loc, keyed off
the exe-level ENGLISH/GERMAN/... compile macro (whichever the build
happened to select). That's redundant with the pointer globals every
other subsystem already uses (Text.h / LanguageStrings.cpp).
Drop the private copy; the unqualified table names in Loc::ExportStrings
now resolve to the global pointer externs, which BindLanguageStrings has
already rebound to the runtime g_lang by the time this runs (EXPORT_STRINGS
ini flag, checked after GetRuntimeSettings in sgp.cpp). gs_Lang (used only
by Loc::Translate for the Polish/Russian byte remap on raw .edt exports) is
now derived from g_lang via ToLocLanguage, so the export always matches
whatever language is actually active instead of a compile-time pick.
-
Editor/popupmenu.cpp and Strategic/Scheduling.cpp kept their own
call-site extern of gszScheduleActions as CHAR16[NUM_SCHEDULE_ACTIONS][20]
after the real definition changed into a rebindable pointer
(CHAR16 (*)[20], LanguageStrings.cpp). MSVC decays the outer array
dimension when mangling globals, so both declarations produce the same
symbol (?gszScheduleActions@@3PAY0BE@_WA) and the mismatch linked
silently -- but the array-typed TUs then indexed the 4-byte pointer
slot itself as string data, so the editor schedule popup and the map
schedule message text read garbage.
-
add text.def to be single-source of truth on symbol names and use it
.much simpler, less error prone if adding more strings
-
add pseudo interface for language state. MAX_SAGES_ON_BOTTOM must always
change in lockstep with g_lang. this isn't foolproof but better
---------
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Jerry Melo complains about too many mercs if player has hired over 6 mercs and refuses to fly. This allows player to dismiss mercs until the pilot is happy.
The mapscreen "drop everything" hotkey (Shift+W) looped over every
inventory slot from HELMETPOS to NUM_INV_SLOTS and dumped each one.
Madlab's robot stores its installed gear in those same slots - ammo (14),
targeting (15), chassis/armour (16) and utility (17). Dropping these left
the robot defenceless, and the player cannot re-install the components
through normal inventory, so the loss was permanent.
Skip the robot's component slots in the drop loop; only its general cargo
slot (ROBOT_INVENTORY_SLOT) may still be emptied. Regular mercs are
unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Load required mapinfo for checking suitable gridnos
* Modify SOLDIERTYPE::DropSectorEquipment( ) to take an external gridno when placing items into an unloaded sector
- These gameclock variables were not updated properly so it created a discrepancy between it and the proper gameclock variables, and it was causing issues with the lua hourly update script
- Function that heals boxers after they finish resting
Emails from new GameDir file TableData/Email/Emails.xml can be added via lua scripting using calls to AddEmailFromXML & AddPreReadEmailFromXML. The order of emails in Emails.xml acts as the index to be referenced and provided when calling lua functions.
An error message will be displayed if an email is added with an out-of-scope index but game will be able to continue.
Localization is supported and was tested on Chinese localization.
* Clicking destination column in teamlist for a squad in a helicopter will switch to first airspace view that shows safe / unsafe air sectors instead of the second sector.
* Clicking destination column in teamlist for a squad not in a helicopter while in either airspace view will now correctly turn it off