Jump to content

Recommended Posts

kkXnnu0.png

I have a phonograph that can be activated by inserting a coin. The activation works as intended, but currently it still shows “Turn On” or “Turn Off” when hovering over it. I already implemented a simple mute/unmute logic. Is there a way to remove these default prompts and replace them with a custom context menu for Mute/Unmute instead?

Any help would be greatly appreciated!

 

------------------------------------------------------------------
-- monkeygraph.lua - Coin-activated monkey music device with mute switch
------------------------------------------------------------------
require "prefabutil"

------------------------------------------------------------------
-- Assets & Constants
------------------------------------------------------------------
local assets = {
    Asset("ANIM",  "anim/monkeygraph.zip"),
    Asset("IMAGE", "images/inventoryimages/monkeygraph.tex"),
    Asset("ATLAS", "images/inventoryimages/monkeygraph.xml"),
}

local SPAWN_RADIUS = 15             -- Radius for monkey spawning
local RETURN_RADIUS = 90            -- Radius to attract monkeys from
local PLAYER_CHECK_RADIUS = 30      -- Radius to check for drunk Guybrush
local LOOP_HANDLE = "monkeygraph_loop" -- Sound handle
local BASE_VOLUME = 0.05            -- Default volume when not muted
local COIN_TIME = 30 * 60           -- 30 minutes per coin (seconds)

------------------------------------------------------------------
-- Utility Functions
------------------------------------------------------------------
local function GetRandomSpawnPosition(inst)
    if not TheWorld.ismastersim then return 0,0,0 end
    local x,y,z = inst.Transform:GetWorldPosition()
    local angle = math.random() * 2 * math.pi
    local dist = math.random() * SPAWN_RADIUS
    return x + math.cos(angle) * dist, 0, z + math.sin(angle) * dist
end

local function IsGuybrushDrunk(inst)
    local x,y,z = inst.Transform:GetWorldPosition()
    local players = TheSim:FindEntities(x,y,z, PLAYER_CHECK_RADIUS, {"player"})
    for _,player in ipairs(players) do
        if player.prefab == "guybrush" and 
           player.components.drunkenness and 
           (player.components.drunkenness.current or 0) > 50 then
            return true
        end
    end
    return false
end

------------------------------------------------------------------
-- Core Functionality
------------------------------------------------------------------
-- Sound Management
local function UpdateSound(inst)
    if not inst.is_active then 
        if inst.SoundEmitter then
            inst.SoundEmitter:KillSound(LOOP_HANDLE)
        end
        return 
    end
    
    if inst.SoundEmitter then
        -- Start sound if active
        if not inst.SoundEmitter:PlayingSound(LOOP_HANDLE) then
            inst.SoundEmitter:PlaySound("soundtrack/custom/monkeytracks", LOOP_HANDLE, nil, true)
        end
        
        -- Set volume based on mute state
        local volume = inst.is_muted and 0 or BASE_VOLUME
        inst.SoundEmitter:SetVolume(LOOP_HANDLE, volume)
    end
end

-- Monkey Management
local function ManageMonkeys(inst)
    -- Stop loops if inactive
    if not inst.is_active then
        if inst.attract_task then inst.attract_task:Cancel(); inst.attract_task = nil end
        if inst.spawn_task then inst.spawn_task:Cancel(); inst.spawn_task = nil end
        return
    end
    
    -- Start attraction loop if needed
    if inst.attract_task == nil then
        inst.attract_task = inst:DoPeriodicTask(10, function()
            if not inst.is_active then return end
            
            local x,y,z = inst.Transform:GetWorldPosition()
            local monkeys = TheSim:FindEntities(x,y,z, RETURN_RADIUS, {"powder_monkey"})
            for _,monkey in ipairs(monkeys) do
                if monkey.components.locomotor then
                    monkey.components.locomotor:GoToPoint(Vector3(x,y,z))
                end
            end
        end)
    end
    
    -- Start spawning loop if needed
    if inst.spawn_task == nil then
        inst.spawn_task = inst:DoPeriodicTask(60, function()
            if not inst.is_active or not IsGuybrushDrunk(inst) then return end
            
            local x,y,z = GetRandomSpawnPosition(inst)
            local monkey = SpawnPrefab("powder_monkey")
            if monkey then monkey.Transform:SetPosition(x,y,z) end
        end)
    end
end

-- Timer Management
local function UpdateTimer(inst, added_seconds)
    -- Calculate remaining time if timer is already running
    if inst.activation_timer ~= nil then
        if inst.activation_timer.timeleft ~= nil then
            -- Use timeleft if available
            inst.remaining_time = inst.activation_timer.timeleft
        elseif inst.last_timer_start ~= nil then
            -- Otherwise calculate from elapsed time
            local elapsed = math.max(GetTime() - inst.last_timer_start, 0)
            inst.remaining_time = math.max((inst.remaining_time or 0) - elapsed, 0)
        end
        
        inst.activation_timer:Cancel()
        inst.activation_timer = nil
    end
    
    -- Add new time and start timer
    inst.remaining_time = (inst.remaining_time or 0) + added_seconds
    inst.last_timer_start = GetTime()
    
    inst.activation_timer = inst:DoTaskInTime(inst.remaining_time, function()
        -- Deactivate
        inst.is_active = false
        inst.coin_inserted = false
        inst.remaining_time = 0
        inst.last_timer_start = nil
        inst.activation_timer = nil
        inst:RemoveTag("coin_inserted")
        
        -- Update state
        UpdateSound(inst)
        ManageMonkeys(inst)
        inst.AnimState:PlayAnimation("idle")
        inst.entity:SetCanSleep(true)
    end)
end

-- State Management
local function SetActive(inst, active)
    inst.is_active = active
    
    if active then
        inst.entity:SetCanSleep(false)
        inst.AnimState:PlayAnimation("play_loop", true)
    else
        inst.entity:SetCanSleep(true)
        inst.AnimState:PlayAnimation("idle")
    end
    
    UpdateSound(inst)
    ManageMonkeys(inst)
end

local function SetMuted(inst, muted, doer)
    -- Only allow muting when active
    if not inst.is_active then
        if doer and doer.components.talker then
            doer.components.talker:Say("Needs a guycoin to activate.")
        end
        return false
    end
    
    inst.is_muted = muted
    UpdateSound(inst)
    return true
end

------------------------------------------------------------------
-- Component Functions
------------------------------------------------------------------
-- Machine Component Handlers
local function onUnmute(inst, doer) return SetMuted(inst, false, doer) end
local function onMute(inst, doer) return SetMuted(inst, true, doer) end

-- Trader Component Handler
local function OnInsertCoin(inst, doer, item)
    -- Always check if doer exists and has inventory
    if not doer or not doer.components.inventory or not doer.components.inventory:Has("guycoin", 1) then
        return false
    end

    -- Remove coin from inventory
    doer.components.inventory:ConsumeByName("guycoin", 1)
    
    -- Add message when coin is inserted
    if doer.components.talker then
        if not inst.is_active then
            doer.components.talker:Say("Says here... five minutes of fun. We'll see.")
        else
            doer.components.talker:Say("Another thirty minutes added!")
        end
    end
    
    -- First activation or extension
    if not inst.coin_inserted then
        inst.coin_inserted = true
        inst:AddTag("coin_inserted")
    end
    
    -- Activate if not already active
    if not inst.is_active then
        SetActive(inst, true)
    end
    
    -- Update timer
    UpdateTimer(inst, COIN_TIME)
    return true
end

-- Destruction Handlers
local function onHammered(inst)
    SpawnPrefab("collapse_small").Transform:SetPosition(inst.Transform:GetWorldPosition())
    inst.SoundEmitter:PlaySound("dontstarve/common/destroy_wood")
    
    if inst.activation_timer then inst.activation_timer:Cancel() end
    if inst.attract_task then inst.attract_task:Cancel() end
    if inst.spawn_task then inst.spawn_task:Cancel() end
    
    inst:Remove()
end

local function onWorked(inst, worker, workleft)
    if workleft <= 0 then
        onHammered(inst)
    else
        inst.AnimState:PlayAnimation("shifted")
        inst.AnimState:PushAnimation("idle", false)
    end
end

local function onBurnt(inst)
    inst.AnimState:PlayAnimation("burnt")
    
    if inst.activation_timer then inst.activation_timer:Cancel() end
    if inst.attract_task then inst.attract_task:Cancel() end
    if inst.spawn_task then inst.spawn_task:Cancel() end
    
    if inst.SoundEmitter then
        inst.SoundEmitter:KillSound(LOOP_HANDLE)
    end
    
    inst:DoTaskInTime(3.5, inst.Remove)
end

------------------------------------------------------------------
-- Prefab Definition
------------------------------------------------------------------
local function fn()
    local inst = CreateEntity()

    inst:AddTag("structure")

    inst.entity:AddTransform()
    inst.Transform:SetScale(1.5, 1.5, 1.5)
    inst.entity:AddAnimState()
    inst.entity:AddMiniMapEntity()
    inst.entity:AddSoundEmitter()
    inst.entity:AddNetwork()

    inst.MiniMapEntity:SetIcon("monkeygraph.tex")

    inst.AnimState:SetBank("monkeygraph")
    inst.AnimState:SetBuild("monkeygraph")
    inst.AnimState:PlayAnimation("idle")

    MakeObstaclePhysics(inst, 1.0)
    inst.entity:SetPristine()

    if not TheWorld.ismastersim then
        return inst
    end

    --------------------------------------------------------------
    -- Server-side Components & State
    --------------------------------------------------------------
    -- Initialize state variables
    inst.is_active = false
    inst.is_muted = false
    inst.coin_inserted = false
    inst.remaining_time = 0
    inst.last_timer_start = nil
    inst.activation_timer = nil
    inst.attract_task = nil
    inst.spawn_task = nil

    -- Inspectable component
    inst:AddComponent("inspectable")
    inst.components.inspectable.getspecialdescription = function(inst, viewer)
        if inst.is_active then
            if inst.is_muted then
                return "It's active for 30 minutes per coin. Sound is muted."
            else
                return "It's playing and seems to attract monkeys."
            end
        else
            if inst.coin_inserted then
                return "A coin was inserted. Insert more to extend time."
            else
                return "Insert a guycoin to activate (30 minutes per coin)."
            end
        end
    end

    -- Workable component
    inst:AddComponent("workable")
    inst.components.workable:SetWorkAction(ACTIONS.HAMMER)
    inst.components.workable:SetWorkLeft(6)
    inst.components.workable:SetOnFinishCallback(onHammered)
    inst.components.workable:SetOnWorkCallback(onWorked)

    -- Machine component (for mute functionality)
    inst:AddComponent("machine")
    inst.components.machine.turnonfn = onUnmute   -- "Turn On" = Sound on
    inst.components.machine.turnofffn = onMute    -- "Turn Off" = Sound off
    inst.components.machine.canturnoff = false
    inst.components.machine.caninteract = true
    inst.components.machine.ison = not inst.is_muted

    -- Trader component (for coin insertion)
    inst:AddComponent("trader")
    inst.components.trader:SetAcceptTest(function(inst, item) 
        return item.prefab == "guycoin"
    end)
    inst.components.trader.onaccept = function(inst, giver, item)
        return OnInsertCoin(inst, giver, item)
    end

    -- Additional components
    inst:AddComponent("lootdropper")
    inst:AddComponent("burnable")
    inst.components.burnable:SetOnBurntFn(onBurnt)

    return inst
end

return Prefab("monkeygraph", fn, assets),
       MakePlacer("monkeygraph_placer", "monkeygraph", "monkeygraph", "idle")

 

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
  • Create New...