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

506 lines
15 KiB
C++

#include "shipwepselect.h"
#include "ship/ship.h"
#include "weapon/weapon.h"
#include "missionui/missionweaponchoice.h"
#include "network/multiteamselect.h"
namespace scripting {
namespace api {
ss_wing_info_h::ss_wing_info_h() : ss_wing(-1) {}
ss_wing_info_h::ss_wing_info_h(int l_wing) : ss_wing(l_wing) {}
bool ss_wing_info_h::isValid() const
{
return ss_wing >= 0;
}
ss_wing_info* ss_wing_info_h::getWing() const
{
return &Ss_wings[ss_wing];
}
int ss_wing_info_h::getWingIndex() const
{
return ss_wing;
}
ss_slot_info_h::ss_slot_info_h() {}
ss_slot_info_h::ss_slot_info_h(ss_slot_info* l_slots, int l_idx, int l_wing) : ss_slots(l_slots), ss_idx(l_idx), ss_wing(l_wing) {}
bool ss_slot_info_h::isValid() const
{
return ss_slots != nullptr;
}
ss_slot_info* ss_slot_info_h::getSlot() const
{
return &ss_slots[ss_idx];
}
int ss_slot_info_h::getSlotIndex() const
{
return ss_idx;
}
int ss_slot_info_h::getWingIndex() const
{
return ss_wing;
}
wss_unit_wep_h::wss_unit_wep_h() : ss_unit(-1) {}
wss_unit_wep_h::wss_unit_wep_h(int l_unit) : ss_unit(l_unit) {}
bool wss_unit_wep_h::isValid() const
{
return ss_unit >= 0;
// return ((ss_unit >= 0) && ((ss_bank >= 0) && (ss_bank <= 6)));
}
wss_unit* wss_unit_wep_h::getBank() const
{
return &Wss_slots[ss_unit];
}
wss_unit_count_h::wss_unit_count_h() : ss_unit(-1) {}
wss_unit_count_h::wss_unit_count_h(int l_unit) : ss_unit(l_unit) {}
bool wss_unit_count_h::isValid() const
{
return ss_unit >= 0;
// return ((ss_unit >= 0) && ((ss_bank >= 0) && (ss_bank <= 6)));
}
wss_unit* wss_unit_count_h::getBank() const
{
return &Wss_slots[ss_unit];
}
//**********HANDLE: loadout wing
ADE_OBJ(l_Loadout_Wing_Slot, ss_slot_info_h, "loadout_wing_slot", "Loadout wing slot handle");
ADE_VIRTVAR(isShipLocked, l_Loadout_Wing_Slot, nullptr, "If the slot's ship is locked", "boolean", "The slot ship status")
{
ss_slot_info_h current;
if (!ade_get_args(L, "o", l_Loadout_Wing_Slot.Get(&current))) {
return ADE_RETURN_NIL;
}
if (ADE_SETTING_VAR) {
LuaError(L, "This property is read only.");
}
return ade_set_args(L, "b", (current.getSlot()->status & WING_SLOT_SHIPS_DISABLED) != 0);
}
ADE_VIRTVAR(isWeaponLocked,
l_Loadout_Wing_Slot,
nullptr,
"If the slot's weapons are locked",
"boolean",
"The slot weapon status")
{
ss_slot_info_h current;
if (!ade_get_args(L, "o", l_Loadout_Wing_Slot.Get(&current))) {
return ADE_RETURN_NIL;
}
if (ADE_SETTING_VAR) {
LuaError(L, "This property is read only.");
}
return ade_set_args(L, "b", (current.getSlot()->status & WING_SLOT_WEAPONS_DISABLED) != 0);
}
ADE_VIRTVAR(isDisabled, l_Loadout_Wing_Slot, nullptr, "If the slot is not used in the current mission or disabled for the current player in multi", "boolean", "The slot disabled status")
{
ss_slot_info_h current;
if (!ade_get_args(L, "o", l_Loadout_Wing_Slot.Get(&current))) {
return ADE_RETURN_NIL;
}
if (ADE_SETTING_VAR) {
LuaError(L, "This property is read only.");
}
bool result = !current.getSlot()->in_mission;
if (Game_mode & GM_MULTIPLAYER) {
int index = (current.getWingIndex() * MAX_WING_SLOTS) + current.getSlotIndex();
result = multi_ts_disabled_slot(index);
}
return ade_set_args(L, "b", result);
}
ADE_VIRTVAR(isFilled, l_Loadout_Wing_Slot, "boolean", "If the slot is empty or filled. true if filled, false if empty", "boolean", "The slot filled status")
{
ss_slot_info_h current;
bool setv;
if (!ade_get_args(L, "o|b", l_Loadout_Wing_Slot.Get(&current), &setv)) {
return ADE_RETURN_NIL;
}
if (ADE_SETTING_VAR) {
if (Game_mode & GM_MULTIPLAYER) {
LuaError(L, "This property may not be modified in multiplayer.");
return ADE_RETURN_NIL;
}
if (!(current.getSlot()->status & WING_SLOT_DISABLED)) {
if (setv) {
current.getSlot()->status &= ~WING_SLOT_EMPTY;
current.getSlot()->status |= WING_SLOT_FILLED;
} else {
current.getSlot()->status &= ~WING_SLOT_FILLED;
current.getSlot()->status |= WING_SLOT_EMPTY;
}
} else {
LuaError(L, "You cannot empty a slot that is ship or weapon locked!");
}
}
return ade_set_args(L, "b", (current.getSlot()->status & WING_SLOT_EMPTY) == 0);
}
ADE_VIRTVAR(isPlayer, l_Loadout_Wing_Slot, nullptr, "If the slot is a player ship", "boolean", "The slot player status")
{
ss_slot_info_h current;
if (!ade_get_args(L, "o", l_Loadout_Wing_Slot.Get(&current))) {
return ADE_RETURN_NIL;
}
if (ADE_SETTING_VAR) {
LuaError(L, "This property is read only.");
}
bool result = (current.getSlot()->status & WING_SLOT_IS_PLAYER) != 0;
if (Game_mode & GM_MULTIPLAYER) {
int index = (current.getWingIndex() * MAX_WING_SLOTS) + current.getSlotIndex();
result = multi_ts_is_player_in_slot(index);
}
return ade_set_args(L, "b", result);
}
ADE_VIRTVAR(isPlayerAllowed, l_Loadout_Wing_Slot, nullptr, "If the slot is allowed to have player ship. In single player this is functionally the same as isPlayer.", "boolean", "The slot player allowed status")
{
ss_slot_info_h current;
if (!ade_get_args(L, "o", l_Loadout_Wing_Slot.Get(&current))) {
return ADE_RETURN_NIL;
}
if (ADE_SETTING_VAR) {
LuaError(L, "This property is read only.");
}
bool result = (current.getSlot()->status & WING_SLOT_IS_PLAYER) != 0;
if (Game_mode & GM_MULTIPLAYER) {
int index = (current.getWingIndex() * MAX_WING_SLOTS) + current.getSlotIndex();
result = multi_ts_is_player_allowed_in_slot(index);
}
return ade_set_args(L, "b", result);
}
// Probably don't need this one!
ADE_VIRTVAR(ShipClassIndex,
l_Loadout_Wing_Slot,
nullptr,
"The index of the ship class assigned to the slot",
"number",
"The ship class index")
{
ss_slot_info_h current;
if (!ade_get_args(L, "o", l_Loadout_Wing_Slot.Get(&current))) {
return ADE_RETURN_NIL;
}
if (ADE_SETTING_VAR) {
LuaError(L, "This property is read only.");
}
return ade_set_args(L, "i", (current.getSlot()->original_ship_class + 1));
}
ADE_VIRTVAR(Callsign,
l_Loadout_Wing_Slot,
nullptr,
"The callsign of the ship slot. In multiplayer this may be the player's callsign.",
"string",
"the callsign")
{
ss_slot_info_h current;
if (!ade_get_args(L, "o", l_Loadout_Wing_Slot.Get(&current))) {
return ADE_RETURN_NIL;
}
if (ADE_SETTING_VAR) {
LuaError(L, "This property is read only.");
}
char name[NAME_LENGTH];
ss_return_name(current.getWingIndex(), current.getSlotIndex(), name);
return ade_set_args(L, "s", name);
}
//**********HANDLE: loadout amount
ADE_OBJ(l_Loadout_Amount, wss_unit_count_h, "loadout_amount", "Loadout handle");
ADE_INDEXER(l_Loadout_Amount,
"number bank, number amount",
"Array of ship bank weapons. 1-3 are Primary weapons. 4-7 are Secondary weapons. Note that banks "
"that do not exist on the ship class are still valid here as a loadout slot. Also note that "
"primary banks will hold the value of 1 even if it is ballistic. If the amount to set is greater than "
"the bank's capacity then it will be set to capacity. Set to -1 to empty the slot. Amounts less than -1 will be set to -1.",
"number",
"Amount of the currently loaded weapon, -1 if bank has no weapon, or nil if the ship or index is invalid")
{
wss_unit_count_h current;
int idx = -1;
int amount;
if (!ade_get_args(L, "oi|i", l_Loadout_Amount.Get(&current), &idx, &amount))
return ADE_RETURN_NIL;
if (idx < 1 || idx > MAX_SHIP_WEAPONS) {
return ADE_RETURN_NIL;
};
idx--; // Convert from Lua
if (ADE_SETTING_VAR) {
if (Game_mode & GM_MULTIPLAYER) {
LuaError(L, "This property may not be modified in multiplayer.");
return ADE_RETURN_NIL;
}
ship_info* sip = &Ship_info[current.getBank()->ship_class];
int widx = current.getBank()->wep[idx];
if (widx >= 0 && widx < (int)Weapon_info.size()) {
weapon_info* wip = &Weapon_info[widx];
int capacity = 0;
// Calculate max capacity of the weapon in the current bank
if (wip->subtype == WP_LASER) {
capacity = 1;
} else {
capacity = wl_calc_missile_fit(widx, sip->secondary_bank_ammo_capacity[idx - MAX_SHIP_PRIMARY_BANKS]);
}
if (amount > capacity) {
amount = capacity;
}
if (amount < -1) {
amount = -1;
}
} else {
Warning(LOCATION, "Could not set amount for loadout weapon slot %i because the loaded weapon is invalid!", idx);
amount = -1;
}
current.getBank()->wep_count[idx] = amount;
}
return ade_set_args(L, "i", current.getBank()->wep_count[idx]);
}
ADE_FUNC(__len, l_Loadout_Amount, nullptr, "The number of weapon banks in the slot", "number", "The number of banks.")
{
return ade_set_args(L, "i", MAX_SHIP_WEAPONS);
}
//**********HANDLE: loadout weapon
ADE_OBJ(l_Loadout_Weapon, wss_unit_wep_h, "loadout_weapon", "Loadout handle");
ADE_INDEXER(l_Loadout_Weapon,
"number bank, number WeaponIndex",
"Array of ship bank weapons. 1-3 are Primary weapons. 4-7 are Secondary weapons. Note that banks "
"that do not exist on the ship class are still valid here as a loadout slot. When setting the weapon "
"it will be checked if it is valid for the ship and bank. If it is not then it will be set to -1 and the "
"amount will be set to -1. If it is valid for the ship then the amount is set to 0. Use .Amounts to set "
"the amount afterwards. Set to -1 to empty the slot.",
"number",
"index into Weapon Classes, 0 if bank is empty, -1 if the ship cannot carry the weapon, or nil if the ship or index is invalid")
{
wss_unit_wep_h current;
int bank = -1;
int wepIndex;
if (!ade_get_args(L, "oi|i", l_Loadout_Weapon.Get(&current), &bank, &wepIndex))
return ADE_RETURN_NIL;
if (bank < 1 || bank > MAX_SHIP_WEAPONS) {
return ADE_RETURN_NIL;
};
ship_info* sip = &Ship_info[current.getBank()->ship_class];
int bankType = -1;
// Decide if we're setting a valid primary or secondary bank on the current ship class
if (bank <= sip->num_primary_banks) {
bankType = WP_LASER;
} else if (bank <= (sip->num_secondary_banks + MAX_SHIP_PRIMARY_BANKS)) {
bankType = WP_MISSILE;
}
wepIndex--; // Convert from Lua
bank--; //Convert from Lua
if (ADE_SETTING_VAR) {
if (Game_mode & GM_MULTIPLAYER) {
LuaError(L, "This property may not be modified in multiplayer.");
return ADE_RETURN_NIL;
}
if (bankType == -1 || wepIndex > weapon_info_size()) {
LuaError(L, "The provided bank or weapon index is invalid!");
} else {
bool valid = false;
//First we check that this is a valid bank for the ship and that the script is not trying to empty the bank
if (wepIndex > -1) {
// Next we check that this is a weapon the ship can carry
if (eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[bank])) {
if (eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[bank][wepIndex])) {
valid = true;
}
} else {
if (eval_weapon_flag_for_game_type(sip->allowed_weapons[wepIndex])) {
valid = true;
}
}
}
if (valid) {
//Now we double check that we're putting a primary in a primary slot or secondary in a secondary slot
weapon_info* wip = &Weapon_info[wepIndex];
if (wip->subtype == bankType) {
current.getBank()->wep[bank] = wepIndex;
} else {
LuaError(L, "Cannot put a primary in a secondary bank or a secondary in a primary bank!");
}
current.getBank()->wep_count[bank] = 0;
}
else {
//If the ship cannot carry this weapon in this bank then we set it to -1 and the amount to -1
current.getBank()->wep[bank] = -1;
current.getBank()->wep_count[bank] = -1;
}
}
}
return ade_set_args(L, "i", current.getBank()->wep[bank] + 1);
}
ADE_FUNC(__len, l_Loadout_Weapon, nullptr, "The number of weapon banks in the slot", "number", "The number of banks.")
{
return ade_set_args(L, "i", MAX_SHIP_WEAPONS);
}
//**********HANDLE: loadout ship
ADE_OBJ_NO_MULTI(l_Loadout_Ship, int, "loadout_ship", "Loadout handle");
ADE_OBJ_VALIDATOR_RANGE(l_Loadout_Ship, MAX_WSS_SLOTS);
ADE_VIRTVAR(ShipClassIndex,
l_Loadout_Ship,
"number",
"The index of the Ship Class. When setting the ship class this will also set the weapons to empty slots. Use "
".Weapons and .Amounts to set those afterwards. Set to -1 to empty the slot and be sure to set the slot to empty "
"using Loadout_Wings[slot].isFilled.",
"number",
"The index or nil if handle is invalid")
{
int current;
int shipIndex;
if (!ade_get_args(L, "o|i", l_Loadout_Ship.Get(&current), &shipIndex)) {
return ADE_RETURN_NIL;
}
shipIndex--; //Convert from Lua
if (ADE_SETTING_VAR) {
if (Game_mode & GM_MULTIPLAYER) {
LuaError(L, "This property may not be modified in multiplayer.");
return ADE_RETURN_NIL;
}
if (shipIndex > -1) {
// If we're not trying to empty the slot then make sure it's a valid ship index
if (shipIndex > ship_info_size()) {
LuaError(L, "The provided ship index is invalid!");
} else {
Wss_slots[current].ship_class = shipIndex;
//Reset all currently loaded weapons here
for (int i = 0; i < MAX_SHIP_WEAPONS; i++) {
Wss_slots[current].wep[i] = -1;
Wss_slots[current].wep_count[i] = -1;
}
}
} else {
Wss_slots[current].ship_class = -1;
}
}
return ade_set_args(L, "i", (Wss_slots[current].ship_class + 1));
}
ADE_VIRTVAR(Weapons,
l_Loadout_Ship,
nullptr,
"Array of weapons in the loadout slot",
"loadout_weapon",
"The weapons array or nil if handle is invalid")
{
int current;
if (!ade_get_args(L, "o", l_Loadout_Ship.Get(&current))) {
return ADE_RETURN_NIL;
}
if (ADE_SETTING_VAR) {
LuaError(L, "This property is read only.");
}
return ade_set_args(L, "o", l_Loadout_Weapon.Set(wss_unit_wep_h(current)));
}
ADE_VIRTVAR(Amounts,
l_Loadout_Ship,
nullptr,
"Array of weapon amounts in the loadout slot",
"loadout_amount",
"The weapon amounts array or nil if handle is invalid")
{
int current;
if (!ade_get_args(L, "o", l_Loadout_Ship.Get(&current))) {
return ADE_RETURN_NIL;
}
if (ADE_SETTING_VAR) {
LuaError(L, "This property is read only.");
}
return ade_set_args(L, "o", l_Loadout_Amount.Set(wss_unit_count_h(current)));
}
//**********HANDLE: loadout wing
ADE_OBJ(l_Loadout_Wing, ss_wing_info_h, "loadout_wing", "Loadout handle");
ADE_INDEXER(l_Loadout_Wing,
"number idx",
"Array of loadout wing slot data",
"loadout_wing_slot",
"loadout slot handle, or invalid handle if index is invalid")
{
ss_wing_info_h current;
int idx;
if (!ade_get_args(L, "oi", l_Loadout_Wing.Get(&current), &idx))
return ade_set_error(L, "s", "");
idx--; // Convert to Lua's 1 based index system
return ade_set_args(L, "o", l_Loadout_Wing_Slot.Set(ss_slot_info_h(current.getWing()->ss_slots, idx, current.getWingIndex())));
}
//This seems superfluous because the loadout wing slots is always max but not only is it something scripters may expect,
//but also if max wings is ever expanded this will automatically reflect that
ADE_FUNC(__len, l_Loadout_Wing, nullptr, "The number of slots in the wing", "number", "The number of slots.")
{
return ade_set_args(L, "i", MAX_WING_SLOTS);
}
ADE_VIRTVAR(Name, l_Loadout_Wing, nullptr, "The name of the wing", "string", "The wing")
{
ss_wing_info_h current;
if (!ade_get_args(L, "o", l_Loadout_Wing.Get(&current))) {
return ADE_RETURN_NIL;
}
if (ADE_SETTING_VAR) {
LuaError(L, "This property is read only.");
}
wing* wp;
wp = &Wings[current.getWing()->wingnum];
return ade_set_args(L, "s", wp->name);
}
} // namespace api
} // namespace scripting