Files
Goober5000andClaude Fable 5.1 37b0b6ea82 Standardize isValid across the Lua API by making the types auto-generate it (#7761)
* Automatically generate the isValid() Lua API function for handle types

If an ADE object's stored type has an isValid() member, ade_obj now registers an isValid function for it automatically.  An explicit ADE_FUNC(isValid, ...) declared after the ADE_OBJ replaces the generated one, so types with special semantics (e.g. sound) keep their own.  The 67 hand-written functions that merely delegated to the member are removed, and 26 types that previously exposed no isValid() gain one.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

* Add ADE_OBJ_VALIDATOR for index-based Lua handles

Int-index handles (shipclass, weaponclass, team, etc.) can now declare their validity predicate with a single ADE_OBJ_VALIDATOR or ADE_OBJ_VALIDATOR_RANGE line after the ADE_OBJ.  The registered validator drives the generated isValid() function and is also honored by ade_set_args when the mod table asks for nil instead of invalid objects, which previously never applied to these handles.  The 19 hand-written range-check functions are replaced, and loadout_ship, default_primary, default_secondary, and support_rearm_pool_team gain isValid().  Add a validator to HudGauge while we're at it.

Also fully qualify the scripting namespace in LuaConvert.h so its templates can be instantiated from the unit tests.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

---------

Co-authored-by: Claude Fable 5.1 <[email protected]>
2026-09-08 10:50:53 -04:00

330 lines
9.0 KiB
C++

//
//
#include "scripting/api/libs/graphics.h"
#include "streaminganim.h"
#include "freespace.h"
namespace scripting {
namespace api {
bool streaminganim_h::isValid() const {
return (ga.num_frames > 0);
}
streaminganim_h::streaminganim_h(const char* real_filename) {
char filename[MAX_FILENAME_LEN];
// make sure no one passed an extension
memset(filename, 0, MAX_FILENAME_LEN);
strncpy(filename, real_filename, MAX_FILENAME_LEN - 1);
char* p = strrchr(filename, '.');
if (p) {
mprintf(("Someone passed an extension to streaminganim_h for file '%s'\n", real_filename));
*p = 0;
}
generic_anim_init(&ga, filename);
}
streaminganim_h::~streaminganim_h() {
// don't bother to check if valid before unloading
// generic_anim_unload has safety checks
generic_anim_unload(&ga);
}
streaminganim_h::streaminganim_h(streaminganim_h&& other) noexcept
: ga(other.ga)
{
// Reset the other instance so that we own the only instance
generic_anim_init(&other.ga, nullptr);
}
streaminganim_h& streaminganim_h::operator=(streaminganim_h&& other) noexcept {
if (this == &other)
return *this;
generic_anim_unload(&ga);
ga = other.ga;
// Reset the other instance so that we own the only instance
generic_anim_init(&other.ga, nullptr);
return *this;
}
//**********HANDLE: streamingAnimation
ADE_OBJ(l_streaminganim, streaminganim_h, "streaminganim", "Streaming Animation handle");
ADE_VIRTVAR(Loop, l_streaminganim, "boolean", "Make the streaming animation loop.", "boolean", "Is the animation looping, or nil if anim invalid")
{
streaminganim_h* sah;
bool loop = false;
if(!ade_get_args(L, "o|b", l_streaminganim.GetPtr(&sah), &loop))
return ADE_RETURN_NIL;
if(!sah->isValid())
return ADE_RETURN_NIL;
if (ADE_SETTING_VAR) {
if (loop == false)
sah->ga.direction |= GENERIC_ANIM_DIRECTION_NOLOOP;
else
sah->ga.direction &= ~GENERIC_ANIM_DIRECTION_NOLOOP;
}
return ((sah->ga.direction & GENERIC_ANIM_DIRECTION_NOLOOP) == false ? ADE_RETURN_TRUE : ADE_RETURN_FALSE);
}
ADE_VIRTVAR(Pause, l_streaminganim, "boolean", "Pause the streaming animation.", "boolean", "Is the animation paused, or nil if anim invalid")
{
streaminganim_h* sah;
bool pause = false;
if(!ade_get_args(L, "o|b", l_streaminganim.GetPtr(&sah), &pause))
return ADE_RETURN_NIL;
if(!sah->isValid())
return ADE_RETURN_NIL;
if (ADE_SETTING_VAR) {
if (pause)
sah->ga.direction |= GENERIC_ANIM_DIRECTION_PAUSED;
else
sah->ga.direction &= ~GENERIC_ANIM_DIRECTION_PAUSED;
}
return ((sah->ga.direction & GENERIC_ANIM_DIRECTION_PAUSED) ? ADE_RETURN_TRUE : ADE_RETURN_FALSE);
}
ADE_VIRTVAR(Reverse, l_streaminganim, "boolean", "Make the streaming animation play in reverse.", "boolean", "Is the animation playing in reverse, or nil if anim invalid")
{
streaminganim_h* sah;
bool reverse = false;
if(!ade_get_args(L, "o|b", l_streaminganim.GetPtr(&sah), &reverse))
return ADE_RETURN_NIL;
if(!sah->isValid())
return ADE_RETURN_NIL;
if (ADE_SETTING_VAR) {
if (reverse)
sah->ga.direction |= GENERIC_ANIM_DIRECTION_BACKWARDS;
else
sah->ga.direction &= ~GENERIC_ANIM_DIRECTION_BACKWARDS;
}
return ((sah->ga.direction & GENERIC_ANIM_DIRECTION_BACKWARDS) ? ADE_RETURN_TRUE : ADE_RETURN_FALSE);
}
ADE_VIRTVAR(Grayscale, l_streaminganim, "boolean", "Whether the streaming animation is drawn as grayscale multiplied by the current color (the HUD method).", "boolean", "Boolean flag")
{
streaminganim_h* sah;
bool grayscale = false;
if (!ade_get_args(L, "o|b", l_streaminganim.GetPtr(&sah), &grayscale))
return ADE_RETURN_NIL;
if (!sah->isValid())
return ADE_RETURN_NIL;
if (ADE_SETTING_VAR) {
LuaError(L, "This variable can only be set before the anim is loaded.");
}
return sah->ga.use_hud_color ? ADE_RETURN_TRUE : ADE_RETURN_FALSE;
}
ADE_FUNC(getFilename, l_streaminganim, NULL, "Get the filename of the animation", "string", "Filename or nil if invalid")
{
streaminganim_h* sah;
if (!ade_get_args(L, "o", l_streaminganim.GetPtr(&sah)))
return ADE_RETURN_NIL;
if(!sah->isValid())
return ADE_RETURN_NIL;
return ade_set_args(L, "s", sah->ga.filename);
}
ADE_FUNC(getFrameCount, l_streaminganim, nullptr, "Get the number of frames in the animation.", "number",
"Total number of frames")
{
streaminganim_h* sah;
if (!ade_get_args(L, "o", l_streaminganim.GetPtr(&sah)))
return ADE_RETURN_NIL;
if(!sah->isValid())
return ADE_RETURN_NIL;
return ade_set_args(L, "i", sah->ga.num_frames);
}
ADE_FUNC(getFrameIndex, l_streaminganim, nullptr, "Get the current frame index of the animation", "number",
"Current frame index")
{
streaminganim_h* sah;
if (!ade_get_args(L, "o", l_streaminganim.GetPtr(&sah)))
return ADE_RETURN_NIL;
if(!sah->isValid())
return ADE_RETURN_NIL;
int cframe = sah->ga.current_frame;
if (cframe < 0 || cframe >= sah->ga.num_frames) {
return ADE_RETURN_NIL;
}
return ade_set_args(L, "i", ++cframe); // C++ to LUA array index
}
ADE_FUNC(getHeight, l_streaminganim, nullptr, "Get the height of the animation in pixels", "number",
"Height or nil if invalid")
{
streaminganim_h* sah;
if (!ade_get_args(L, "o", l_streaminganim.GetPtr(&sah)))
return ADE_RETURN_NIL;
if(!sah->isValid())
return ADE_RETURN_NIL;
return ade_set_args(L, "i", sah->ga.height);
}
ADE_FUNC(getWidth, l_streaminganim, nullptr, "Get the width of the animation in pixels", "number",
"Width or nil if invalid")
{
streaminganim_h* sah;
if (!ade_get_args(L, "o", l_streaminganim.GetPtr(&sah)))
return ADE_RETURN_NIL;
if(!sah->isValid())
return ADE_RETURN_NIL;
return ade_set_args(L, "i", sah->ga.width);
}
ADE_FUNC(preload, l_streaminganim, NULL, "Load all apng animations into memory, enabling apng frame cache if not already enabled", "boolean", "true if preload was successful, nil if a syntax/type error occurs")
{
streaminganim_h* sah;
if(!ade_get_args(L, "o", l_streaminganim.GetPtr(&sah)))
return ADE_RETURN_NIL;
if (sah->ga.type != BM_TYPE_PNG)
return ADE_RETURN_NIL;
sah->ga.png.anim->preload();
return ADE_RETURN_TRUE;
}
ADE_FUNC(process, l_streaminganim, "[number x1, number y1, number x2, number y2, number u0, number v0, number u1, number v1, number alpha, boolean draw]",
"Processes a streaming animation, including selecting the correct frame & drawing it.",
"boolean", "True if processing was successful, otherwise nil")
{
streaminganim_h* sah;
generic_extras ge;
int x1 = 0, y1 = 0;
int x2 = INT_MAX, y2 = INT_MAX;
if(!ade_get_args(L, "o|iiiifffffb", l_streaminganim.GetPtr(&sah),
&x1, &y1, &x2, &y2,
&ge.u0, &ge.v0, &ge.u1, &ge.v1, &ge.alpha,
&ge.draw))
return ADE_RETURN_NIL;
if(!sah->isValid())
return ADE_RETURN_NIL;
if (sah->ga.use_hud_color)
{
float scale_x = (x2 != INT_MAX) ? i2fl(x2 - x1) / i2fl(sah->ga.width) : 1.0f;
float scale_y = (y2 != INT_MAX) ? i2fl(y2 - y1) / i2fl(sah->ga.height) : 1.0f;
int base_w = gr_screen.max_w;
int base_h = gr_screen.max_h;
gr_set_screen_scale(fl2ir(base_w / scale_x), fl2ir(base_h / scale_y));
// generic_anim_render can't use generic_extras, says the function
// but we can at least do scaling, as above
generic_anim_render(&sah->ga, flFrametime, fl2ir(x1 / scale_x), fl2ir(y1 / scale_y), false);
gr_reset_screen_scale();
}
else
{
ge.width = sah->ga.width;
ge.height = sah->ga.height;
if (x2 != INT_MAX) ge.width = x2 - x1;
if (y2 != INT_MAX) ge.height = y2 - y1;
// use the currently active resize mode for scripting
// (generic_anim_render will use this field when ge is provided)
ge.resize_mode = get_resize_mode();
generic_anim_render(&sah->ga, flFrametime, x1, y1, false, &ge);
}
return ADE_RETURN_TRUE;
}
ADE_FUNC(reset, l_streaminganim, nullptr, "Reset a streaming animation back to its 1st frame", "boolean", "True if successful, otherwise nil")
{
streaminganim_h* sah;
if(!ade_get_args(L, "o", l_streaminganim.GetPtr(&sah)))
return ADE_RETURN_NIL;
sah->ga.png.anim->goto_start();
sah->ga.current_frame = 0;
sah->ga.anim_time = 0.0f;
sah->ga.png.previous_frame_time = 0.0f;
return ADE_RETURN_TRUE;
}
ADE_FUNC(timeLeft, l_streaminganim, nullptr, "Get the amount of time left in the animation, in seconds", "number", "Time left in secs or nil if invalid")
{
streaminganim_h* sah;
if (!ade_get_args(L, "o", l_streaminganim.GetPtr(&sah)))
return ADE_RETURN_NIL;
if(!sah->isValid())
return ADE_RETURN_NIL;
float timeLeft = 0.0f;
if ((sah->ga.direction & GENERIC_ANIM_DIRECTION_BACKWARDS))
timeLeft = sah->ga.anim_time;
else
timeLeft = sah->ga.total_time - sah->ga.anim_time;
// there was an anim that completed with less than 1/1000 seconds left;
// let's round under 1/200 seconds because nobody plays at 200 FPS
if (timeLeft < 0.005)
timeLeft = 0.0f;
return ade_set_args(L, "f", timeLeft);
}
ADE_FUNC(unload, l_streaminganim, NULL, "Unloads a streaming animation from memory", NULL, NULL)
{
streaminganim_h* sah;
if(!ade_get_args(L, "o", l_streaminganim.GetPtr(&sah)))
return ADE_RETURN_NIL;
// don't bother to check if valid before unloading
// generic_anim_unload has safety checks
generic_anim_unload(&sah->ga);
return ADE_RETURN_TRUE;
}
}
}