diff --git a/config/fxdata/lua/classes/Thing.lua b/config/fxdata/lua/classes/Thing.lua index 290c098fa3..12e5e25567 100644 --- a/config/fxdata/lua/classes/Thing.lua +++ b/config/fxdata/lua/classes/Thing.lua @@ -24,6 +24,7 @@ ---@field health integer ---@field max_health integer If the health gets beyond this point, it will be decreased. ---@field picked_up boolean +---@field lua_data table A table that can be used to store any Lua data on the thing. arbitrary data accessible from all Lua code. if not Thing then Thing = {} end ---@class Object: Thing diff --git a/config/fxdata/lua/triggers/Builtins.lua b/config/fxdata/lua/triggers/Builtins.lua index 14614b61e9..9833a14872 100644 --- a/config/fxdata/lua/triggers/Builtins.lua +++ b/config/fxdata/lua/triggers/Builtins.lua @@ -2,7 +2,7 @@ -- Entry points for engine-triggered events (e.g. OnPowerCast, OnGameTick). -- These functions are called by the C engine and dispatch event data to the Lua trigger system. ----@alias event_type "PowerCast"|"Death"|"SpecialActivated"|"GameTick"|"ChatMsg"|"DungeonDestroyed"|"TrapPlaced"|"ApplyDamage"|"LevelUp"|"Rebirth"|"SlabKindChange"|"SlabOwnerChange"|"RoomOwnerChange"|"ShotHitThing"|"Destroyed" +---@alias event_type "PowerCast"|"Death"|"SpecialActivated"|"GameTick"|"ChatMsg"|"DungeonDestroyed"|"TrapPlaced"|"ApplyDamage"|"LevelUp"|"Rebirth"|"SlabKindChange"|"SlabOwnerChange"|"RoomOwnerChange"|"ShotHitThing"|"Destroyed"|"ThingDeleted" --- Called when a spell is cast on a unit --- @param pwkind power_kind @@ -153,4 +153,12 @@ function OnShotHit(shot, shooter, target, next_stl_x, next_stl_y, rebound_hit) eventData.next_stl_y = next_stl_y eventData.rebound_hit = rebound_hit ProcessEvent("ShotHitThing",eventData) +end + +--- Called when a thing is deleted +---@param thing Thing the thing being deleted. +function OnThingDeleted(thing) + local eventData = {} + eventData.thing = thing + ProcessEvent("ThingDeleted",eventData) end \ No newline at end of file diff --git a/config/fxdata/lua/triggers/Events.lua b/config/fxdata/lua/triggers/Events.lua index 0d2855920c..12f8a54565 100644 --- a/config/fxdata/lua/triggers/Events.lua +++ b/config/fxdata/lua/triggers/Events.lua @@ -265,4 +265,18 @@ function RegisterOnShotHitEvent(action, shooter_type, target_type, shot_type) TriggerAddCondition(trigger, function(eventData, triggerData) return eventData.shot.model == triggerData.shot_type end) end return trigger +end + +---Triggers when a shot hits something +---eventData.thing contains the deleted thing +---@param action function|string the function to call when the event happens +---@param thing? Thing the thing that was deleted (nil for any) +---@return table +function RegisterThingDeletedEvent(action, thing) + local trigData = {thing = thing} + local trigger = CreateTrigger("ThingDeleted", action, trigData) + if thing then + TriggerAddCondition(trigger, function(eventData, triggerData) return eventData.thing == triggerData.thing end) + end + return trigger end \ No newline at end of file diff --git a/src/lua_api_things.c b/src/lua_api_things.c index 797ef7ec66..55b144d722 100644 --- a/src/lua_api_things.c +++ b/src/lua_api_things.c @@ -32,12 +32,63 @@ #include "lua_utils.h" +#include "lua_api.h" + #include "post_inc.h" /**********************************************/ static int thing_set_field(lua_State *L); static int thing_get_field(lua_State *L); +static void get_or_create_lua_data_table(lua_State *L, struct Thing* thing) { + // Get the global Game table + lua_getglobal(L, "Game"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); // Pop whatever is on top + lua_newtable(L); + lua_setglobal(L, "Game"); + lua_getglobal(L, "Game"); + } + + // Get or create Game.LuaData + lua_getfield(L, -1, "LuaData"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); // Pop nil + lua_newtable(L); + lua_setfield(L, -2, "LuaData"); // Set Game.LuaData + lua_getfield(L, -1, "LuaData"); + } + + // Get or create Game.LuaData.Thing + lua_getfield(L, -1, "Thing"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); // Pop nil + lua_newtable(L); + lua_setfield(L, -2, "Thing"); // Set Game.LuaData.Thing + lua_getfield(L, -1, "Thing"); + } + + // Now Game.LuaData.Thing is at the top of the stack. + // Create a unique key for the thing's data table + char key[40]; + snprintf(key, sizeof(key), "thing_%d_%d", thing->index, thing->creation_turn); + + // Get or create the specific table for this thing + lua_getfield(L, -1, key); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); // Pop nil + lua_newtable(L); + lua_setfield(L, -2, key); // Set Game.LuaData.Thing[key] + lua_getfield(L, -1, key); + } + + // The desired table is now at the top of the stack. + // We need to clean up the stack, leaving only the thing's data table. + lua_remove(L, -2); // Remove Game.LuaData.Thing + lua_remove(L, -2); // Remove Game.LuaData + lua_remove(L, -2); // Remove Game +} + static const struct luaL_Reg thing_methods[]; @@ -481,6 +532,8 @@ static int thing_get_field(lua_State *L) { lua_pushboolean(L, thing_is_picked_up(thing)); } else if (strcmp(key, "thing_class") == 0) { lua_pushstring(L, thing_class_code_name(thing->class_id)); + } else if (strcmp(key, "lua_data") == 0) { + get_or_create_lua_data_table(L, thing); } else if (try_get_from_methods(L, 1, key)) { return 1; } diff --git a/src/lua_triggers.c b/src/lua_triggers.c index 1a88507af6..a99b37b87f 100644 --- a/src/lua_triggers.c +++ b/src/lua_triggers.c @@ -23,6 +23,40 @@ #include "post_inc.h" +static void delete_thing_lua_data(lua_State *L, struct Thing* thing) { + + // Get Game.LuaData.Thing table + lua_getglobal(L, "Game"); + if (!lua_istable(L, -1)) { lua_pop(L, 1); return; } + lua_getfield(L, -1, "LuaData"); + if (!lua_istable(L, -1)) { lua_pop(L, 2); return; } + lua_getfield(L, -1, "Thing"); + if (!lua_istable(L, -1)) { lua_pop(L, 3); return; } + + char key[40]; + snprintf(key, sizeof(key), "thing_%d_%d", thing->index, thing->creation_turn); + lua_pushnil(L); + lua_setfield(L, -2, key); + + lua_pop(L, 3); +} + +void lua_on_thing_deleted(struct Thing *thing) +{ + SYNCDBG(6, "Starting"); + delete_thing_lua_data(Lvl_script, thing); + lua_getglobal(Lvl_script, "OnThingDeleted"); + if (lua_isfunction(Lvl_script, -1)) + { + lua_pushThing(Lvl_script, thing); + CheckLua(Lvl_script, lua_pcall(Lvl_script, 1, 0, 0), "OnThingDeleted"); + } + else + { + lua_pop(Lvl_script, 1); + } +} + void lua_on_dungeon_destroyed(PlayerNumber plyr_idx) { SYNCDBG(6,"Starting"); diff --git a/src/lua_triggers.h b/src/lua_triggers.h index acb5376daf..52ed946097 100644 --- a/src/lua_triggers.h +++ b/src/lua_triggers.h @@ -43,6 +43,7 @@ void lua_on_slab_kind_change(MapSlabCoord slb_x, MapSlabCoord slb_y, SlabKind ol void lua_on_slab_owner_change(MapSlabCoord slb_x, MapSlabCoord slb_y, PlayerNumber old_owner); void lua_on_room_owner_change(struct Room *room, PlayerNumber old_owner); void lua_on_shot_hit(struct Thing *shot, struct Thing *shooter, struct Thing *target, MapSubtlCoord next_stl_x, MapSubtlCoord next_stl_y, bool rebound_hit); +void lua_on_thing_deleted(struct Thing *thing); #ifdef __cplusplus } diff --git a/src/thing_data.c b/src/thing_data.c index 1db0579f17..059380e9b9 100644 --- a/src/thing_data.c +++ b/src/thing_data.c @@ -36,6 +36,7 @@ #include "engine_arrays.h" #include "kjm_input.h" #include "gui_topmsg.h" +#include "lua_triggers.h" #include "post_inc.h" #ifdef __cplusplus @@ -159,6 +160,7 @@ void delete_thing_structure_f(struct Thing *thing, TbBool deleting_everything, c remove_first_creature(thing); } if (!deleting_everything) { + lua_on_thing_deleted(thing); struct CreatureControl *cctrl = creature_control_get_from_thing(thing); if (!creature_control_invalid(cctrl)) { if (creature_under_spell_effect(thing, CSAfF_Armour)) {