The color-shift opcode copies a run of palette entries from the previous
frame's palette to the current one. libsmacker rejected the copy when the
source and destination ranges overlapped, aborting the whole palette decode
and leaving the palette half-updated -- so every frame from the first such
opcode on rendered with a mix of the new and stale palette.
The overlap check is bogus: the copy reads from oldPalette, a snapshot taken
at the top of the function, and writes into s->palette, a separate buffer, so
overlapping ranges are harmless (and it is a memmove regardless). ffmpeg and
the original SMACKW32.DLL have no such check.
Several of the videos that ship with the game (Rebel_cr, Omerta, Prague) use
these overlapping copies, and showed washed-out colours with the previous
frame's palette bleeding through. Drop the overlap clause, keep the real
256-entry bounds checks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drops the last piece of the proprietary Smacker SDK: SMACKW32.LIB, its
header sgp/SMACK.H, and with them the SMACKW32.DLL the game needed at
runtime. Bink is untouched and still needs binkw32.
The two libraries divide the work differently, so Cinematics.cpp changes
more than the call names. SMACKW32.DLL played a file: it kept the playback
clock (SmackWait), blitted converted 16bpp pixels into a surface we handed
it (SmackToBuffer) and pushed the audio to DirectSound itself. libsmacker
only decodes, so this module now:
- keeps the clock. Frames are advanced against GetTickCount() from the
moment the flic starts, skipping frames if the game fell behind, which
is what SmackWait/SmackDoFrame did internally.
- blits itself. Frames come out 8bpp palettised, so the palette is
converted with Get16BPPColor() and the frame is written into the frame
buffer, clipped to the screen. The Y-scaling flags are not handled;
none of the videos shipping with the game set them.
- plays the audio itself, through the SoundPlayFromBuffer() added earlier.
libsmacker hands out audio a frame at a time, so the whole track is
decoded up front, with the video track disabled to keep that pass cheap,
and given a WAV header so the sound module can take it.
libsmacker reads from memory, which lets the file be read straight out of
the VFS. That removes the dance where the .smk was first copied to a real
file under Temp/ because SmackOpen() could only open a path.
SmkInitialize() loses its window and screen size arguments, which only
existed for SmackBufferOpen(). SmkOpenFlic(), SmkSetBlitPosition() and
SmkGetFreeFlic() are no longer declared in the header; nothing outside the
module called them. SMKFLIC is now opaque, Intro.cpp only holds pointers.
The unused SMACK.H include in Cinematics Bink.cpp goes away as well.
Tested by running the Sirtech splash screen (SPLASHSCREEN.SMK, 640x480,
15 fps, one 44.1kHz 16 bit stereo track) under Wine: picture, palette,
placement and playback speed are right.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor
Smacker video carries its audio as PCM that the video decoder produces
itself, so there is no file for the sound module to open. Add
SoundPlayFromBuffer(), which streams from a caller-owned memory buffer.
It deliberately does not go through the sample cache: a full length intro
video decodes to close to 20 MB of PCM, which does not fit in the cache's
memory budget and would evict everything else on the way to failing. The
channel bookkeeping already has a notion of a stream with no cache slot
behind it (uiSample == -1), which is what streamed files use, so the buffer
simply stays the caller's property for as long as the sound plays.
To share the parameter handling, everything SoundStartStream() did after
opening the stream moves unchanged into SoundStartOpenedStream().
No caller yet.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
libsmacker is an open reimplementation of the parts of smackw32.dll needed
to get frames and audio out of an .smk file. Vendoring it here is the first
step to dropping the proprietary SMACKW32.LIB/SMACKW32.DLL dependency.
Nothing links against it yet.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vfs_debug.h forward declares std::exception under #ifdef _MSC_VER. clang-cl
defines _MSC_VER too, and rejects the declaration:
vfs_debug.h(34,15): error: forward declaration of class cannot have a
nested name specifier
so any tool built on clang stops at the first file that reaches sgp/DEBUG.H,
which is nearly all of them. Guarding on __clang__ as well leaves the MSVC
build byte for byte identical and lets include-what-you-use parse the tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tiledef.h is included by 1900 objects, and each one was compiling a tentative
definition of gFullBaseTileValues rather than a declaration of it. The array
is defined once in TileDat.cpp, and every other global in the same block is
already declared extern; this one was missed.
MSVC accepts it and merges the tentative definitions, so nothing was visibly
broken. A conforming front end rejects it outright:
tiledef.h(180,12): error: definition of variable with array type needs
an explicit size or an initializer
which is what turned it up.
Verification: ninja -C build -k 0, all four applications link.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
to avoid having to ship a whole ja2.ini for languages that
currently don't
command-line /language:german still works, but INI file is
prioritized
format:
```
[Language]
LANGUAGE = ITALIAN
```
InitializeFileDatabase, the only reader of gGameLibaries[], is called
nowhere in the tree — VFS replaced this legacy loader. Delete the
7 #ifdef GERMAN/POLISH/DUTCH/ITALIAN/RUSSIAN/FRENCH/CHINESE entries
outright rather than runtime-selecting them.
NPC.h: two commented-out #if defined(CRIPPLED_VERSION)||defined(RUSSIAN)
triplets. AimMembers.cpp: two commented-out #ifdef POLISH character-remap
switch blocks. All already-dead. Verify grep for language macros outside
i18n/ now returns only unrelated hits (filename literal, dev-tool CLI flag,
stray comment, historical prose) — no #ifdef guards left.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <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
"Corpses are not properly shown even if within mercs' vision due to the check only
running intermittently since we're not constantly running the reveal roofs and items
function. In addition, mercs such as Raven who have the quoteexpheadshotonly set to true,
will NEVER reveal any corpses"
Revert "Remove extra line"
This reverts commit 1e1eb88864.
Revert "Move visibility check after already included pCorpse check"
This reverts commit 72e586a378.
Revert "Make 3rd party corpses invisible till they in fov"
This reverts commit 07dbf9c848.
Same logic but cleaned up version of https://github.com/1dot13/source/pull/602 which Tais contributed with Claude Code's help
Most calls to SOLDIERTYPE::IsDragging() aren't supposed to have a side effect of cancelling a drag, being simple queries.
Changing the default to reflect that is right move.
Assisted-by: Claude:
Hovering mouse over an option long enough that the popup text appears and scrolling to last page with mouse wheel would run into assertion error in gprintfdirty() Assert(pFontString!=NULL); if the option was one of the last ones that didn't have a text box in the last option page.
The LayerProp palette lookup was gated on the *filter* attribute instead
of the *palette* attribute -- the filter checks from the line above were
copy-pasted verbatim. As a result, any LayerProp with an empty filter but
a real palette never resolved its palette table: paletteTable stayed NULL
and the layer fell back to the merc's shade table.
The most visible victim is the heli fast-rope arrival layer
(filter="" palette="hats"): rendered with the merc palette, the rope's
pixel indices (45-81, 166-172) hit the merc's unreplaced orange/red glow
ramp instead of the rope's own tan/green, painting orange specks along the
rope. Testing the palette attribute restores the intended "hats" palette
and the correct rope colours; it also fixes any other empty-filter layers.
Co-Authored-By: Claude Opus 4.7