Page 1 of 1

[NPC Pack] Flurret

Posted: Sat Jul 27, 2019 7:16 pm
by Emral
Flurret shootm's the Flurry. Lucienne says it best: Kind of like a Bowser Statue with bounceable projectiles.

A download link can be found here:
https://drive.google.com/file/d/1rsjmIq ... sp=sharing

Demo:
https://www.youtube.com/watch?v=3yZSJNPa7u8
0:40

Installation instructions can be found in the handbook or in my thread.

Modifications and adaptations of the code is permitted. If you use this particular code as a basis for an enhanced version of these NPCs, please give credit.

Re: [NPC Pack] Flurret

Posted: Mon Jul 06, 2020 3:27 pm
by Lithobraker
Hey, I'm currently repurposing the Flurret code into a Ruff Puff, and I've got a 4-frame moving animation for it. Everything seems to work fine when it's left-facing with this code, but when it's facing right, the animation plays extremely rapidly for some reason.
I might be able to fix it if I knew how modulus operators (%) worked...

Code: Select all

v.animationFrame = v.animationFrame % 2
Changing the 2 here to 3 gets me an NPC which animates with only the first 3 frames of its moving animation- but it doesn't have a fit when facing right.
If it's set to 4 though, the fit occurs.
I feel like this should be obvious, but my understanding of modulus operators is that they give the remainder of the first number divided by the second. If this was true, shouldn't it not animate properly, since a remainder is only returned with an odd number? Clearly I'm missing something.

Sprite:
Image

NPC.lua:
Spoiler: show

Code: Select all

local npcManager = require("npcManager")

local ruffPuffNPC = {}

local npcID = NPC_ID
local deathEffectID = npcID

-- settings
local config = {
	id = npcID, 
	gfxoffsetx = 0, 
	gfxoffsety = 0, 
	width = 30, 
    height = 30,
    gfxwidth = 46,
    gfxheight = 38,
    frames = 6,
    framestyle = 1,
    playerblocktop = false,
    npcblocktop = false,
    playerblock = false,
    npcblock = false,
    nofireball = true,
    noiceball = false,
    nohurt = true,
	nogravity = true,
	noblockcollision = true,
    jumphurt = true,
	spinjumpsafe = true,
    projectile = 935,
	
	iswalker = true
}

npcManager.setNpcSettings(config)

npcManager.registerHarmTypes(npcID,
{
		--HARM_TYPE_JUMP,
		--HARM_TYPE_FROMBELOW,
		HARM_TYPE_NPC,
		--HARM_TYPE_PROJECTILE_USED,
		HARM_TYPE_LAVA,
		HARM_TYPE_HELD,
		--HARM_TYPE_TAIL,
		--HARM_TYPE_SPINJUMP,
		--HARM_TYPE_OFFSCREEN,
		HARM_TYPE_SWORD
	}, 
	{
		--[HARM_TYPE_JUMP]=10,
		[HARM_TYPE_FROMBELOW]= deathEffectID,
		[HARM_TYPE_NPC]= deathEffectID,
		--[HARM_TYPE_PROJECTILE_USED]= 10,
		[HARM_TYPE_LAVA]={id=13, xoffset=0.5, xoffsetBack = 0, yoffset=1, yoffsetBack = 1.5},
		[HARM_TYPE_HELD]= deathEffectID,
		--[HARM_TYPE_TAIL]=10,
		--[HARM_TYPE_SPINJUMP]=10,
		--[HARM_TYPE_OFFSCREEN]=10,
		[HARM_TYPE_SWORD]= deathEffectID,
		[HARM_TYPE_EXT_ICE] = 10,
	}
);

local ST_IDLE = 0
local ST_WAIT = 1
local ST_SHOOT = 2
local turnTimer = 0

function ruffPuffNPC.onInitAPI()
    npcManager.registerEvent(npcID, ruffPuffNPC, "onTickEndNPC")
end

function ruffPuffNPC.onTickEndNPC(v)
    if Defines.levelFreeze then return end
    if v.isHidden or v:mem(0x12A, FIELD_WORD) <= 0 or v:mem(0x138, FIELD_WORD) > 0 then
        v.data.state = 0
        v.data.timer = 0
		turnTimer = 0
		return
    end

    local data = v.data

    local shootframe = NPC.config[v.id].frames - 2

    if data.state == nil then
        data.state = 0
        data.timer = 0
		turnTimer = 0
    end

    data.timer = data.timer + 1
	turnTimer = turnTimer + 1

    v.animationFrame = v.animationFrame % 4
	
	if data.state ~= ST_SHOOT then
		if turnTimer >= 128 then
			v.speedX = v.speedX * -1
			turnTimer = 0
		end
	
	end

    if data.state == ST_IDLE then
        if data.timer == 64 and not v.friendly then
            data.timer = 0
            data.state = ST_WAIT
        end
    elseif data.state == ST_WAIT then
        if data.timer == 16 then
            local n = NPC.spawn(NPC.config[v.id].projectile, v.x + 0.5 * v.width, v.y + 0.5 * v.height, player.section, false, true)
            n.speedX = 2 * v.direction
            data.timer = 0
            data.state = ST_SHOOT
            SFX.play("puffspit.ogg")
        end
        v.animationFrame = shootframe
    elseif data.state == ST_SHOOT then
        if data.timer == 16 then
            data.timer = 0
            data.state = ST_IDLE
        end
        v.animationFrame = shootframe + 1
    end

    if v.direction == 1 and NPC.config[v.id].framestyle == 1 then
        v.animationFrame = v.animationFrame + NPC.config[v.id].frames
    end
end

return ruffPuffNPC

Re: [NPC Pack] Flurret

Posted: Mon Jul 06, 2020 4:09 pm
by Emral
Ah, yeah. Might've taken an animation shortcut there. Modulo can get a bit wacky due to the nature of the spritesheet and how the right-facing frames are just higher numbers. Your intuition is correct. IDK what that line 71 is doing but you miiight be able to fix it by removing it and putting the following just after line 77 instead:
v.animationFrame = math.floor(data.timer / NPC.config[v.id].framespeed) % frame
That'll basically overwrite the basegame frame timer completely. Fairly confident that'll work.

Re: [NPC Pack] Flurret

Posted: Mon Jul 06, 2020 4:12 pm
by Lithobraker
Enjl wrote:
Mon Jul 06, 2020 4:09 pm
Ah, yeah. Might've taken an animation shortcut there. Modulo can get a bit wacky due to the nature of the spritesheet and how the right-facing frames are just higher numbers. Your intuition is correct. IDK what that line 71 is doing but you miiight be able to fix it by removing it and putting the following just after line 77 instead:
v.animationFrame = math.floor(data.timer / NPC.config[v.id].framespeed) % frame
That'll basically overwrite the basegame frame timer completely. Fairly confident that'll work.
Um... line 71? In my file, line 71 is this:

Code: Select all

    npcManager.registerEvent(npcID, ruffPuffNPC, "onTickEndNPC")
I think that one's important. Could you just copy+paste which line you're talking about?

Re: [NPC Pack] Flurret

Posted: Mon Jul 06, 2020 4:16 pm
by Emral
Lithobraker wrote:
Mon Jul 06, 2020 4:12 pm
Enjl wrote:
Mon Jul 06, 2020 4:09 pm
Ah, yeah. Might've taken an animation shortcut there. Modulo can get a bit wacky due to the nature of the spritesheet and how the right-facing frames are just higher numbers. Your intuition is correct. IDK what that line 71 is doing but you miiight be able to fix it by removing it and putting the following just after line 77 instead:
v.animationFrame = math.floor(data.timer / NPC.config[v.id].framespeed) % frame
That'll basically overwrite the basegame frame timer completely. Fairly confident that'll work.
Um... line 71? In my file, line 71 is this:

Code: Select all

    npcManager.registerEvent(npcID, ruffPuffNPC, "onTickEndNPC")
I think that one's important. Could you just copy+paste which line you're talking about?
Oh, you must've modified some earlier lines. Got it.
Line 71:
v.animationFrame = v.animationFrame % 2

"Just after line 77" is between these two:

end
-- right here
elseif data.state == ST_WAIT then

Re: [NPC Pack] Flurret

Posted: Mon Jul 06, 2020 4:20 pm
by Lithobraker
Enjl wrote:
Mon Jul 06, 2020 4:16 pm
Lithobraker wrote:
Mon Jul 06, 2020 4:12 pm
Enjl wrote:
Mon Jul 06, 2020 4:09 pm
Ah, yeah. Might've taken an animation shortcut there. Modulo can get a bit wacky due to the nature of the spritesheet and how the right-facing frames are just higher numbers. Your intuition is correct. IDK what that line 71 is doing but you miiight be able to fix it by removing it and putting the following just after line 77 instead:
v.animationFrame = math.floor(data.timer / NPC.config[v.id].framespeed) % frame
That'll basically overwrite the basegame frame timer completely. Fairly confident that'll work.
Um... line 71? In my file, line 71 is this:

Code: Select all

    npcManager.registerEvent(npcID, ruffPuffNPC, "onTickEndNPC")
I think that one's important. Could you just copy+paste which line you're talking about?
Oh, you must've modified some earlier lines. Got it.
Line 71:
v.animationFrame = v.animationFrame % 2

"Just after line 77" is between these two:

end
-- right here
elseif data.state == ST_WAIT then
Well, I had to change your line a bit, because "% frame" is calling a global that doesn't exist. You meant "% NPC.config[v.id].frames", right?

Code: Select all

v.animationFrame = math.floor(data.timer / NPC.config[v.id].framespeed) % NPC.config[v.id].frames
With the changes you suggested made, though, while the idle animations seem to work mostly fine, the NPC plays the shooting animation repeatedly. I can't tell if it's doing this because it's cycling through all its frames, or if it actually thinks it's shooting a bit before it does.
I'm inclined to believe the former.

Re: [NPC Pack] Flurret

Posted: Mon Jul 06, 2020 4:23 pm
by Emral
Lithobraker wrote:
Mon Jul 06, 2020 4:20 pm
Well, I had to change your line a bit, because "% frame" is calling a global that doesn't exist. You meant "% NPC.config[v.id].frames", right?

Code: Select all

v.animationFrame = math.floor(data.timer / NPC.config[v.id].framespeed) % NPC.config[v.id].frames
With the changes you suggested made, though, while the idle animations seem to work mostly fine, the NPC plays the shooting animation repeatedly. I can't tell if it's doing this because it's cycling through all its frames, or if it actually thinks it's shooting a bit before it does.
I'm inclined to believe the former.
Wait... huh? We were both looking at the flurret ai file, right?
It is the former, hold on.. Here's what my file I just downloaded looked like:
https://hastebin.com/oleduqugog.rb
Notice the definition of "frame" in line 62. It's the number of the first shooting frame, so I was taking modulo by that to take the animation loop of the frames before it.

Re: [NPC Pack] Flurret

Posted: Mon Jul 06, 2020 4:27 pm
by Lithobraker
Enjl wrote:
Mon Jul 06, 2020 4:23 pm
Lithobraker wrote:
Mon Jul 06, 2020 4:20 pm
Well, I had to change your line a bit, because "% frame" is calling a global that doesn't exist. You meant "% NPC.config[v.id].frames", right?

Code: Select all

v.animationFrame = math.floor(data.timer / NPC.config[v.id].framespeed) % NPC.config[v.id].frames
With the changes you suggested made, though, while the idle animations seem to work mostly fine, the NPC plays the shooting animation repeatedly. I can't tell if it's doing this because it's cycling through all its frames, or if it actually thinks it's shooting a bit before it does.
I'm inclined to believe the former.
Wait... huh? We were both looking at the flurret ai file, right?
It is the former, hold on.. Here's what my file I just downloaded looked like:
https://hastebin.com/oleduqugog.rb
Notice the definition of "frame" in line 62. It's the number of the first shooting frame, so I was taking modulo by that to take the animation loop of the frames before it.
Ah, right. I renamed that to "shootframe" since that's what it appears to mean, so I would be able to read the file better. That fixed it! Thanks for the help.
You've been a huge help in general so far! I hope you don't mind all these questions I've been having.