-- name:\\#964B00\\Goomba Possession -- description:\\#964B00\\Press D-pad down near a Goomba to possess it! Press it again to return to Mario. By \\#00faf6\\k\\#00b3fa\\i\\#0075fa\\k\\#af00fa\\w\\#cc00fa\\e\\#f200fa\\e -- Constants local POSSESSION_RANGE = 200 local MODEL_GOOMBA = _G.MODEL_GOOMBA or 0x67 local POSSESSION_ACTION = 0x40000000 -- Custom action flag -- State local possessed = false local possessedGoomba = nil local id_bhvGoomba = _G.id_bhvGoomba or id_bhvGoomba -- try global, or let it be nil if not found -- Find Goomba local function find_nearest_goomba(m) local nearest = nil local minDist = POSSESSION_RANGE if id_bhvGoomba then local obj = obj_get_first_with_behavior_id(id_bhvGoomba) while obj do local dx = obj.oPosX - m.pos.x local dy = obj.oPosY - m.pos.y local dz = obj.oPosZ - m.pos.z local dist = math.sqrt(dx*dx + dy*dy + dz*dz) if dist < minDist then minDist = dist nearest = obj end obj = obj_get_next_with_same_behavior_id(obj) end else -- fallback: model check (less reliable) for list = 0, 15 do local obj = obj_get_first(list) while obj do if obj.header and obj.header.gfx and obj.header.gfx.sharedChild and obj.header.gfx.sharedChild.model == MODEL_GOOMBA then local dx = obj.oPosX - m.pos.x local dy = obj.oPosY - m.pos.y local dz = obj.oPosZ - m.pos.z local dist = math.sqrt(dx*dx + dy*dy + dz*dz) if dist < minDist then minDist = dist nearest = obj end end obj = obj_get_next(obj) end end end return nearest end local function set_mario_invisible(m) m.marioObj.header.gfx.node.flags = m.marioObj.header.gfx.node.flags & ~GRAPH_RENDER_ACTIVE end local function set_mario_visible(m) m.marioObj.header.gfx.node.flags = m.marioObj.header.gfx.node.flags | GRAPH_RENDER_ACTIVE end local function move_goomba_with_input(goomba, m) -- prevent squish of possessed goomba goomba.oAction = 0 -- Goomba negative stats local speed = 4 -- reduced speed local jumpVel = 18 -- reduced jump height local stickMag = m.controller.stickMag local stickAngle = m.controller.stickY ~= 0 or m.controller.stickX ~= 0 and math.atan2(m.controller.stickY, m.controller.stickX) or 0 if stickMag > 0 then goomba.oMoveAngleYaw = m.intendedYaw goomba.oForwardVel = speed * (stickMag / 32) else goomba.oForwardVel = 0 end -- Jump with A if (m.controller.buttonPressed & A_BUTTON) ~= 0 and goomba.oPosY <= goomba.oHomeY + 5 then goomba.oVelY = jumpVel end end local function is_in_water(m) return m.waterLevel ~= 0 and m.pos.y < m.waterLevel - 50 end local function block_interactions(m) -- interaction abilitys local blockedActs = { [ACT_PULLING_DOOR] = true, [ACT_PUSHING_DOOR] = true, [ACT_READING_NPC_DIALOG] = true, [ACT_READING_AUTOMATIC_DIALOG] = true, [ACT_STAR_DANCE_EXIT] = true, [ACT_STAR_DANCE_NO_EXIT] = true, [ACT_STAR_DANCE_WATER] = true, [ACT_COLLECT_STAR] = true, [ACT_UNLOCKING_KEY_DOOR] = true, [ACT_UNLOCKING_STAR_DOOR] = true, [ACT_WATER_PLUNGE] = true, [ACT_SWIMMING] = true, [ACT_BUTT_SLIDE] = true, [ACT_HOLD_BUTT_SLIDE] = true, [ACT_HOLD_WATER_JUMP] = true, [ACT_WATER_JUMP] = true, [ACT_HOLD_WATER_FALL] = true, [ACT_WATER_FALL] = true, [ACT_HOLD_IDLE] = true, [ACT_HOLD_WALKING] = true, [ACT_HOLD_HEAVY_WALKING] = true, [ACT_HOLD_FREEFALL] = true, [ACT_HOLD_JUMP] = true, [ACT_HOLD_DECELERATING] = true, [ACT_HOLDING_BOWSER] = true, [ACT_THROWING] = true, [ACT_DIVE] = true, [ACT_SHOT_FROM_CANNON] = true, [ACT_GRABBED] = true, [ACT_IN_CANNON] = true, [ACT_TORNADO_TWIRLING] = true, [ACT_PUNCHING] = true, [ACT_PICKING_UP_BOWSER] = true, [ACT_READING_SIGN] = true, [ACT_DOOR_PULL] = true, [ACT_DOOR_PUSH] = true, [ACT_ENTERING_STAR_DOOR] = true, [ACT_UNLOCKING_KEY_DOOR] = true, [ACT_UNLOCKING_STAR_DOOR] = true, [ACT_EXIT_LAND_SAVE_DIALOG] = true, [ACT_READING_AUTOMATIC_DIALOG] = true, [ACT_READING_NPC_DIALOG] = true, [ACT_WAITING_FOR_DIALOG] = true, } if blockedActs[m.action] then set_mario_action(m, ACT_IDLE, 0) end end local distractedGoombas = {} local function distract_other_goombas(exceptGoomba) distractedGoombas = {} if id_bhvGoomba then local obj = obj_get_first_with_behavior_id(id_bhvGoomba) while obj do if obj ~= exceptGoomba then distractedGoombas[obj] = true obj.oAction = 0 obj.oForwardVel = 0 end obj = obj_get_next_with_same_behavior_id(obj) end end end local function restore_goombas() for obj, _ in pairs(distractedGoombas) do if obj then obj.oAction = 0 obj.oForwardVel = 0 end end distractedGoombas = {} end -- Main possession logic local function mario_update(m) if m.playerIndex ~= 0 then return end -- Only local player local controller = m.controller if not possessed then restore_goombas() lastHealth = m.health lastHurtCounter = m.hurtCounter if (controller.buttonPressed & D_JPAD) ~= 0 then local goomba = find_nearest_goomba(m) if goomba then possessed = true possessedGoomba = goomba set_mario_invisible(m) -- Move Mario to Goomba's position m.pos.x, m.pos.y, m.pos.z = goomba.oPosX, goomba.oPosY, goomba.oPosZ djui_popup_create("You are now controlling a Goomba! Press D-Pad Down to return.", 2) else djui_popup_create("No Goomba found nearby!", 2) end end else -- Unpossess if (controller.buttonPressed & D_JPAD) ~= 0 then if possessedGoomba then m.pos.x, m.pos.y, m.pos.z = possessedGoomba.oPosX, possessedGoomba.oPosY, possessedGoomba.oPosZ end set_mario_visible(m) possessed = false possessedGoomba = nil restore_goombas() djui_popup_create("Returned to Mario!", 2) return end if possessedGoomba then m.pos.x, m.pos.y, m.pos.z = possessedGoomba.oPosX, possessedGoomba.oPosY, possessedGoomba.oPosZ move_goomba_with_input(possessedGoomba, m) distract_other_goombas(possessedGoomba) end if is_in_water(m) then set_mario_visible(m) possessed = false possessedGoomba = nil restore_goombas() djui_popup_create("Goombas can't swim! You drowned and returned to Mario.", 2) return end if lastHealth and m.health < lastHealth or (lastHurtCounter and m.hurtCounter > lastHurtCounter) then set_mario_visible(m) possessed = false possessedGoomba = nil restore_goombas() djui_popup_create("Goomba defeated! You returned to Mario.", 2) return end lastHealth = m.health lastHurtCounter = m.hurtCounter block_interactions(m) end end hook_event(HOOK_MARIO_UPDATE, mario_update)