Having trouble with my first LunaLua npc

Post here for help and support regarding LunaLua and SMBX2's libraries and features.

Moderator: Userbase Moderators

gohkenytp
Koopa
Koopa
Posts: 16
Joined: Sun Mar 20, 2016 1:55 pm

Having trouble with my first LunaLua npc

Postby gohkenytp » Thu Oct 24, 2024 4:39 pm

Hi. I'm new to this LunaLua method and I'm having trouble with my first NPC.

Here's the error if you guys don't understand what I mean:
Image

Hope I can get some luck.

I'm trying to make a Yoshi's Island styled Wild Fire Piranha Plant that spits fire.

deice
Volcano Lotus
Volcano Lotus
Posts: 596
Joined: Fri Jul 23, 2021 7:35 am

Re: Having trouble with my first LunaLua npc

Postby deice » Thu Oct 24, 2024 8:19 pm

it would be very helpful if you sent the code in your npc's lua file. the error indicates you forgot a closing brace (probably for a table) somewhere, but without the code it's quite hard to tell you where exactly you should put it

gohkenytp
Koopa
Koopa
Posts: 16
Joined: Sun Mar 20, 2016 1:55 pm

Re: Having trouble with my first LunaLua npc

Postby gohkenytp » Fri Oct 25, 2024 10:34 am

Well i fixed it.

Now I needed help with the fireball spitting part

Here's the code for any understandance:

Code: Select all

local npcManager = require("npcManager")
local npcutils = require("npcs/npcutils")

local imagic = require("imagic")

local wildFirePiranha = {}

local wildFirePiranhaID = {}

local wildFirePiranhaParams = {}

--Offset their Y position
local offsetDir = {
[-1] = 1,
[1] = 0.5
}

--Offset their x position
local offsetDir2 = {
[-1] = 0,
[1] = 10
}

local sharedSettings = {
	activerange = 180, --Active range for regular Wild Piranha, Wild Ptooie Piranha is unaffacted
	
	bitesfx  = "piranha-plant.ogg", -- Idle Biting sfx, can be either filename or SMBX internal ID. Comment this to not play any sounds
	deadsfx  = "piranha-plant-defeat.ogg", -- Defeated sfx, can be either filename or SMBX internal ID. Comment this to not play any sounds
	
	--stalkID = 751 --npcID of the Wild Piranha Plant stalk/bulb. Used by both type of Wild Piranhas. Default being wildFirePiranhaID-1 (The first npc ID in this set) Uncomment this and change manually otherwise
}
function wildFirePiranha.registerCommonHarmTypes(id)
	npcManager.registerHarmTypes(id,
		{
			HARM_TYPE_NPC,
			HARM_TYPE_SWORD
		}, 
		{
		}
	);
end

--Register events
function wildFirePiranha.onInitAPI()
	registerEvent(wildFirePiranha, "onNPCHarm")
end

function wildFirePiranha.register(id, isceiling, angmin, angmax,stalkID)
	wildFirePiranhaID[id] = true
	
	local param = {}
	param.isceiling = isceiling;
	param.angmin = angmin;
	param.angmax = angmax;
	param.stalkID = sharedSettings.stalkID or stalkID
	wildFirePiranhaParams[id] = param;
	
	npcManager.registerEvent(id, wildFirePiranha, "onTickNPC")
	npcManager.registerEvent(id, wildFirePiranha, "onTickEndNPC")
	npcManager.registerEvent(id, wildFirePiranha, "onDrawNPC")
	
	npcManager.setNpcSettings(table.join(sharedSettings, {id=id}));
end

--Custom local definitions below
local STATE_DORMANT = 0
local STATE_ACTIVE = 1

local DEGTORAD = math.pi/180
local RADTODEG = 180/math.pi

local DOUBLEPI = math.pi*2
local HALFPI = math.pi*0.5

--Sprites Drawing Logic tweaked from Basegame Grrrol AI
function drawNPCFrame(id, frame, x, y, angle,scale)
	local settings = npcManager.getNpcSettings(id)
	local priority = -45
	
	frame = frame or 0
	
	if settings.foreground then
		priority = -15
	end
	imagic.Draw{
		texture = Graphics.sprites.npc[id].img,
		width = settings.gfxwidth*scale,
		height = settings.gfxheight*scale,
		sourceWidth = settings.gfxwidth,
		sourceHeight = settings.gfxheight,
		sourceY = frame * settings.gfxheight,
		scene = true,
		x = x + settings.width  * 0.5,
		y = y + settings.height * 0.5,
		rotation = angle,
		align = imagic.ALIGN_CENTRE,
		priority = -45
	}
end

function wildFirePiranha.onDrawNPC(v)
	v.animationFrame = -50
	
	local data = v.data
	
	if data.state == STATE_DORMANT then return end
	
	local time = data.animationTimer or 0
	
	if data.isEating then
		if data.timer <= 16 or (data.timer >= 48 and data.timer <= 178) then
			time = 9
		else
			time = 0
		end
	end
	
	local f
	
	local rot = data.rangle or 0
	local sc = data.renderscale or 1
	
	if data.isdying then
		f = 2+math.floor(time/4)%2
	else
		f = math.floor(time/8)%2
	end
	
	drawNPCFrame(v.id, npcutils.getFrameByFramestyle(v, {frame=f}), v.x, v.y, rot*RADTODEG,sc)
end

function wildFirePiranha.onTickNPC(v)
	--Don't act during time freeze
	if Defines.levelFreeze then return end
	
	local config = NPC.config[v.id]
	local data = v.data
	local settings = v.data._settings
	local plr = Player.getNearest(v.x + v.width/2, v.y + v.height)
	
	if settings.delay == nil then settings.delay = 192 end
	
	--If despawned
	if v:mem(0x12A, FIELD_WORD) <= 0 then
		--Reset our properties, if necessary
		
		--Despawn Stem
		if data.mystem.isValid then
			data.mystem:kill(HARM_TYPE_OFFSCREEN)
		end
		
		data.initialized = false
		return
	end

	--Initialize
	if not data.initialized then
		--Initialize necessary data.
		
		--Don't Move has no effect on these NPC--
		
		data.bitesfx = NPC.config[v.id].bitesfx
		data.deadsfx = NPC.config[v.id].deadsfx
		data.activerange = NPC.config[v.id].activerange or 180
		
		data.playbitesounds = data.bitesfx ~= null
		
		data.state = STATE_DORMANT
		
		data.animationTimer = 0
		data.angle = 0
		
		data.deathcounter   = 0
		
		data.renderscale = 0.3
		
		data.offx = 0
		data.offy = 0
		
		data.ogwidth = v.width
		
		v.width = 32
		v.height = 32
		
		data.eatingRender = 1
		
		data.shrinkTimerfire = 0
		
		data.vector = vector(plr.x-v.x+(plr.width-v.width)*0.5, plr.y-v.y+(plr.height-v.height)*0.5):normalize()

		w = NPC.spawn(wildFirePiranhaParams[v.id].stalkID,v.x,v.y,v.section,false,false)
		w.data.myhead = v
		w.data.isceiling = wildFirePiranhaParams[v.id].isceiling
		w.friendly = true
		w.state = STATE_DORMANT
		w.layerObj = v.layerObj --spawn stem in the same layer as head
		
		data.mystem = w
		
		data.ox = v.x
		data.oy = v.y
		
		data.hp = 5
		
		data.timer = 0
		
		data.ceiloff = 0
		
		data.hitbox = Colliders.Box(v.x, v.y - v.height * 0.25, v.width * 2.5, v.height * 2.5)
		
		if wildFirePiranhaParams[v.id].isceiling then
			data.ceiloff = 12
			data.ceilangoff = 0
			
			data.basedeadang = -90
		else
			data.ceiloff = -28
			data.ceilangoff = 0
			
			data.basedeadang = 90
		end
		
		data.initialized = true
	end

	--Depending on the NPC, these checks must be handled differently
	if v:mem(0x12C, FIELD_WORD) > 0    --Grabbed
	or v:mem(0x136, FIELD_BOOL)        --Thrown
	or v:mem(0x138, FIELD_WORD) > 0    --Contained within
	then
		--Self-destruct if it's a part of any container or generator (Yoshi also, but since it's noyoshi by default this should not triggered)
		if data.mystem.isValid then
			data.mystem:kill()
		end
		v:kill()
		
	end
	
	data.hitbox.x = v.x - v.height * 0.25
	data.hitbox.y = v.y - v.height * 0.25
	
	local player = npcutils.getNearestPlayer(v)
	
	if data.state == STATE_DORMANT then
	
		v.x = data.ox
		v.y = data.oy
	
		if math.abs((player.x + 0.5 * player.width) - (data.ox + 0.5 * data.ogwidth)) < data.activerange then
			data.state = STATE_ACTIVE
			data.mystem.data.state = STATE_ACTIVE
			
			v.width = 48
			v.height = 48
		end
	
	elseif data.state == STATE_ACTIVE then
		if not data.isdying then
			if not data.isEating then
				if math.abs((player.x + 0.5 * player.width) - (data.ox + 0.5 * data.ogwidth)) > data.activerange then
					data.renderscale = math.clamp(data.renderscale - 0.03, 0.3, 1)
					if data.renderscale <= 0.3 then
						data.state = STATE_DORMANT
						data.mystem.data.state = STATE_DORMANT
						
						v.width = 32
						v.height = 32
						
						v.x = data.ox
						v.y = data.oy
					end
				else
					data.renderscale = math.clamp(data.renderscale + 0.1, 0.3, 1)
				end
				
				if data.renderscale == 1 then
					for _,plr in ipairs(Player.get()) do
						if Colliders.collide(plr, data.hitbox) and data.timer >= 0 and plr:mem(0x140, FIELD_WORD) == 0 then
							data.timer = 0
							data.targetPlayer = plr
							data.isEating = true
						end
					end
				end
				data.timer = data.timer + 1
				if data.timer >= 0 then v.friendly = false end
			else
				v.friendly = true
				data.timer = data.timer + 1
				--Gruesome stuff to do to the victim who gets caught
				if data.targetPlayer ~= nil then
					data.targetPlayer.x = v.x + 0.5 * v.width
					data.targetPlayer.y = v.y + v.height * 0.25
					
					if data.timer <= 178 and data.timer ~= 17 then
						data.targetPlayer.forcedState = 8
					else
						if data.timer == 17 then
							data.targetPlayer.forcedState = 0
							data.targetPlayer:harm()
						end
					end
					
					data.renderscale = math.clamp(data.renderscale - (0.0125 * data.eatingRender), 0.95, 1)
					
					if data.timer % 32 <= 15 then
						data.eatingRender = -1
					else
						data.eatingRender = 1
					end
					
					if data.timer >= 48 and data.timer < 218 then
						if (data.targetPlayer.keys.up == KEYS_PRESSED or data.targetPlayer.keys.down == KEYS_PRESSED or data.targetPlayer.keys.left == KEYS_PRESSED or data.targetPlayer.keys.right == KEYS_PRESSED or data.targetPlayer.keys.run == KEYS_PRESSED or data.targetPlayer.keys.jump == KEYS_PRESSED) then
							data.timer = data.timer + 8
						end
					end
					
					if data.timer >= 218 then
						data.isEating = false
						data.timer = -128
						data.targetPlayer.forcedState = 0
						data.targetPlayer:mem(0x140, FIELD_WORD, 150)
						data.targetPlayer.speedX = 4 * v.direction
						data.targetPlayer.speedY = -6
						data.targetPlayer = nil
					end
					
				else
					data.isEating = false
				end
			end
		end
	end
	if data.isdying then v.friendly = true end
	if not data.isdying then
	if not data.isEating then
	if data.timer > v.data._settings.delay - 32 then
				data.myhead = 0
				if data.timer >= v.data._settings.delay then
					data.timer = -64
					local n = NPC.spawn(config.fireballID, v.x+(v.width/2)+config.gfxoffsetx + (offsetDir2[v.direction]) + (data.rangle * (0.05 * -v.direction) - 8), v.y+v.height-(config.gfxwidth * (offsetDir[v.direction]))+config.gfxoffsety  + ((data.shrinkTimerfire * 0.4) * -v.direction) + config.fireOffset * v.direction, player.section, false)
					if NPC.config[n.id].nogravity or n.id == 246 then
						n.speedX = data.vector.x * 4
						n.speedY = data.vector.y * 4
					else
						local bombxspeed = vector.v2(Player.getNearest(v.x + v.width/2, v.y + v.height).x + 0.5 * Player.getNearest(v.x + v.width/2, v.y + v.height).width - (v.x + 0.5 * v.width))
						n.speedX = data.vector.x * 12
						n.speedY = data.vector.y * 16
					end
					if NPC.config[v.id].shootSFX then
						SFX.play(config.fireballSFX)
					end
				end
			else
				data.myhead = 1
			end
	    end
	end
end

function wildFirePiranha.onTickEndNPC(v)
	--Don't act during time freeze
	if Defines.levelFreeze then return end
	
	local data = v.data
	
	local param = wildFirePiranhaParams[v.id]

	if data.state == STATE_ACTIVE then
		if not data.isdying then
			data.animationTimer = data.animationTimer+1
			
			if data.playbitesounds and not data.isEating then
				if data.animationTimer%16==0 then
					SFX.play(data.bitesfx)
				end
			end
			
			local aimangle = math.atan2(v.y-player.y,v.x-player.x)
			
			--Update angle only if aimangle is in range
			if aimangle >= param.angmin and aimangle <= param.angmax then
				data.angle = aimangle
			end
			
		else
			data.deathcounter = data.deathcounter+1
			
			if data.deathcounter<=60 then
		
			data.angle = math.anglelerp(data.angle-0.001,(data.basedeadang-30*data.deaddirection)*DEGTORAD,data.deathcounter/60) -- -0.001 is to prevent weird angle leap at 180
			data.rangle = data.angle
			elseif data.deathcounter>60 then
			
				if data.deadsfx and data.deathcounter==61 then
					SFX.play(data.deadsfx)
				end
				
				data.animationTimer = data.animationTimer+1
				
				if data.renderscale > 0 then
				data.renderscale = data.renderscale-0.03
				else
					if data.mystem.isValid then
						Effect.spawn(10, data.mystem.x, data.mystem.y)
						data.mystem:kill(HARM_TYPE_OFFSCREEN)
					end
					v:kill(HARM_TYPE_OFFSCREEN)
				end
				
			end
		end
		
		if data.angle > HALFPI or data.angle < -HALFPI then
				v.direction = 1
				data.rangle = data.angle-math.pi
			else
				v.direction = -1
				data.rangle = data.angle
			end
		
		v.x = data.ox -6 + 32*data.renderscale*math.cos(data.angle+math.pi)+data.offx
		v.y = data.oy + 16*data.renderscale*math.sin(data.angle+math.pi)+data.offy+data.ceiloff
	end
	
	--Update Stem Position
	if data.mystem ~= nil and data.mystem.isValid then
		data.ox = data.mystem.x
		data.oy = data.mystem.y
		if NPC.config[v.id].simulateGravity then
			data.mystem.speedY = data.mystem.speedY + Defines.npc_grav
			data.mystem.noblockcollision = false
		else
			data.mystem.noblockcollision = true
		end
	end
end

function wildFirePiranha.onNPCHarm(eventObj, v, killReason, culprit)
	if not wildFirePiranhaID[v.id] then return end
	
	local data = v.data
	
	if data.state == STATE_DORMANT then
		if data.mystem.isValid then
				Effect.spawn(10, data.mystem.x, data.mystem.y)
				data.mystem:kill(HARM_TYPE_OFFSCREEN)
		end
		return
	end
	
	--Fireball
	if culprit then
		if culprit.__type == "NPC" and culprit.id == 13 then
				data.hp = data.hp - 1
				
				if data.hp > 0 then
					SFX.play(9)
					eventObj.cancelled = true
					return
				end
		end
		
		data.deathcounter = 0
		
		--Adjust Dying Direction
		if culprit.speedX > 0 or (culprit.speedX==0 and culprit.x < v.x) then
			v.direction = -1
		else
			v.direction = 1
		end
	end
	
	--Dead
	SFX.play(9)
	data.renderscale = 1

	if data.mystem.isValid then
		data.mystem.friendly = true
	end
	
	data.isdying = true
	
	
	
	
	data.offx = -4*v.direction
	if wildFirePiranhaParams[v.id].isceiling then
		data.deaddirection = -v.direction
		data.offy = -2
	else
		data.deaddirection = v.direction
		data.offy = 2
	end
	
	
	
	data.animationTimer = 0
	
	if killReason==HARM_TYPE_NPC or killReason==HARM_TYPE_SWORD then
		eventObj.cancelled = true;
		if culprit then
			culprit:kill()
		else
			for _,p in ipairs(NPC.getIntersecting(v.x - 6, v.y - 6, v.x + v.width + 6, v.y + v.height + 6)) do
			
				--**********************************************************************************************
				--Thrown Yoshi Egg ID, change it here. 953 is the default egg broken effect, so change that too.
				--**********************************************************************************************
				
				if p.id == 953 then
					p:kill(HARM_TYPE_VANISH)
					SFX.play(2)
					local e = Effect.spawn(953,v.x+v.width*0.5,v.y+v.height)

					e.x = e.x - e.width*0.5
					e.y = e.y - e.height
				end
			end
		end
	end
	

end

return wildFirePiranha


Return to “LunaLua Help”

Who is online

Users browsing this forum: No registered users and 3 guests

SMWCentralTalkhausMario Fan Games GalaxyKafukaMarioWikiSMBXEquipoEstelari