Well i fixed it.
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