Yeah, Lua used to be my bread and butter 20 years ago. And trying to get back to it again.
For a worry-free Lua integration, check out LuaCSharp (use the v0.5-dev branch due to various API changes). This got me hooked on Lua again because like MoonSharp it’s a pure C# implementation but unlike MoonSharp this is actually fast and well designed.
Even if Lua is just feeding data to your engine, it allows modders for great flexibility and power since they can feed your game with algorithmic data.
Here’s an example how to define data in Lua and return it to the caller. It returns a Lua table (with nested tables inside) which is an array+dictionary in unison:
local DamageType = { "Fire", "Ice", "Nuclear", }
return
{
Goblin = { Damage = 3, DamageType = DamageType.Fire },
GoblinBoss = { Damage = 17, DamageType = DamageType.Nuclear },
}
Now the nice thing is that unlike Json this is both a data storage format and executable code in one. You can use Lua to algorithmically generate more data at the blink of an eye:
local DamageType = { "Fire", "Ice", "Nuclear", }
local data =
{
Goblin = { Damage = 3, DamageType = DamageType.Fire },
GoblinBoss = { Damage = 17, DamageType = DamageType.Nuclear },
}
-- add linearly scaling goblins
for 1, 100 do
data["Goblin_Level" .. i] = { Damage = i, DamageType.Ice }
end
return data
And just like that you got yourself 100 extra Goblins with increasing damage output. 
I also find the Lua table format very easy to edit. It’s human readable, and if you make a syntax error, you get told so where and why, rather than an ominous “json read error”
.
If you design your modding capabilities around defining data you can already allow users to mod a LOT without even providing them with a custom API. For instance you could scan for a DamageTypeNuclear.lua file and then run it. It expects a Lua function as return value with predefined parameters which allows modders to change how damage values for this type are computed. Perhaps “Nuclear” was added by modders as a damage type, and the function accordingly just returns a high value:
return function CalculateDamage(enemyTable)
return 100000000000 -- enough to kill everything
end
On the C# side you’ll have a precompiled LuaFunction instance that you can just invoke a lot and it’ll run very fast, thanks to the LuaCSharp bytecode interpreter not having to cross language barriers (which makes xLua, nLua, etc so slow).