This is the place for discussion and support for LunaLua and related modifications and libraries.
Forum rules
Before you make a topic/post, consider the following:
-Is there a topic for this already?
-Is your post on topic/appropriate?
-Are you posting in the right forum/following the forum rules?
Have any basic/cool LunaLua code you want to share? Post it here! Let everyone know that you made something cool with Lua and stuff. Feel free to give small advice to other posters in this thread if their code is inefficent, or they could make their code more easily configurable, etc., as well.
To kick this thread off, here's some simple, configurable, commented code for a multi jump:
-- Configuration
local numberOfJumps = 2;
local jumpHeight = -11;
-- Setup
local jumpCounter = numberOfJumps;
-- Reset jump counter upon touching the floor
function onTick()
if player:mem(0x146,FIELD_WORD) == 2 then
jumpCounter = numberOfJumps;
end
end
-- Actual double jump
function onKeyDown(keycode)
if keycode == KEY_JUMP and jumpCounter > 0 then
-- Subtract jump counter
jumpCounter = jumpCounter - 1;
-- Launch player
player.speedY = jumpHeight;
-- Play sound effect and do dust effect
if player:mem(0x146,FIELD_WORD) == 0 then
playSFX(1)
for i=-3,3,6 do
local smoke = Animation.spawn(74,player.x+(player.width/2),player.y+player.height)
smoke.speedX = i;
smoke.speedY = 4;
end
end
end
end
There's even a sound effect and a fancy little boost effect and everything. You can change numberOfJumps to however many jumps you want your player to have, and you can change jump height to whatever you want.
Function to draw an image for a single frame that can be scaled and rotated. Best used with onCameraUpdate().
If screenpos = true it takes x and y as screen coordinates, otherwise they are world coordinates. The coordinates are for the center of the rectangle.
Edit: Now supports either a table of named arguments or the regular list of arguments. Combined r,g,b,a into the table rgbacolor (also accepts the name "color").
Edit 2: Added math.ceil to coordinates, which reduces wobbling against other sprites and objects. (Edit 3: made this a proper rounding.)
Edit 4: Ugh, it appears that other objects with non-integer positions (especially around 0.5 off) actually do wobble when the camera moves. Reverted changes so it syncs with them.
If your sprites wobble in a way you don't like, modify the coordinates you enter with math.floor(x+0.5) or something.
Edit 5: Uses triangle strip primitive. Included an additive blend option (thanks Hoeloe).
function DrawRotatedSprite(x,y,angle,texture,width,height,rgbacolor,priority,flipX,flipY,numframesY,frameY,numframesX,frameX,screenpos,additive)
if type(x) == "table" then
angle = x.angle; texture = x.texture; width = x.width; height = x.height;
rgbacolor = x.rgbacolor or x.color; priority = x.priority; flipX = x.flipX; flipY = x.flipY; screenpos = x.screenpos
numframesX = x.numframesX; frameX = x.frameX; numframesY = x.numframesY; frameY = x.frameY;
additive=x.additive
y = x.y; x = x.x
end
if screenpos ~= true then local cam = Camera.get()[1]; x=x-cam.x;y=y-cam.y end
priority = priority or 1;frameX=frameX or 0 ;frameY=frameY or 0 --frames start at 0
if numframesX == nil or numframesX < 1 then numframesX = 1 end
if numframesY == nil or numframesY < 1 then numframesY = 1 end
if width == nil and texture ~= nil then width = texture.width/numframesX end
if height == nil and texture ~= nil then height = texture.height/numframesY end
if width == nil then width = 32 end; if height == nil then height = 32 end
angle = angle or 0; local s = math.sin(angle); local c = math.cos(angle)
local wc = 0.5*width*c; local ws = 0.5*width*s; local hc = 0.5*height*c; local hs = 0.5*height*s
local tris = {x-wc-hs,y+hc-ws,x-wc+hs,y-hc-ws,x+wc-hs,y+hc+ws,x+wc+hs,y-hc+ws}
local txs = {0,1,0,0,1,1,1,0}
local cols
if additive then local r=rgbacolor[1]*rgbacolor[4];local g=rgbacolor[2]*rgbacolor[4];local b=rgbacolor[3]*rgbacolor[4];
cols={r,g,b,0,r,g,b,0,r,g,b,0,r,g,b,0}
end
if flipX then for i=1,7,2 do txs[i]=1-txs[i] end end
if flipY then for i=2,8,2 do txs[i]=1-txs[i] end end
if numframesX>1 then for i = 1,7,2 do txs[i]=(txs[i]+frameX)/numframesX end end
if numframesY>1 then for i = 2,8,2 do txs[i]=(txs[i]+frameY)/numframesY end end
Graphics.glDraw{texture=texture, textureCoords=txs,color = rgbacolor, vertexColors=cols,vertexCoords=tris, priority=priority, primitive = Graphics.GL_TRIANGLE_STRIP}
end
Apologies if something like this is already in the API libraries, I didn't see one.
Last edited by Quantumenace on Tue May 31, 2016 6:35 pm, edited 5 times in total.
With so many arguments, I'd recommend that you use named arguments. If one wants to modify only angle, x, y and screenpos he'd have to count all the way to the end to give all the arguments.
local pNPC = API.load("pnpc");
local tableOfWaluigisFireballs = {};
function onTick()
for k, v in pairs(NPC.get({13, 265, 291}, -1)) do
if(v ~= player.holdingNPC) then
if(not pNPC.getExistingWrapper(v)) then
local initFireball = pNPC.wrap(v);
initFireball.ai2 = 1;
initFireball.data.sinWaveY = 0;
if(initFireball.speedX > 0) then
initFireball.data.sinWaveX = 0;
else
initFireball.data.sinWaveX = 180;
end
initFireball.data.posY = initFireball.y;
table.insert(tableOfWaluigisFireballs, initFireball);
end
end
end
for k, v in pairs(tableOfWaluigisFireballs) do
if(v.isValid) then
v.data.posY = v.data.posY + 4*(math.sin(v.data.sinWaveY));
v.data.sinWaveY = (v.data.sinWaveY + 0.5) % 360
v.x = v.x + 4*math.sin(v.data.sinWaveX);
v.data.sinWaveX = (v.data.sinWaveX + 0.1) % 360
v.y = v.data.posY;
else
table.remove(tableOfWaluigisFireballs, k);
end
end
end
It's just fun to watch.
EDIT: Somehow I left out loading pNPC. It's fixed now.
Here's a little something cool, that could be helpful: using tags in level names to implement common features for levels (such as timers with the same time limit) more efficiently through your lunaworld.lua in an episode.
For example:
If we have five levels we want to set timers for, three are the same and the other two are the same as well. Let's say they are named the following:
timer = API.load("leveltimer")
function onStart()
if string.byte(Level.filename(), 1) == 65 then
timer.setSecondsLeft(300)
timer.setTimerState(true)
elseif string.byte(Level.filename(), 1) == 66 then
timer.setSecondsLeft(600)
timer.setTimerState(true)
end
end
We can use string.byte(string name, digit, ASCII value of digit) to read the name of any string, retrive one of its digits, and then find the ASCII value of that digit (A = 65, B = 66,...), in this case the name of a .lvl file using Level.filename() and the first digit of that file's name. You could also add in more tags and end up with something like:
ABAF_Grassy Greens
You can also read other strings like "hello". Also, since "hello" has five digits, if we try to read the sixth digit, string.byte will return nil.
Overall, I find this very helpful, since you can use it to quickly group together levels without having to put them into a giant table. It can also help minimize repetition throughout lunadll.lua files
local pNPC = API.load("pnpc");
--parabeetleAI.lua
--v1.0
--Created by S1eth, 2015
local parabeetleAI = {}
function parabeetleAI.onInitAPI()
--register event handler
registerEvent(parabeetleAI, "onTick", "onTick", true) --Register the loop event
end
--***************************************************************************************************
-- *
-- CONSTANTS *
-- *
--***************************************************************************************************
parabeetleAI.NPCID = 19;
--***************************************************************************************************
-- *
-- PARABEETLE AI *
-- *
--***************************************************************************************************
parabeetleAI.enabled = true;
function parabeetleAI.Enable()
parabeetleAI.enabled = true;
end
function parabeetleAI.Disable()
parabeetleAI.enabled = false;
end
function parabeetleAI.onTick()
if (not parabeetleAI.enabled) then return end
--find the NPC the player is standing on
local riddenNPCIndex = player:mem(0x176, FIELD_WORD);
local riddenNPC = nil;
if (riddenNPCIndex ~= 0) then
riddenNPC = pNPC.wrap(NPC(riddenNPCIndex-1));
end
--execute AI logic for each parabeetle
for _,npc in pairs(NPC.get(parabeetleAI.NPCID, player.section)) do
local parabeetle = pNPC.wrap(npc);
if (parabeetle.data.timeRidden == nil) then
parabeetle.data.timeRidden = 0;
end
if (riddenNPC ~= nil and riddenNPC.uid == parabeetle.uid) then
--player rides on the parabeetle
--make the parabeetle flutter faster
parabeetle.animationTimer = parabeetle.animationTimer + 1;
--make the parabeetle lose altitude, then fly back up
if (parabeetle.data.timeRidden == 0 and parabeetle.speedY == 0) then
parabeetle.speedY = 2;
else
parabeetle.speedY = math.max(-1.6, parabeetle.speedY - 0.08);
end
parabeetle.data.timeRidden = parabeetle.data.timeRidden + 1;
else
--player does not ride on the parabeetle
--reset the parabeetle's speedY back to 0
parabeetle.data.timeRidden = 0;
if (parabeetle.speedY > 0) then
parabeetle.speedY = math.max(0, parabeetle.speedY - 0.2);
elseif (parabeetle.speedY < 0) then
parabeetle.speedY = math.min(0, parabeetle.speedY + 0.2);
end
end
end
end
return parabeetleAI;
local pNPC = API.load("pnpc");
--parabeetleAI.lua
--v1.0
--Created by S1eth, 2015
local parabeetleAI = {}
function parabeetleAI.onInitAPI()
--register event handler
registerEvent(parabeetleAI, "onTick", "onTick", true) --Register the loop event
end
--***************************************************************************************************
-- *
-- CONSTANTS *
-- *
--***************************************************************************************************
parabeetleAI.NPCID = 19;
--***************************************************************************************************
-- *
-- PARABEETLE AI *
-- *
--***************************************************************************************************
parabeetleAI.enabled = true;
function parabeetleAI.Enable()
parabeetleAI.enabled = true;
end
function parabeetleAI.Disable()
parabeetleAI.enabled = false;
end
function parabeetleAI.onTick()
if (not parabeetleAI.enabled) then return end
--find the NPC the player is standing on
local riddenNPCIndex = player:mem(0x176, FIELD_WORD);
local riddenNPC = nil;
if (riddenNPCIndex ~= 0) then
riddenNPC = pNPC.wrap(NPC(riddenNPCIndex-1));
end
--execute AI logic for each parabeetle
for _,npc in pairs(NPC.get(parabeetleAI.NPCID, player.section)) do
local parabeetle = pNPC.wrap(npc);
if (parabeetle.data.timeRidden == nil) then
parabeetle.data.timeRidden = 0;
end
if (riddenNPC ~= nil and riddenNPC.uid == parabeetle.uid) then
--player rides on the parabeetle
--make the parabeetle flutter faster
parabeetle.animationTimer = parabeetle.animationTimer + 1;
--make the parabeetle lose altitude, then fly back up
if (parabeetle.data.timeRidden == 0 and parabeetle.speedY == 0) then
parabeetle.speedY = 2;
else
parabeetle.speedY = math.max(-1.6, parabeetle.speedY - 0.08);
end
parabeetle.data.timeRidden = parabeetle.data.timeRidden + 1;
else
--player does not ride on the parabeetle
--reset the parabeetle's speedY back to 0
parabeetle.data.timeRidden = 0;
if (parabeetle.speedY > 0) then
parabeetle.speedY = math.max(0, parabeetle.speedY - 0.2);
elseif (parabeetle.speedY < 0) then
parabeetle.speedY = math.min(0, parabeetle.speedY + 0.2);
end
end
end
end
return parabeetleAI;
Does anyone know why this doen't work for me? I put the lua file in the folder...
Circle Guy wrote:Does anyone know why this doen't work for me? I put the lua file in the folder...
Do you have LunaLUA installed? Okay, if you do you have to put the code in a .lua file. Copy the code and paste it somewhere like on Notepad.
I recommend using Notepad++ or Visual Studio Code to paste the code.
Then save as a .lua file in the LuaScriptsLib folder.
Make a lunaworld.lua (to put in your episode) or lunadll.lua (place it in a folder named after the level you wanna use it in) file and I guess you would put:
If you already tried that then I guess the coding is wrong, the lunaworld/dll file needs some specific settings, or you might not be using LunaLUA.
Hope it helps!
Circle Guy wrote:
Does anyone know why this doen't work for me? I put the lua file in the folder...
Could you explain in more detail what part of the script "doesn't work"? Are you getting an error message?
How does your folder hierarchy look like? Are you using a Lua-compatible SMBX version? What does your lunadll.lua file look like?
You need this line in your lunadll.lua file, assuming you extracted the files into your level folder:
To simplify setting up default values for named arguments, I whipped up a quick little function that goes through an array and grabs the first non-nil value.
local function defaultChain (args)
local returnval
i = 1;
while i <= #args do
if args[i] ~= nil then
returnval = args[i]
break;
else
i = i+1
end
end
return returnval;
end
-- Example
local function doAThing (props)
local name = defaultChain {props.name, globalName, "Dave"}
windowDebug (name)
end
function onStart ()
doAThing {}
doAThing {name="Harold"}
globalName = "Walter"
doAThing {}
doAThing {name="Paul"}
-- Will output "Dave", then "Harold", "Walter" and finally "Paul"
end
Here's a fun thing that I might turn into an API: Sniper Snifits and Homing Snifits. With much help from S1eth as well as others, I've created two simple scripts that turn all Snifits into a special type. Both scripts require pnpc.lua to differentiate between bullets. Working on a system that will allow both types and modifications of speeds, etc. in the same level, however right now this is what's going on:
Sniper Snifit--The Snifit fires each of its projectiles towards Mario's position at the time of fire. Code: http://hastebin.com/hokirogebu.lua
local pnpc = API.load("pnpc);
local bulletSpeed = 1;
local bulletAccel = 1;
local function getAngleToPlayer(sourceX, sourceY)
local deltaX = player.x + (player.Width/2) - sourceX;
local deltaY = player.y + (player.Height/2) - sourceY;
return math.atan2(deltaY, deltaX);
end
function onTick()
for _, v in pairs(NPC.get(133, -1)) do
local bullet = pnpc.wrap(v);
if bullet.data.angle == nil then
bullet.data.angle = getAngleToPlayer(bullet.x + bullet.width/2, bullet.y + bullet.height/2);
bullet.data.counter = 0;
end
bullet.speedX = bulletSpeed*math.cos(bullet.data.angle)*(bulletAccel^bullet.data.counter);
bullet.speedY = bulletSpeed*math.sin(bullet.data.angle)*(bulletAccel^bullet.data.counter);
bullet.data.counter = bullet.data.counter + 1;
end
end
Homing Snifit--Each of the Snifit's projectiles will follow the player until the player leaves the section, dies, or forces them to run into a block, etc. Code: http://hastebin.com/izukefiyuf.lua
local bulletSpeed = 1;
local bulletAccel = 1;
local function getAngleToPlayer(sourceX, sourceY)
local deltaX = player.x + (player.Width/2) - sourceX;
local deltaY = player.y + (player.Height/2) - sourceY;
return math.atan2(deltaY, deltaX);
end
function onTick()
for _, v in pairs(NPC.get(133, -1)) do
local bullet = pnpc.wrap(v);
bullet.data.angle = getAngleToPlayer(bullet.x + bullet.width/2, bullet.y + bullet.height/2);
if bullet.data.counter == nil then
bullet.data.counter = 0;
end
bullet.speedX = bulletSpeed*math.cos(bullet.data.angle)*(bulletAccel^bullet.data.counter);
bullet.speedY = bulletSpeed*math.sin(bullet.data.angle)*(bulletAccel^bullet.data.counter);
bullet.data.counter = bullet.data.counter + 1;
end
end
Warning! Setting bulletAccel to values greater than around 1.3 or less than 1 can cause unwanted results including crashes of SMBX. 4 is the default speed in SMBX for bulletSpeed. Enjoy!
Have any basic/cool LunaLua code you want to share? Post it here! Let everyone know that you made something cool with Lua and stuff. Feel free to give small advice to other posters in this thread if their code is inefficent, or they could make their code more easily configurable, etc., as well.
To kick this thread off, here's some simple, configurable, commented code for a multi jump:
-- Configuration
local numberOfJumps = 2;
local jumpHeight = -11;
-- Setup
local jumpCounter = numberOfJumps;
-- Reset jump counter upon touching the floor
function onTick()
if player:mem(0x146,FIELD_WORD) == 2 then
jumpCounter = numberOfJumps;
end
end
-- Actual double jump
function onKeyDown(keycode)
if keycode == KEY_JUMP and jumpCounter > 0 then
-- Subtract jump counter
jumpCounter = jumpCounter - 1;
-- Launch player
player.speedY = jumpHeight;
-- Play sound effect and do dust effect
if player:mem(0x146,FIELD_WORD) == 0 then
playSFX(1)
for i=-3,3,6 do
local smoke = Animation.spawn(74,player.x+(player.width/2),player.y+player.height)
smoke.speedX = i;
smoke.speedY = 4;
end
end
end
end
There's even a sound effect and a fancy little boost effect and everything. You can change numberOfJumps to however many jumps you want your player to have, and you can change jump height to whatever you want.
I am having an issue with this script. It does the double-jump, but the counter never resets. I have put the script in my lunaworld.lua file word for word, so I have no idea what is wrong with it.
My lunaworld.lua file:
-- Configuration
local numberOfJumps = 2;
local jumpHeight = -11;
-- Setup
local jumpCounter = numberOfJumps;
-- Reset jump counter upon touching the floor
function onTick()
if player:mem(0x146,FIELD_WORD) == 2 then
jumpCounter = numberOfJumps;
end
end
-- Actual double jump
function onKeyDown(keycode)
if keycode == KEY_JUMP and jumpCounter > 0 then
-- Subtract jump counter
jumpCounter = jumpCounter - 1;
-- Launch player
player.speedY = jumpHeight;
-- Play sound effect and do dust effect
if player:mem(0x146,FIELD_WORD) == 0 then
playSFX(1)
for i=-3,3,6 do
local smoke = Animation.spawn(74,player.x+(player.width/2),player.y+player.height)
smoke.speedX = i;
smoke.speedY = 4;
end
end
end
end http://i.imgur.com/TYI391p.png
Last edited by Creepermon on Tue Jun 28, 2016 11:36 am, edited 1 time in total.
snoruntpyro wrote:Are you trying to hop off solid npcs or slopes? The code doesn't actually account for that because I was a dumb.
No, just jump off the floor.
I wrote this for a double jump a while ago that I thought I would share now anyway that you might want to look at. It's a simple code that also allows for a classy falling jump. I'm pretty sure it works for slopes and NPCs as well since it tests for the player's speed: http://hastebin.com/isevoqenen.lua