Custom Powerups Don't Work When Testing World File

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

Moderator: Userbase Moderators

Cambroski
Shy Guy
Shy Guy
Posts: 8
Joined: Tue Nov 01, 2022 7:29 pm
Flair: Awake by day, asleep by night.

Custom Powerups Don't Work When Testing World File

Postby Cambroski » Tue Nov 01, 2022 9:58 pm

Hello. I am currently making a world that uses custom powerups from the Super Mario Land 1 & 2 NPC pack by MegaDood. When I test the level the powerups work like they should, however when I test a world file, as if I were playing the episode/game, I get an error from anotherpowerup.lua.
Spoiler: show
Image
I have also tried to test this with the latest anotherpowerup.lua from the forums and I still receive the same error. I don't really know what to look at when examining the debug window and I haven't seen any other errors like this on the forums. I was also wondering if it could be the version of SMBX2 that I am using. It is Beta 4 Patch 4 if that can narrow anything down. Thanks in advance!

Hoeloe
Phanto
Phanto
Posts: 1465
Joined: Sat Oct 03, 2015 6:18 pm
Flair: The Codehaus Girl
Pronouns: she/her

Re: Custom Powerups Don't Work When Testing World File

Postby Hoeloe » Thu Nov 03, 2022 11:20 am

.wld files can't be tested from the editor in SMBX2. Attempting to do so will instead boot up the Moondust engine, which is not complete and does not support SMBX2 features.

Emral
Cute Yoshi Egg
Cute Yoshi Egg
Posts: 9891
Joined: Mon Jan 20, 2014 12:58 pm
Flair: Phoenix

Re: Custom Powerups Don't Work When Testing World File

Postby Emral » Thu Nov 03, 2022 11:38 am

This is a bug in anotherpowerup. Having never tested it in worlds (>_>), it loads testmodemenu regardless of whether or not it's in the editor.

The solution is to ctrl + f for all lines that use "testmodemenu" in the anotherpowerup.lua script, and encasing them in an if statement like so

Code: Select all

if Misc.inEditor() then
 -- the code that loads or uses Testmodemenu
end

Cambroski
Shy Guy
Shy Guy
Posts: 8
Joined: Tue Nov 01, 2022 7:29 pm
Flair: Awake by day, asleep by night.

Re: Custom Powerups Don't Work When Testing World File

Postby Cambroski » Thu Nov 03, 2022 6:51 pm

Enjl wrote:
Thu Nov 03, 2022 11:38 am
This is a bug in anotherpowerup. Having never tested it in worlds (>_>), it loads testmodemenu regardless of whether or not it's in the editor.

The solution is to ctrl + f for all lines that use "testmodemenu" in the anotherpowerup.lua script, and encasing them in an if statement like so

Code: Select all

if Misc.inEditor() then
 -- the code that loads or uses Testmodemenu
end
That didn't seem to work. It's giving me the exact same error that it usually does.

Code: Select all

===> scripts/base/engine/testmodemenu.lua:4 attempt to index global 'ffi' (a nil value)
===========================================================================
stack traceback:
	scripts/base/engine/require.lua:150: in function 'require'
	worlds/WIP Game/libs/anotherpowerup.lua:13: in function 'func'
	
	scripts/base/engine/require.lua:150: in function 'require'
	worlds/WIP Game/luna.lua:5: in function 'codeFile'
	main.lua:743: in function 'loadCodeFile'
	main.lua.889: in function <main.lua:793
	[C]: in function '__xpcall'
	main.lua:793: in function <main.lua:792>

Emral
Cute Yoshi Egg
Cute Yoshi Egg
Posts: 9891
Joined: Mon Jan 20, 2014 12:58 pm
Flair: Phoenix

Re: Custom Powerups Don't Work When Testing World File

Postby Emral » Thu Nov 03, 2022 9:23 pm

can you post the modified script?

Cambroski
Shy Guy
Shy Guy
Posts: 8
Joined: Tue Nov 01, 2022 7:29 pm
Flair: Awake by day, asleep by night.

Re: Custom Powerups Don't Work When Testing World File

Postby Cambroski » Thu Nov 03, 2022 11:37 pm

Enjl wrote:
Thu Nov 03, 2022 9:23 pm
can you post the modified script?
Yup here it is. I think that I edited like you said but it didn't do anything different. :(
Spoiler: show

Code: Select all

local playerManager = require("playerManager")
local testModeMenu = require("engine/testmodemenu")
local npcManager = require("npcManager")

local ap = {}

SaveData._ap = SaveData._ap or {}

local powerups = {}
local currentPowerup
local itemMap = {}
local tiers = {}
local isPoweringUp = false

powerups[2] = {9, 184, 185}
powerups[3] = {14, 182, 183}
powerups[4] = {34}
powerups[5] = {169}
powerups[6] = {170}
powerups[7] = {264, 277}

local defaultNPCPowerupMap = {
    [9]   = PLAYER_BIG,
    [14]  = PLAYER_FIREFLOWER,
    [34]  = PLAYER_LEAF,
    [169] = PLAYER_TANOOKIE,
    [170] = PLAYER_HAMMER,
    [182] = PLAYER_FIREFLOWER,
    [183] = PLAYER_FIREFLOWER,
    [184] = PLAYER_BIG,
    [185] = PLAYER_BIG,
    [249] = PLAYER_BIG,
    [264] = PLAYER_ICE,
    [277] = PLAYER_ICE,
}

local players = {
    "mario", "luigi", "peach", "toad", "link"
}

local function registerItemsInternal(powerup, ids)
    if type(ids) == "number" then
        ids = {}
    elseif type(ids) ~= "table" then
        powerup.items = {}
        return
    end

    for k,v in ipairs(powerup.items) do
        itemMap[v] = nil
    end

    powerup.items = ids

    for k,v in ipairs(ids) do
        if itemMap[v] ~= nil then
            Misc.warn("Item " .. v .. " could not be registered to Powerup " .. libraryName .. ", because it was already registered to Powerup " .. itemMap[v] .. ".")
            return
        else
            itemMap[v] = libraryName
        end
    end
end


local function loadPowerupAssets(thisPlayer,basePowerup)
    if currentPowerup == nil then
        return
    end
    
    local iniFile = Misc.resolveFile(players[thisPlayer.character] .. "-" .. currentPowerup.name .. ".ini")
	
    if (iniFile == nil) then
        iniFile = playerManager.getHitboxPath(thisPlayer.character, basePowerup);
    end

    Misc.loadCharacterHitBoxes(thisPlayer.character, basePowerup, iniFile)

    Graphics.sprites[players[thisPlayer.character]][basePowerup].img = currentPowerup.spritesheets[thisPlayer.character]
end

-- registerPowerup registers a new powerup to the system
--- libraryName: string - The name of the library containing powerup information. One library per powerup. Think of it as a require(libraryName) call

-- Returns the library table for the powerup
function ap.registerPowerup(libraryName)
    local entry = require(libraryName)
    entry.registerItems = registerItemsInternal
    entry.name = libraryName
    entry.items = entry.items or {}
    powerups[libraryName] = entry

    for k,v in ipairs(entry.items) do
        if itemMap[v] ~= nil then
            Misc.warn("Item " .. v .. " could not be registered to Powerup " .. libraryName .. ", because it was already registered to Powerup " .. itemMap[v] .. ".")
            return
        else
            itemMap[v] = libraryName
        end
    end
    return entry
end

-- Replacement powerups
ap.powerReplacements = {}
ap.powerReplacements[3] = ap.registerPowerup("ap_fireflower")
ap.powerReplacements[7] = ap.registerPowerup("ap_iceflower")

-- registerItemTier registers a new tier chain for items.
-- spawnedItem: number - The item that spawns from a block.
-- chain: table - The list of states ordered by significance. Numbers correspond to vanilla states, strings correspond to the names of anotherpowerup powerups. See demo for more info.
-- if you provide chain as "true", it will accept everything except for small mario
function ap.registerItemTier(spawnedItem, chain)
    if chain ~= true then
        for k,v in ipairs(chain) do
            if ap.powerReplacements[v] then
                chain[k] = ap.powerReplacements[v].name
            end
        end
    end
    tiers[spawnedItem] = chain
end

-- Returns the player's current powerup, accounting for anotherpowerup powerups
function ap.getPowerup()
    if player.powerup == 3 or player.powerup == 7 then
        return currentPowerup.name
    else
        return player.powerup
    end
end

function ap.onInitAPI()
    registerEvent(ap, "onPostNPCKill")
    registerEvent(ap, "onPlayerHarm")
    registerEvent(ap, "onTickEnd")
    registerEvent(ap, "onTick")
    registerEvent(ap, "onExit")
    registerEvent(ap, "onDraw")
    registerEvent(ap, "onStart")
end

function ap.onTick()
    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        if player.mount < 2 then
            if player.character ~= CHARACTER_LINK then
                player:mem(0x160, FIELD_WORD, 3) -- Disable the nomral projectile timer. Powerups need to implement their own.
            else                
                player:mem(0x162, FIELD_WORD, 29) -- Disable the link projectile timer. Powerups need to implement their own.
            end
        end
        currentPowerup.onTick()
    end
end

local testMenuWasActive = false

if Misc.inEditor () then
	function ap.onDraw()
		if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
			currentPowerup.onDisable()
			currentPowerup = nil
			SaveData._ap.cp = nil
		end
		if currentPowerup then
			currentPowerup.onDraw()
		end
    
		-- Handle test mode menu, and reload hitboxes after a bit
		if (not testModeMenu.active and testMenuWasActive) or lunatime.tick() == 1 then
			loadPowerupAssets(player,player.powerup)
		end

		testMenuWasActive = testModeMenu.active
	end
end

function ap.onTickEnd()
    for k,v in ipairs(NPC.get(table.unmap(itemMap))) do -- Thankfully this is more performant these days I guess!
        if not v.data._anotherpowerup and v.despawnTimer > 0 then
            v.data._anotherpowerup = true
            local spawn = v:mem(0x138, FIELD_WORD)
            if spawn == 1 or spawn == 3 or spawn == 4 then
                local currentTier
                local list
                if tiers[v.id] ~= nil and tiers[v.id] ~= true then
                    if type(ap.getPowerup()) == "number" then
                        list = powerups[ap.getPowerup()]
                    else
                        list = currentPowerup.items
                    end
                    if list ~= nil then
                        for k,v in ipairs(tiers[v.id]) do
                            currentTier = 0
                            for _,n in ipairs(list) do
                                if n == v then
                                    currentTier = k
                                    break
                                end
                            end
                            if currentTier ~= 0 then break end
                        end
                    else
                        if tiers[v.id] then
                            currentTier = 0
                        end
                    end

                    if currentTier and currentTier < #tiers[v.id] then
                        local nextTier = tiers[v.id][currentTier + 1]
                        v.id = powerups[itemMap[nextTier]].items[1]
                    end
                else
                    if player.powerup == 1 then
                        v.id = 9
                    end
                    return
                end
            end
        end
    end

    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        currentPowerup.onTickEnd()
    end

    if isPoweringUp then
        if not (player.forcedState ~= 0 or (currentPowerup and ap.getPowerup() ~= currentPowerup.name)) then
            player.powerup = isPoweringUp
            isPoweringUp = false
        end
    end
end

function ap.setPlayerPowerup(appower, silent, reservePowerup, thisPlayer)
    thisPlayer = thisPlayer or player
    local nextPower = 7
    if thisPlayer.powerup == 3 then
        nextPower = 3
    end

    if currentPowerup and appower.name ~= currentPowerup.name then
        currentPowerup.onDisable()
    end

    if not silent then
        if currentPowerup and appower.name == currentPowerup.name then
            if appower.apSounds ~= nil and appower.apSounds.reserve ~= nil then
                SFX.play(appower.apSounds.reserve)
            end

            if player.forcedState == 3 or player.forcedState == 41 then
                thisPlayer:mem(0x122, FIELD_WORD, 0)
                thisPlayer:mem(0x124, FIELD_WORD, 0)
            end
            return
        else
            if appower.apSounds ~= nil and appower.apSounds.upgrade ~= nil then
                SFX.play(appower.apSounds.upgrade)
            end

            if nextPower == 3 then
                thisPlayer:mem(0x122, FIELD_WORD, 4)
            else
                thisPlayer:mem(0x122, FIELD_WORD, 41)
            end
            thisPlayer:mem(0x124, FIELD_WORD, 100)
            
            --Audio.sounds[7].sfx = nil
        end
    
        isPoweringUp = nextPower
    else
        thisPlayer.powerup = nextPower
    end

    currentPowerup = appower
    SaveData._ap.cp = appower.name
    
    --thisPlayer.forcedState = 0
    if thisPlayer.character > 5 then
        error("Character IDs above 5 are unsupported.")
        return
    end

    loadPowerupAssets(thisPlayer,nextPower)

    currentPowerup.onEnable()
end

function ap.onStart()
    if Misc.inEditor() then
        SaveData._ap.cp = nil
        SaveData._ap.lastPowerupItem = nil

        currentPowerup = nil

        return
    end
    
    if SaveData._ap.cp then
        ap.setPlayerPowerup(powerups[SaveData._ap.cp], true)
    end
end

function ap.onPostNPCKill(v, r)
    if isPoweringUp then return end

    local p = npcManager.collected(v, r)

    if not p then
        return
    end

    local defaultPowerupID = defaultNPCPowerupMap[v.id]
    local apPowerupName = itemMap[v.id]

    if defaultPowerupID or apPowerupName then
        if p.character >= 3 and p.character <= 5 and not defaultPowerupID then
            p:mem(0x16, FIELD_WORD, p:mem(0x16, FIELD_WORD) + 1)
        end

        if SaveData._ap.lastPowerupItem then
            player.reservePowerup = SaveData._ap.lastPowerupItem
        end

        SaveData._ap.lastPowerupItem = v.id
    end

    if apPowerupName then
        ap.setPlayerPowerup(powerups[apPowerupName], false, v.id, p)
        v:kill(9)
    end
end

function ap.onPlayerHarm(event, p)
    if p.powerup > 1 and p.mount == MOUNT_NONE and not p:mem(0x0C, FIELD_BOOL) and not p.hasStarman then
        SaveData._ap.lastPowerupItem = nil
    end
end


return ap

Emral
Cute Yoshi Egg
Cute Yoshi Egg
Posts: 9891
Joined: Mon Jan 20, 2014 12:58 pm
Flair: Phoenix

Re: Custom Powerups Don't Work When Testing World File

Postby Emral » Fri Nov 04, 2022 3:40 am

Code: Select all

local playerManager = require("playerManager")
if Misc.inEditor() then
local testModeMenu = require("engine/testmodemenu")
end
local npcManager = require("npcManager")

local ap = {}

SaveData._ap = SaveData._ap or {}

local powerups = {}
local currentPowerup
local itemMap = {}
local tiers = {}
local isPoweringUp = false

powerups[2] = {9, 184, 185}
powerups[3] = {14, 182, 183}
powerups[4] = {34}
powerups[5] = {169}
powerups[6] = {170}
powerups[7] = {264, 277}

local defaultNPCPowerupMap = {
    [9]   = PLAYER_BIG,
    [14]  = PLAYER_FIREFLOWER,
    [34]  = PLAYER_LEAF,
    [169] = PLAYER_TANOOKIE,
    [170] = PLAYER_HAMMER,
    [182] = PLAYER_FIREFLOWER,
    [183] = PLAYER_FIREFLOWER,
    [184] = PLAYER_BIG,
    [185] = PLAYER_BIG,
    [249] = PLAYER_BIG,
    [264] = PLAYER_ICE,
    [277] = PLAYER_ICE,
}

local players = {
    "mario", "luigi", "peach", "toad", "link"
}

local function registerItemsInternal(powerup, ids)
    if type(ids) == "number" then
        ids = {}
    elseif type(ids) ~= "table" then
        powerup.items = {}
        return
    end

    for k,v in ipairs(powerup.items) do
        itemMap[v] = nil
    end

    powerup.items = ids

    for k,v in ipairs(ids) do
        if itemMap[v] ~= nil then
            Misc.warn("Item " .. v .. " could not be registered to Powerup " .. libraryName .. ", because it was already registered to Powerup " .. itemMap[v] .. ".")
            return
        else
            itemMap[v] = libraryName
        end
    end
end


local function loadPowerupAssets(thisPlayer,basePowerup)
    if currentPowerup == nil then
        return
    end
    
    local iniFile = Misc.resolveFile(players[thisPlayer.character] .. "-" .. currentPowerup.name .. ".ini")
	
    if (iniFile == nil) then
        iniFile = playerManager.getHitboxPath(thisPlayer.character, basePowerup);
    end

    Misc.loadCharacterHitBoxes(thisPlayer.character, basePowerup, iniFile)

    Graphics.sprites[players[thisPlayer.character]][basePowerup].img = currentPowerup.spritesheets[thisPlayer.character]
end

-- registerPowerup registers a new powerup to the system
--- libraryName: string - The name of the library containing powerup information. One library per powerup. Think of it as a require(libraryName) call

-- Returns the library table for the powerup
function ap.registerPowerup(libraryName)
    local entry = require(libraryName)
    entry.registerItems = registerItemsInternal
    entry.name = libraryName
    entry.items = entry.items or {}
    powerups[libraryName] = entry

    for k,v in ipairs(entry.items) do
        if itemMap[v] ~= nil then
            Misc.warn("Item " .. v .. " could not be registered to Powerup " .. libraryName .. ", because it was already registered to Powerup " .. itemMap[v] .. ".")
            return
        else
            itemMap[v] = libraryName
        end
    end
    return entry
end

-- Replacement powerups
ap.powerReplacements = {}
ap.powerReplacements[3] = ap.registerPowerup("ap_fireflower")
ap.powerReplacements[7] = ap.registerPowerup("ap_iceflower")

-- registerItemTier registers a new tier chain for items.
-- spawnedItem: number - The item that spawns from a block.
-- chain: table - The list of states ordered by significance. Numbers correspond to vanilla states, strings correspond to the names of anotherpowerup powerups. See demo for more info.
-- if you provide chain as "true", it will accept everything except for small mario
function ap.registerItemTier(spawnedItem, chain)
    if chain ~= true then
        for k,v in ipairs(chain) do
            if ap.powerReplacements[v] then
                chain[k] = ap.powerReplacements[v].name
            end
        end
    end
    tiers[spawnedItem] = chain
end

-- Returns the player's current powerup, accounting for anotherpowerup powerups
function ap.getPowerup()
    if player.powerup == 3 or player.powerup == 7 then
        return currentPowerup.name
    else
        return player.powerup
    end
end

function ap.onInitAPI()
    registerEvent(ap, "onPostNPCKill")
    registerEvent(ap, "onPlayerHarm")
    registerEvent(ap, "onTickEnd")
    registerEvent(ap, "onTick")
    registerEvent(ap, "onExit")
    registerEvent(ap, "onDraw")
    registerEvent(ap, "onStart")
end

function ap.onTick()
    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        if player.mount < 2 then
            if player.character ~= CHARACTER_LINK then
                player:mem(0x160, FIELD_WORD, 3) -- Disable the nomral projectile timer. Powerups need to implement their own.
            else                
                player:mem(0x162, FIELD_WORD, 29) -- Disable the link projectile timer. Powerups need to implement their own.
            end
        end
        currentPowerup.onTick()
    end
end

local testMenuWasActive = false

	function ap.onDraw()
		if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
			currentPowerup.onDisable()
			currentPowerup = nil
			SaveData._ap.cp = nil
		end
		if currentPowerup then
			currentPowerup.onDraw()
		end
    
		-- Handle test mode menu, and reload hitboxes after a bit
if Misc.inEditor() then
		if (not testModeMenu.active and testMenuWasActive) or lunatime.tick() == 1 then
			loadPowerupAssets(player,player.powerup)
		end

		testMenuWasActive = testModeMenu.active
end
	end

function ap.onTickEnd()
    for k,v in ipairs(NPC.get(table.unmap(itemMap))) do -- Thankfully this is more performant these days I guess!
        if not v.data._anotherpowerup and v.despawnTimer > 0 then
            v.data._anotherpowerup = true
            local spawn = v:mem(0x138, FIELD_WORD)
            if spawn == 1 or spawn == 3 or spawn == 4 then
                local currentTier
                local list
                if tiers[v.id] ~= nil and tiers[v.id] ~= true then
                    if type(ap.getPowerup()) == "number" then
                        list = powerups[ap.getPowerup()]
                    else
                        list = currentPowerup.items
                    end
                    if list ~= nil then
                        for k,v in ipairs(tiers[v.id]) do
                            currentTier = 0
                            for _,n in ipairs(list) do
                                if n == v then
                                    currentTier = k
                                    break
                                end
                            end
                            if currentTier ~= 0 then break end
                        end
                    else
                        if tiers[v.id] then
                            currentTier = 0
                        end
                    end

                    if currentTier and currentTier < #tiers[v.id] then
                        local nextTier = tiers[v.id][currentTier + 1]
                        v.id = powerups[itemMap[nextTier]].items[1]
                    end
                else
                    if player.powerup == 1 then
                        v.id = 9
                    end
                    return
                end
            end
        end
    end

    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        currentPowerup.onTickEnd()
    end

    if isPoweringUp then
        if not (player.forcedState ~= 0 or (currentPowerup and ap.getPowerup() ~= currentPowerup.name)) then
            player.powerup = isPoweringUp
            isPoweringUp = false
        end
    end
end

function ap.setPlayerPowerup(appower, silent, reservePowerup, thisPlayer)
    thisPlayer = thisPlayer or player
    local nextPower = 7
    if thisPlayer.powerup == 3 then
        nextPower = 3
    end

    if currentPowerup and appower.name ~= currentPowerup.name then
        currentPowerup.onDisable()
    end

    if not silent then
        if currentPowerup and appower.name == currentPowerup.name then
            if appower.apSounds ~= nil and appower.apSounds.reserve ~= nil then
                SFX.play(appower.apSounds.reserve)
            end

            if player.forcedState == 3 or player.forcedState == 41 then
                thisPlayer:mem(0x122, FIELD_WORD, 0)
                thisPlayer:mem(0x124, FIELD_WORD, 0)
            end
            return
        else
            if appower.apSounds ~= nil and appower.apSounds.upgrade ~= nil then
                SFX.play(appower.apSounds.upgrade)
            end

            if nextPower == 3 then
                thisPlayer:mem(0x122, FIELD_WORD, 4)
            else
                thisPlayer:mem(0x122, FIELD_WORD, 41)
            end
            thisPlayer:mem(0x124, FIELD_WORD, 100)
            
            --Audio.sounds[7].sfx = nil
        end
    
        isPoweringUp = nextPower
    else
        thisPlayer.powerup = nextPower
    end

    currentPowerup = appower
    SaveData._ap.cp = appower.name
    
    --thisPlayer.forcedState = 0
    if thisPlayer.character > 5 then
        error("Character IDs above 5 are unsupported.")
        return
    end

    loadPowerupAssets(thisPlayer,nextPower)

    currentPowerup.onEnable()
end

function ap.onStart()
    if Misc.inEditor() then
        SaveData._ap.cp = nil
        SaveData._ap.lastPowerupItem = nil

        currentPowerup = nil

        return
    end
    
    if SaveData._ap.cp then
        ap.setPlayerPowerup(powerups[SaveData._ap.cp], true)
    end
end

function ap.onPostNPCKill(v, r)
    if isPoweringUp then return end

    local p = npcManager.collected(v, r)

    if not p then
        return
    end

    local defaultPowerupID = defaultNPCPowerupMap[v.id]
    local apPowerupName = itemMap[v.id]

    if defaultPowerupID or apPowerupName then
        if p.character >= 3 and p.character <= 5 and not defaultPowerupID then
            p:mem(0x16, FIELD_WORD, p:mem(0x16, FIELD_WORD) + 1)
        end

        if SaveData._ap.lastPowerupItem then
            player.reservePowerup = SaveData._ap.lastPowerupItem
        end

        SaveData._ap.lastPowerupItem = v.id
    end

    if apPowerupName then
        ap.setPlayerPowerup(powerups[apPowerupName], false, v.id, p)
        v:kill(9)
    end
end

function ap.onPlayerHarm(event, p)
    if p.powerup > 1 and p.mount == MOUNT_NONE and not p:mem(0x0C, FIELD_BOOL) and not p.hasStarman then
        SaveData._ap.lastPowerupItem = nil
    end
end


return ap
try this

Cambroski
Shy Guy
Shy Guy
Posts: 8
Joined: Tue Nov 01, 2022 7:29 pm
Flair: Awake by day, asleep by night.

Re: Custom Powerups Don't Work When Testing World File

Postby Cambroski » Fri Nov 04, 2022 9:27 am

Enjl wrote:
Fri Nov 04, 2022 3:40 am

Code: Select all

local playerManager = require("playerManager")
if Misc.inEditor() then
local testModeMenu = require("engine/testmodemenu")
end
local npcManager = require("npcManager")

local ap = {}

SaveData._ap = SaveData._ap or {}

local powerups = {}
local currentPowerup
local itemMap = {}
local tiers = {}
local isPoweringUp = false

powerups[2] = {9, 184, 185}
powerups[3] = {14, 182, 183}
powerups[4] = {34}
powerups[5] = {169}
powerups[6] = {170}
powerups[7] = {264, 277}

local defaultNPCPowerupMap = {
    [9]   = PLAYER_BIG,
    [14]  = PLAYER_FIREFLOWER,
    [34]  = PLAYER_LEAF,
    [169] = PLAYER_TANOOKIE,
    [170] = PLAYER_HAMMER,
    [182] = PLAYER_FIREFLOWER,
    [183] = PLAYER_FIREFLOWER,
    [184] = PLAYER_BIG,
    [185] = PLAYER_BIG,
    [249] = PLAYER_BIG,
    [264] = PLAYER_ICE,
    [277] = PLAYER_ICE,
}

local players = {
    "mario", "luigi", "peach", "toad", "link"
}

local function registerItemsInternal(powerup, ids)
    if type(ids) == "number" then
        ids = {}
    elseif type(ids) ~= "table" then
        powerup.items = {}
        return
    end

    for k,v in ipairs(powerup.items) do
        itemMap[v] = nil
    end

    powerup.items = ids

    for k,v in ipairs(ids) do
        if itemMap[v] ~= nil then
            Misc.warn("Item " .. v .. " could not be registered to Powerup " .. libraryName .. ", because it was already registered to Powerup " .. itemMap[v] .. ".")
            return
        else
            itemMap[v] = libraryName
        end
    end
end


local function loadPowerupAssets(thisPlayer,basePowerup)
    if currentPowerup == nil then
        return
    end
    
    local iniFile = Misc.resolveFile(players[thisPlayer.character] .. "-" .. currentPowerup.name .. ".ini")
	
    if (iniFile == nil) then
        iniFile = playerManager.getHitboxPath(thisPlayer.character, basePowerup);
    end

    Misc.loadCharacterHitBoxes(thisPlayer.character, basePowerup, iniFile)

    Graphics.sprites[players[thisPlayer.character]][basePowerup].img = currentPowerup.spritesheets[thisPlayer.character]
end

-- registerPowerup registers a new powerup to the system
--- libraryName: string - The name of the library containing powerup information. One library per powerup. Think of it as a require(libraryName) call

-- Returns the library table for the powerup
function ap.registerPowerup(libraryName)
    local entry = require(libraryName)
    entry.registerItems = registerItemsInternal
    entry.name = libraryName
    entry.items = entry.items or {}
    powerups[libraryName] = entry

    for k,v in ipairs(entry.items) do
        if itemMap[v] ~= nil then
            Misc.warn("Item " .. v .. " could not be registered to Powerup " .. libraryName .. ", because it was already registered to Powerup " .. itemMap[v] .. ".")
            return
        else
            itemMap[v] = libraryName
        end
    end
    return entry
end

-- Replacement powerups
ap.powerReplacements = {}
ap.powerReplacements[3] = ap.registerPowerup("ap_fireflower")
ap.powerReplacements[7] = ap.registerPowerup("ap_iceflower")

-- registerItemTier registers a new tier chain for items.
-- spawnedItem: number - The item that spawns from a block.
-- chain: table - The list of states ordered by significance. Numbers correspond to vanilla states, strings correspond to the names of anotherpowerup powerups. See demo for more info.
-- if you provide chain as "true", it will accept everything except for small mario
function ap.registerItemTier(spawnedItem, chain)
    if chain ~= true then
        for k,v in ipairs(chain) do
            if ap.powerReplacements[v] then
                chain[k] = ap.powerReplacements[v].name
            end
        end
    end
    tiers[spawnedItem] = chain
end

-- Returns the player's current powerup, accounting for anotherpowerup powerups
function ap.getPowerup()
    if player.powerup == 3 or player.powerup == 7 then
        return currentPowerup.name
    else
        return player.powerup
    end
end

function ap.onInitAPI()
    registerEvent(ap, "onPostNPCKill")
    registerEvent(ap, "onPlayerHarm")
    registerEvent(ap, "onTickEnd")
    registerEvent(ap, "onTick")
    registerEvent(ap, "onExit")
    registerEvent(ap, "onDraw")
    registerEvent(ap, "onStart")
end

function ap.onTick()
    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        if player.mount < 2 then
            if player.character ~= CHARACTER_LINK then
                player:mem(0x160, FIELD_WORD, 3) -- Disable the nomral projectile timer. Powerups need to implement their own.
            else                
                player:mem(0x162, FIELD_WORD, 29) -- Disable the link projectile timer. Powerups need to implement their own.
            end
        end
        currentPowerup.onTick()
    end
end

local testMenuWasActive = false

	function ap.onDraw()
		if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
			currentPowerup.onDisable()
			currentPowerup = nil
			SaveData._ap.cp = nil
		end
		if currentPowerup then
			currentPowerup.onDraw()
		end
    
		-- Handle test mode menu, and reload hitboxes after a bit
if Misc.inEditor() then
		if (not testModeMenu.active and testMenuWasActive) or lunatime.tick() == 1 then
			loadPowerupAssets(player,player.powerup)
		end

		testMenuWasActive = testModeMenu.active
end
	end

function ap.onTickEnd()
    for k,v in ipairs(NPC.get(table.unmap(itemMap))) do -- Thankfully this is more performant these days I guess!
        if not v.data._anotherpowerup and v.despawnTimer > 0 then
            v.data._anotherpowerup = true
            local spawn = v:mem(0x138, FIELD_WORD)
            if spawn == 1 or spawn == 3 or spawn == 4 then
                local currentTier
                local list
                if tiers[v.id] ~= nil and tiers[v.id] ~= true then
                    if type(ap.getPowerup()) == "number" then
                        list = powerups[ap.getPowerup()]
                    else
                        list = currentPowerup.items
                    end
                    if list ~= nil then
                        for k,v in ipairs(tiers[v.id]) do
                            currentTier = 0
                            for _,n in ipairs(list) do
                                if n == v then
                                    currentTier = k
                                    break
                                end
                            end
                            if currentTier ~= 0 then break end
                        end
                    else
                        if tiers[v.id] then
                            currentTier = 0
                        end
                    end

                    if currentTier and currentTier < #tiers[v.id] then
                        local nextTier = tiers[v.id][currentTier + 1]
                        v.id = powerups[itemMap[nextTier]].items[1]
                    end
                else
                    if player.powerup == 1 then
                        v.id = 9
                    end
                    return
                end
            end
        end
    end

    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        currentPowerup.onTickEnd()
    end

    if isPoweringUp then
        if not (player.forcedState ~= 0 or (currentPowerup and ap.getPowerup() ~= currentPowerup.name)) then
            player.powerup = isPoweringUp
            isPoweringUp = false
        end
    end
end

function ap.setPlayerPowerup(appower, silent, reservePowerup, thisPlayer)
    thisPlayer = thisPlayer or player
    local nextPower = 7
    if thisPlayer.powerup == 3 then
        nextPower = 3
    end

    if currentPowerup and appower.name ~= currentPowerup.name then
        currentPowerup.onDisable()
    end

    if not silent then
        if currentPowerup and appower.name == currentPowerup.name then
            if appower.apSounds ~= nil and appower.apSounds.reserve ~= nil then
                SFX.play(appower.apSounds.reserve)
            end

            if player.forcedState == 3 or player.forcedState == 41 then
                thisPlayer:mem(0x122, FIELD_WORD, 0)
                thisPlayer:mem(0x124, FIELD_WORD, 0)
            end
            return
        else
            if appower.apSounds ~= nil and appower.apSounds.upgrade ~= nil then
                SFX.play(appower.apSounds.upgrade)
            end

            if nextPower == 3 then
                thisPlayer:mem(0x122, FIELD_WORD, 4)
            else
                thisPlayer:mem(0x122, FIELD_WORD, 41)
            end
            thisPlayer:mem(0x124, FIELD_WORD, 100)
            
            --Audio.sounds[7].sfx = nil
        end
    
        isPoweringUp = nextPower
    else
        thisPlayer.powerup = nextPower
    end

    currentPowerup = appower
    SaveData._ap.cp = appower.name
    
    --thisPlayer.forcedState = 0
    if thisPlayer.character > 5 then
        error("Character IDs above 5 are unsupported.")
        return
    end

    loadPowerupAssets(thisPlayer,nextPower)

    currentPowerup.onEnable()
end

function ap.onStart()
    if Misc.inEditor() then
        SaveData._ap.cp = nil
        SaveData._ap.lastPowerupItem = nil

        currentPowerup = nil

        return
    end
    
    if SaveData._ap.cp then
        ap.setPlayerPowerup(powerups[SaveData._ap.cp], true)
    end
end

function ap.onPostNPCKill(v, r)
    if isPoweringUp then return end

    local p = npcManager.collected(v, r)

    if not p then
        return
    end

    local defaultPowerupID = defaultNPCPowerupMap[v.id]
    local apPowerupName = itemMap[v.id]

    if defaultPowerupID or apPowerupName then
        if p.character >= 3 and p.character <= 5 and not defaultPowerupID then
            p:mem(0x16, FIELD_WORD, p:mem(0x16, FIELD_WORD) + 1)
        end

        if SaveData._ap.lastPowerupItem then
            player.reservePowerup = SaveData._ap.lastPowerupItem
        end

        SaveData._ap.lastPowerupItem = v.id
    end

    if apPowerupName then
        ap.setPlayerPowerup(powerups[apPowerupName], false, v.id, p)
        v:kill(9)
    end
end

function ap.onPlayerHarm(event, p)
    if p.powerup > 1 and p.mount == MOUNT_NONE and not p:mem(0x0C, FIELD_BOOL) and not p.hasStarman then
        SaveData._ap.lastPowerupItem = nil
    end
end


return ap
try this
Yes this worked. I was worried it might be a problem with my install of SMBX2 since it seemed like nobody else had this problem. I'm curious to know why the code was causing the error in the first place. Thank you for your help!

Emral
Cute Yoshi Egg
Cute Yoshi Egg
Posts: 9891
Joined: Mon Jan 20, 2014 12:58 pm
Flair: Phoenix

Re: Custom Powerups Don't Work When Testing World File

Postby Emral » Fri Nov 04, 2022 9:52 pm

Cambroski wrote:
Fri Nov 04, 2022 9:27 am

Yes this worked. I was worried it might be a problem with my install of SMBX2 since it seemed like nobody else had this problem. I'm curious to know why the code was causing the error in the first place. Thank you for your help!
Nobody else had the problem because you appear to be the first person to make an episode using this script and I didn't test thoroughly enough when writing it, hah.
I'll commit the fix to the download in the coming days, then it should be taken care of for other people in the future, too.

Cambroski
Shy Guy
Shy Guy
Posts: 8
Joined: Tue Nov 01, 2022 7:29 pm
Flair: Awake by day, asleep by night.

Re: Custom Powerups Don't Work When Testing World File

Postby Cambroski » Sat Nov 05, 2022 12:19 am

Enjl wrote:
Fri Nov 04, 2022 9:52 pm
Cambroski wrote:
Fri Nov 04, 2022 9:27 am

Yes this worked. I was worried it might be a problem with my install of SMBX2 since it seemed like nobody else had this problem. I'm curious to know why the code was causing the error in the first place. Thank you for your help!
Nobody else had the problem because you appear to be the first person to make an episode using this script and I didn't test thoroughly enough when writing it, hah.
I'll commit the fix to the download in the coming days, then it should be taken care of for other people in the future, too.
Another problem that arises with the fixed code that you provided is that it no longer works on level files. This time it has two different errors.

Here is the first.

Code: Select all

==> worlds/WIP Game/libs/anotherpowerup.lua:188: attempt to index global 'testModeMenu' (a nil value)
===========================================================================================
stack traceback:
	scripts/base/engine/main_events.lua:63: in function 'callApiListeners'
	scripts/base/engine/main_events.lua:169: in function 'callEventInternal'
	scripts/base/engine/main_events.lua:240: in function <scripts/base/engine/main_events.lua:218>
	[C]: in function '__xpcall'
	main.lua:781: in function <main.lua:780> 
Here is the second.

Code: Select all

==> scripts/base/HUDOverride.lua:773: attemt to index upvalue 'activeCameras' (a nil value)
================================================================================
stack traceback:
	scripts/base/engine/main_events.lua:63: in function 'callApiListeners'
	scripts/base/engine/main_events.lua:184: in function 'callEventInternal'
	scripts/base/engine/main_events.lua:240: in function <scripts/base/engine/main_events.lua:218>
	[C]: in function '__xpcall'
	main.lua:781: in function <main.lua:780>
Thanks for your commitment to the SMBX community bro.

Emral
Cute Yoshi Egg
Cute Yoshi Egg
Posts: 9891
Joined: Mon Jan 20, 2014 12:58 pm
Flair: Phoenix

Re: Custom Powerups Don't Work When Testing World File

Postby Emral » Sat Nov 05, 2022 5:31 am

oops
try this

Code: Select all

-- Amotherpowerup serves as a wrapper for an endless powerup system.
-- Features:
--- Infinite Powerups!
--- Custom collision for every individual powerup!
--- Unique images for every individual powerup!
--- Events that happen on powerup switch!
--- Define custom powerup tiers!
-- Limitations:
--- Every powerup will behave as if the player is 'big'
--- Only applicable to Mario, Peach, Luigi, Toad. Not Link. Not SMBX2 Characters.

local playerManager = require("playerManager")
if Misc.inEditor() then
    local testModeMenu = require("engine/testmodemenu")
end
local npcManager = require("npcManager")

local ap = {}

SaveData._ap = SaveData._ap or {}

local powerups = {}
local currentPowerup
local itemMap = {}
local tiers = {}
local isPoweringUp = false

powerups[2] = {9, 184, 185}
powerups[3] = {14, 182, 183}
powerups[4] = {34}
powerups[5] = {169}
powerups[6] = {170}
powerups[7] = {264, 277}

local defaultNPCPowerupMap = {
    [9]   = PLAYER_BIG,
    [14]  = PLAYER_FIREFLOWER,
    [34]  = PLAYER_LEAF,
    [169] = PLAYER_TANOOKIE,
    [170] = PLAYER_HAMMER,
    [182] = PLAYER_FIREFLOWER,
    [183] = PLAYER_FIREFLOWER,
    [184] = PLAYER_BIG,
    [185] = PLAYER_BIG,
    [249] = PLAYER_BIG,
    [264] = PLAYER_ICE,
    [277] = PLAYER_ICE,
}

local players = {
    "mario", "luigi", "peach", "toad", "link"
}

local function registerItemsInternal(powerup, ids)
    if type(ids) == "number" then
        ids = {}
    elseif type(ids) ~= "table" then
        powerup.items = {}
        return
    end

    for k,v in ipairs(powerup.items) do
        itemMap[v] = nil
    end

    powerup.items = ids

    for k,v in ipairs(ids) do
        if itemMap[v] ~= nil then
            Misc.warn("Item " .. v .. " could not be registered to Powerup " .. libraryName .. ", because it was already registered to Powerup " .. itemMap[v] .. ".")
            return
        else
            itemMap[v] = libraryName
        end
    end
end


local function loadPowerupAssets(thisPlayer,basePowerup)
    if currentPowerup == nil then
        return
    end
    
    local iniFile = Misc.resolveFile(players[thisPlayer.character] .. "-" .. currentPowerup.name .. ".ini")
	
    if (iniFile == nil) then
        iniFile = playerManager.getHitboxPath(thisPlayer.character, basePowerup);
    end

    Misc.loadCharacterHitBoxes(thisPlayer.character, basePowerup, iniFile)

    Graphics.sprites[players[thisPlayer.character]][basePowerup].img = currentPowerup.spritesheets[thisPlayer.character]
end

-- registerPowerup registers a new powerup to the system
--- libraryName: string - The name of the library containing powerup information. One library per powerup. Think of it as a require(libraryName) call

-- Returns the library table for the powerup
function ap.registerPowerup(libraryName)
    local entry = require(libraryName)
    entry.registerItems = registerItemsInternal
    entry.name = libraryName
    entry.items = entry.items or {}
    powerups[libraryName] = entry

    for k,v in ipairs(entry.items) do
        if itemMap[v] ~= nil then
            Misc.warn("Item " .. v .. " could not be registered to Powerup " .. libraryName .. ", because it was already registered to Powerup " .. itemMap[v] .. ".")
            return
        else
            itemMap[v] = libraryName
        end
    end
    return entry
end

-- Replacement powerups
ap.powerReplacements = {}
ap.powerReplacements[3] = ap.registerPowerup("ap_fireflower")
ap.powerReplacements[7] = ap.registerPowerup("ap_iceflower")

-- registerItemTier registers a new tier chain for items.
-- spawnedItem: number - The item that spawns from a block.
-- chain: table - The list of states ordered by significance. Numbers correspond to vanilla states, strings correspond to the names of anotherpowerup powerups. See demo for more info.
-- if you provide chain as "true", it will accept everything except for small mario
function ap.registerItemTier(spawnedItem, chain)
    if chain ~= true then
        for k,v in ipairs(chain) do
            if ap.powerReplacements[v] then
                chain[k] = ap.powerReplacements[v].name
            end
        end
    end
    tiers[spawnedItem] = chain
end

-- Returns the player's current powerup, accounting for anotherpowerup powerups
function ap.getPowerup()
    if player.powerup == 3 or player.powerup == 7 then
        return currentPowerup.name
    else
        return player.powerup
    end
end

function ap.onInitAPI()
    registerEvent(ap, "onPostNPCKill")
    registerEvent(ap, "onTickEnd")
    registerEvent(ap, "onTick")
    registerEvent(ap, "onExit")
    registerEvent(ap, "onDraw")
    registerEvent(ap, "onStart")
end

function ap.onTick()
    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        if player.mount < 2 then
            if player.character ~= CHARACTER_LINK then
                player:mem(0x160, FIELD_WORD, 3) -- Disable the nomral projectile timer. Powerups need to implement their own.
            else                
                player:mem(0x162, FIELD_WORD, 29) -- Disable the link projectile timer. Powerups need to implement their own.
            end
        end
        currentPowerup.onTick()
    end
end

local testMenuWasActive = false

function ap.onDraw()
    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        currentPowerup.onDraw()
    end
    
    -- Handle test mode menu, and reload hitboxes after a bit
    if (Misc.inEditor() and (not testModeMenu.active) and testMenuWasActive) or lunatime.tick() == 1 then
        loadPowerupAssets(player,player.powerup)
    end

    if Misc.inEditor() then
        testMenuWasActive = testModeMenu.active
    end
end

function ap.onTickEnd()
    for k,v in ipairs(NPC.get(table.unmap(itemMap))) do -- Thankfully this is more performant these days I guess!
        if not v.data._anotherpowerup and v.despawnTimer > 0 then
            v.data._anotherpowerup = true
            local spawn = v:mem(0x138, FIELD_WORD)
            if spawn == 1 or spawn == 3 or spawn == 4 then
                local currentTier
                local list
                if tiers[v.id] ~= nil and tiers[v.id] ~= true then
                    if type(ap.getPowerup()) == "number" then
                        list = powerups[ap.getPowerup()]
                    else
                        list = currentPowerup.items
                    end
                    if list ~= nil then
                        for k,v in ipairs(tiers[v.id]) do
                            currentTier = 0
                            for _,n in ipairs(list) do
                                if n == v then
                                    currentTier = k
                                    break
                                end
                            end
                            if currentTier ~= 0 then break end
                        end
                    else
                        if tiers[v.id] then
                            currentTier = 0
                        end
                    end

                    if currentTier and currentTier < #tiers[v.id] then
                        local nextTier = tiers[v.id][currentTier + 1]
                        v.id = powerups[itemMap[nextTier]].items[1]
                    end
                else
                    if player.powerup == 1 then
                        v.id = 9
                    end
                    return
                end
            end
        end
    end

    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        currentPowerup.onTickEnd()
    end

    if isPoweringUp then
        if not (player.forcedState ~= 0 or (currentPowerup and ap.getPowerup() ~= currentPowerup.name)) then
            player.powerup = isPoweringUp
            isPoweringUp = false
        end
    end
end

function ap.setPlayerPowerup(appower, silent, reservePowerup, thisPlayer)
    thisPlayer = thisPlayer or player
    local nextPower = 7
    if thisPlayer.powerup == 3 then
        nextPower = 3
    end

    if currentPowerup and appower.name ~= currentPowerup.name then
        currentPowerup.onDisable()
    end

    if not silent then
        if currentPowerup and appower.name == currentPowerup.name then
            if appower.apSounds ~= nil and appower.apSounds.reserve ~= nil then
                SFX.play(appower.apSounds.reserve)
            end

            if player.forcedState == 3 or player.forcedState == 41 then
                thisPlayer:mem(0x122, FIELD_WORD, 0)
                thisPlayer:mem(0x124, FIELD_WORD, 0)
            end
            return
        else
            if appower.apSounds ~= nil and appower.apSounds.upgrade ~= nil then
                SFX.play(appower.apSounds.upgrade)
            end

            if nextPower == 3 then
                thisPlayer:mem(0x122, FIELD_WORD, 4)
            else
                thisPlayer:mem(0x122, FIELD_WORD, 41)
            end
            thisPlayer:mem(0x124, FIELD_WORD, 100)
            
            --Audio.sounds[7].sfx = nil
        end
    
        isPoweringUp = nextPower
    else
        thisPlayer.powerup = nextPower
    end

    currentPowerup = appower
    SaveData._ap.cp = appower.name
    
    --thisPlayer.forcedState = 0
    if thisPlayer.character > 5 then
        error("Character IDs above 5 are unsupported.")
        return
    end

    loadPowerupAssets(thisPlayer,nextPower)

    currentPowerup.onEnable()
end

function ap.onStart()
    if Misc.inEditor() then
        SaveData._ap.cp = nil
        SaveData._ap.lastPowerupItem = nil

        currentPowerup = nil

        return
    end
    
    if SaveData._ap.cp then
        ap.setPlayerPowerup(powerups[SaveData._ap.cp], true)
    end
end

function ap.onPostNPCKill(v, r)
    if isPoweringUp then return end

    local p = npcManager.collected(v, r)

    if not p then
        return
    end

    local defaultPowerupID = defaultNPCPowerupMap[v.id]
    local apPowerupName = itemMap[v.id]

    if defaultPowerupID or apPowerupName then
        if p.character >= 3 and p.character <= 5 and not defaultPowerupID then
            p:mem(0x16, FIELD_WORD, p:mem(0x16, FIELD_WORD) + 1)
        end

        if SaveData._ap.lastPowerupItem then
            player.reservePowerup = SaveData._ap.lastPowerupItem
        end

        SaveData._ap.lastPowerupItem = v.id
    end

    if apPowerupName then
        ap.setPlayerPowerup(powerups[apPowerupName], false, v.id, p)
        v:kill(9)
    end
end

return ap

Cambroski
Shy Guy
Shy Guy
Posts: 8
Joined: Tue Nov 01, 2022 7:29 pm
Flair: Awake by day, asleep by night.

Re: Custom Powerups Don't Work When Testing World File

Postby Cambroski » Sat Nov 05, 2022 11:07 am

Enjl wrote:
Sat Nov 05, 2022 5:31 am
oops
try this

Code: Select all

-- Amotherpowerup serves as a wrapper for an endless powerup system.
-- Features:
--- Infinite Powerups!
--- Custom collision for every individual powerup!
--- Unique images for every individual powerup!
--- Events that happen on powerup switch!
--- Define custom powerup tiers!
-- Limitations:
--- Every powerup will behave as if the player is 'big'
--- Only applicable to Mario, Peach, Luigi, Toad. Not Link. Not SMBX2 Characters.

local playerManager = require("playerManager")
if Misc.inEditor() then
    local testModeMenu = require("engine/testmodemenu")
end
local npcManager = require("npcManager")

local ap = {}

SaveData._ap = SaveData._ap or {}

local powerups = {}
local currentPowerup
local itemMap = {}
local tiers = {}
local isPoweringUp = false

powerups[2] = {9, 184, 185}
powerups[3] = {14, 182, 183}
powerups[4] = {34}
powerups[5] = {169}
powerups[6] = {170}
powerups[7] = {264, 277}

local defaultNPCPowerupMap = {
    [9]   = PLAYER_BIG,
    [14]  = PLAYER_FIREFLOWER,
    [34]  = PLAYER_LEAF,
    [169] = PLAYER_TANOOKIE,
    [170] = PLAYER_HAMMER,
    [182] = PLAYER_FIREFLOWER,
    [183] = PLAYER_FIREFLOWER,
    [184] = PLAYER_BIG,
    [185] = PLAYER_BIG,
    [249] = PLAYER_BIG,
    [264] = PLAYER_ICE,
    [277] = PLAYER_ICE,
}

local players = {
    "mario", "luigi", "peach", "toad", "link"
}

local function registerItemsInternal(powerup, ids)
    if type(ids) == "number" then
        ids = {}
    elseif type(ids) ~= "table" then
        powerup.items = {}
        return
    end

    for k,v in ipairs(powerup.items) do
        itemMap[v] = nil
    end

    powerup.items = ids

    for k,v in ipairs(ids) do
        if itemMap[v] ~= nil then
            Misc.warn("Item " .. v .. " could not be registered to Powerup " .. libraryName .. ", because it was already registered to Powerup " .. itemMap[v] .. ".")
            return
        else
            itemMap[v] = libraryName
        end
    end
end


local function loadPowerupAssets(thisPlayer,basePowerup)
    if currentPowerup == nil then
        return
    end
    
    local iniFile = Misc.resolveFile(players[thisPlayer.character] .. "-" .. currentPowerup.name .. ".ini")
	
    if (iniFile == nil) then
        iniFile = playerManager.getHitboxPath(thisPlayer.character, basePowerup);
    end

    Misc.loadCharacterHitBoxes(thisPlayer.character, basePowerup, iniFile)

    Graphics.sprites[players[thisPlayer.character]][basePowerup].img = currentPowerup.spritesheets[thisPlayer.character]
end

-- registerPowerup registers a new powerup to the system
--- libraryName: string - The name of the library containing powerup information. One library per powerup. Think of it as a require(libraryName) call

-- Returns the library table for the powerup
function ap.registerPowerup(libraryName)
    local entry = require(libraryName)
    entry.registerItems = registerItemsInternal
    entry.name = libraryName
    entry.items = entry.items or {}
    powerups[libraryName] = entry

    for k,v in ipairs(entry.items) do
        if itemMap[v] ~= nil then
            Misc.warn("Item " .. v .. " could not be registered to Powerup " .. libraryName .. ", because it was already registered to Powerup " .. itemMap[v] .. ".")
            return
        else
            itemMap[v] = libraryName
        end
    end
    return entry
end

-- Replacement powerups
ap.powerReplacements = {}
ap.powerReplacements[3] = ap.registerPowerup("ap_fireflower")
ap.powerReplacements[7] = ap.registerPowerup("ap_iceflower")

-- registerItemTier registers a new tier chain for items.
-- spawnedItem: number - The item that spawns from a block.
-- chain: table - The list of states ordered by significance. Numbers correspond to vanilla states, strings correspond to the names of anotherpowerup powerups. See demo for more info.
-- if you provide chain as "true", it will accept everything except for small mario
function ap.registerItemTier(spawnedItem, chain)
    if chain ~= true then
        for k,v in ipairs(chain) do
            if ap.powerReplacements[v] then
                chain[k] = ap.powerReplacements[v].name
            end
        end
    end
    tiers[spawnedItem] = chain
end

-- Returns the player's current powerup, accounting for anotherpowerup powerups
function ap.getPowerup()
    if player.powerup == 3 or player.powerup == 7 then
        return currentPowerup.name
    else
        return player.powerup
    end
end

function ap.onInitAPI()
    registerEvent(ap, "onPostNPCKill")
    registerEvent(ap, "onTickEnd")
    registerEvent(ap, "onTick")
    registerEvent(ap, "onExit")
    registerEvent(ap, "onDraw")
    registerEvent(ap, "onStart")
end

function ap.onTick()
    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        if player.mount < 2 then
            if player.character ~= CHARACTER_LINK then
                player:mem(0x160, FIELD_WORD, 3) -- Disable the nomral projectile timer. Powerups need to implement their own.
            else                
                player:mem(0x162, FIELD_WORD, 29) -- Disable the link projectile timer. Powerups need to implement their own.
            end
        end
        currentPowerup.onTick()
    end
end

local testMenuWasActive = false

function ap.onDraw()
    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        currentPowerup.onDraw()
    end
    
    -- Handle test mode menu, and reload hitboxes after a bit
    if (Misc.inEditor() and (not testModeMenu.active) and testMenuWasActive) or lunatime.tick() == 1 then
        loadPowerupAssets(player,player.powerup)
    end

    if Misc.inEditor() then
        testMenuWasActive = testModeMenu.active
    end
end

function ap.onTickEnd()
    for k,v in ipairs(NPC.get(table.unmap(itemMap))) do -- Thankfully this is more performant these days I guess!
        if not v.data._anotherpowerup and v.despawnTimer > 0 then
            v.data._anotherpowerup = true
            local spawn = v:mem(0x138, FIELD_WORD)
            if spawn == 1 or spawn == 3 or spawn == 4 then
                local currentTier
                local list
                if tiers[v.id] ~= nil and tiers[v.id] ~= true then
                    if type(ap.getPowerup()) == "number" then
                        list = powerups[ap.getPowerup()]
                    else
                        list = currentPowerup.items
                    end
                    if list ~= nil then
                        for k,v in ipairs(tiers[v.id]) do
                            currentTier = 0
                            for _,n in ipairs(list) do
                                if n == v then
                                    currentTier = k
                                    break
                                end
                            end
                            if currentTier ~= 0 then break end
                        end
                    else
                        if tiers[v.id] then
                            currentTier = 0
                        end
                    end

                    if currentTier and currentTier < #tiers[v.id] then
                        local nextTier = tiers[v.id][currentTier + 1]
                        v.id = powerups[itemMap[nextTier]].items[1]
                    end
                else
                    if player.powerup == 1 then
                        v.id = 9
                    end
                    return
                end
            end
        end
    end

    if currentPowerup and player.forcedState == 0 and ap.getPowerup() ~= currentPowerup.name then
        currentPowerup.onDisable()
        currentPowerup = nil
        SaveData._ap.cp = nil
    end
    if currentPowerup then
        currentPowerup.onTickEnd()
    end

    if isPoweringUp then
        if not (player.forcedState ~= 0 or (currentPowerup and ap.getPowerup() ~= currentPowerup.name)) then
            player.powerup = isPoweringUp
            isPoweringUp = false
        end
    end
end

function ap.setPlayerPowerup(appower, silent, reservePowerup, thisPlayer)
    thisPlayer = thisPlayer or player
    local nextPower = 7
    if thisPlayer.powerup == 3 then
        nextPower = 3
    end

    if currentPowerup and appower.name ~= currentPowerup.name then
        currentPowerup.onDisable()
    end

    if not silent then
        if currentPowerup and appower.name == currentPowerup.name then
            if appower.apSounds ~= nil and appower.apSounds.reserve ~= nil then
                SFX.play(appower.apSounds.reserve)
            end

            if player.forcedState == 3 or player.forcedState == 41 then
                thisPlayer:mem(0x122, FIELD_WORD, 0)
                thisPlayer:mem(0x124, FIELD_WORD, 0)
            end
            return
        else
            if appower.apSounds ~= nil and appower.apSounds.upgrade ~= nil then
                SFX.play(appower.apSounds.upgrade)
            end

            if nextPower == 3 then
                thisPlayer:mem(0x122, FIELD_WORD, 4)
            else
                thisPlayer:mem(0x122, FIELD_WORD, 41)
            end
            thisPlayer:mem(0x124, FIELD_WORD, 100)
            
            --Audio.sounds[7].sfx = nil
        end
    
        isPoweringUp = nextPower
    else
        thisPlayer.powerup = nextPower
    end

    currentPowerup = appower
    SaveData._ap.cp = appower.name
    
    --thisPlayer.forcedState = 0
    if thisPlayer.character > 5 then
        error("Character IDs above 5 are unsupported.")
        return
    end

    loadPowerupAssets(thisPlayer,nextPower)

    currentPowerup.onEnable()
end

function ap.onStart()
    if Misc.inEditor() then
        SaveData._ap.cp = nil
        SaveData._ap.lastPowerupItem = nil

        currentPowerup = nil

        return
    end
    
    if SaveData._ap.cp then
        ap.setPlayerPowerup(powerups[SaveData._ap.cp], true)
    end
end

function ap.onPostNPCKill(v, r)
    if isPoweringUp then return end

    local p = npcManager.collected(v, r)

    if not p then
        return
    end

    local defaultPowerupID = defaultNPCPowerupMap[v.id]
    local apPowerupName = itemMap[v.id]

    if defaultPowerupID or apPowerupName then
        if p.character >= 3 and p.character <= 5 and not defaultPowerupID then
            p:mem(0x16, FIELD_WORD, p:mem(0x16, FIELD_WORD) + 1)
        end

        if SaveData._ap.lastPowerupItem then
            player.reservePowerup = SaveData._ap.lastPowerupItem
        end

        SaveData._ap.lastPowerupItem = v.id
    end

    if apPowerupName then
        ap.setPlayerPowerup(powerups[apPowerupName], false, v.id, p)
        v:kill(9)
    end
end

return ap
Sadly that did not work. The errors are still the same two.

Emral
Cute Yoshi Egg
Cute Yoshi Egg
Posts: 9891
Joined: Mon Jan 20, 2014 12:58 pm
Flair: Phoenix

Re: Custom Powerups Don't Work When Testing World File

Postby Emral » Sat Nov 05, 2022 11:38 am

that... doesn't really make sense. the line numbers should be different, at least, and i didn't get any errors when i tested the new version.
(also when multiple errors are thrown, usually the first one causes the second due to some hiccup in the code execution, so the second is rarely ever necessary to be known because fixing the first fixes the second, too. just so you know)

can you confirm that the version of anotherpowerup running in the crashing level is the one i just posted and not an older one?

Cambroski
Shy Guy
Shy Guy
Posts: 8
Joined: Tue Nov 01, 2022 7:29 pm
Flair: Awake by day, asleep by night.

Re: Custom Powerups Don't Work When Testing World File

Postby Cambroski » Sat Nov 05, 2022 11:44 am

Enjl wrote:
Sat Nov 05, 2022 11:38 am
that... doesn't really make sense. the line numbers should be different, at least, and i didn't get any errors when i tested the new version.
(also when multiple errors are thrown, usually the first one causes the second due to some hiccup in the code execution, so the second is rarely ever necessary to be known because fixing the first fixes the second, too. just so you know)

can you confirm that the version of anotherpowerup running in the crashing level is the one i just posted and not an older one?
Yes I can. The one that causes an error is the latest code for it. Odd that I'm getting an error an you're not.

Emral
Cute Yoshi Egg
Cute Yoshi Egg
Posts: 9891
Joined: Mon Jan 20, 2014 12:58 pm
Flair: Phoenix

Re: Custom Powerups Don't Work When Testing World File

Postby Emral » Sat Nov 05, 2022 11:50 am

AH
i noticed it
https://hastebin.com/pevigizece.lua

this should fix it for real >_>

Cambroski
Shy Guy
Shy Guy
Posts: 8
Joined: Tue Nov 01, 2022 7:29 pm
Flair: Awake by day, asleep by night.

Re: Custom Powerups Don't Work When Testing World File

Postby Cambroski » Sat Nov 05, 2022 11:53 am

Enjl wrote:
Sat Nov 05, 2022 11:50 am
AH
i noticed it
https://hastebin.com/pevigizece.lua

this should fix it for real >_>
That did it! Both levels and worlds can now be played with custom powerups. Thanks for your help, bro.


Return to “LunaLua Help”

Who is online

Users browsing this forum: No registered users and 1 guest

SMWCentralTalkhausMario Fan Games GalaxyKafukaMarioWikiSMBXEquipoEstelari