Jump to content

[HELP]Why I can't add a widget button for a new container?


Recommended Posts

I made a new container and want to add a widget for it, but it didn't work. The scripts of the function fn() are the following:

 

 

 

require "prefabutil"
 
local assets =
{
Asset("ANIM", "anim/shop.zip"),
Asset("IMAGE", "images/inventoryimages/shop.tex"),
Asset("ATLAS", "images/inventoryimages/shop.xml"),
}
 
local prefabs = 
{
"shop",
}
 
local function onopen(inst) 
    inst.SoundEmitter:PlaySound("dontstarve/wilson/chest_open")
end 
 
local function onclose(inst) 
    inst.SoundEmitter:PlaySound("dontstarve/wilson/chest_close")        
end 
 
local function onhammered(inst, worker)
    if not inst:HasTag("fire") and inst.components.burnable then 
        inst.components.burnable:Extinguish()
    end
    inst.components.lootdropper:DropLoot()
    if inst.components.container then 
        inst.components.container:DropEverything()
    end
    SpawnPrefab("collapse_small").Transform:SetPosition(inst.Transform:GetWorldPosition())
    inst.SoundEmitter:PlaySound("dontstarve/common/destroy_wood")   
    inst:Remove()
end
 
local function onhit(inst, worker)
    if not inst:HasTag("burnt") then 
        inst.AnimState:PlayAnimation("hit")
        inst.AnimState:PushAnimation("closed", false)
        if inst.components.container then 
            inst.components.container:DropEverything()
            inst.components.container:Close()
        end
    end
end
 
local function onbuilt(inst)
    inst.AnimState:PlayAnimation("place")
    inst.AnimState:PushAnimation("idle")
end
 
local function onsave(inst, data)
end
 
local function onload(inst, data)
end
 
local function fn()
    local inst = CreateEntity()
 
    inst.entity:AddTransform()
    inst.entity:AddAnimState()
    inst.entity:AddSoundEmitter()
    MakeObstaclePhysics(inst, 1)
 
    inst.AnimState:SetBank("shop")
    inst.AnimState:SetBuild("shop")
    inst.AnimState:PlayAnimation("idle", true)
 
    local minimap = inst.entity:AddMiniMapEntity()
    minimap:SetIcon( "shop.png" )
 
    inst:AddTag("structure")
 
    inst:AddComponent("lootdropper")
    inst:AddComponent("workable")
    inst.components.workable:SetWorkAction(ACTIONS.HAMMER)
    inst.components.workable:SetWorkLeft(4)
    inst.components.workable:SetOnFinishCallback(onhammered)
    inst.components.workable:SetOnWorkCallback(onhit)
 
    inst:AddComponent("inspectable")
    inst:AddComponent("container")
    inst.components.container:WidgetSetup("treasurechest")
    inst.components.container.onopenfn = onopen
    inst.components.container.onclosefn = onclose
 
    local widgetbuttoninfo = {
    text = "DO",
    position = Vector3(0, -195, 0),
    fn = function(inst)
        --body
    end }
    inst.components.container.widgetbuttoninfo = widgetbuttoninfo
 
    inst:ListenForEvent("onbuilt", onbuilt)
    MakeSnowCovered(inst)
 
    inst.OnSave = onsave 
    inst.OnLoad = onload
 
    return inst
end
 
return Prefab("common/objects/shop", fn, assets, prefabs),
MakePlacer("common/shop_placer", "shop", "shop", "idle")

shop.lua

Link to comment
Share on other sites

@Jupiters, that's for DS.

This is DST.

 

First, setup the function of the button, as an action, in modmain.lua:

local function TradeFn(act)	if act.doer and act.doer.components.inventory then		local test = GLOBAL.SpawnPrefab("goldnugget")		act.doer.components.inventory:GiveItem(test)		return true	endendAddAction("TRADE", "Trade", TradeFn)

This is an example.

 

This is the new shop.lua:

require "prefabutil"local assets = {	Asset("ANIM", "anim/shop.zip"),	Asset("IMAGE", "images/inventoryimages/shop.tex"),	Asset("ATLAS", "images/inventoryimages/shop.xml"),}local function onopen(inst) 	inst.SoundEmitter:PlaySound("dontstarve/wilson/chest_open")end local function onclose(inst) 	inst.SoundEmitter:PlaySound("dontstarve/wilson/chest_close")        end local function onhammered(inst, worker)	if not inst:HasTag("fire") and inst.components.burnable then 		inst.components.burnable:Extinguish()	end	inst.components.lootdropper:DropLoot()	if inst.components.container then 		inst.components.container:DropEverything()	end	SpawnPrefab("collapse_small").Transform:SetPosition(inst.Transform:GetWorldPosition())	inst.SoundEmitter:PlaySound("dontstarve/common/destroy_wood")   	inst:Remove()endlocal function onhit(inst, worker)	if not inst:HasTag("burnt") then 		inst.AnimState:PlayAnimation("hit")		inst.AnimState:PushAnimation("closed", false)		if inst.components.container then 			inst.components.container:DropEverything()			inst.components.container:Close()		end	endendlocal function onbuilt(inst)	inst.AnimState:PlayAnimation("place")	inst.AnimState:PushAnimation("idle")endlocal function onsave(inst, data)endlocal function onload(inst, data)endlocal function EditContainer(inst)	local self	if TheWorld.ismastersim then		self = inst.components.container	else		self = inst.replica.container	end	self:WidgetSetup("treasurechest")	local function buttonfn(inst)		if TheWorld.ismastersim then			BufferedAction(inst.components.container.opener, inst, ACTIONS.TRADE):Do()		else			SendRPCToServer(RPC.DoWidgetButtonAction, ACTIONS.TRADE.code, inst, ACTIONS.TRADE.mod_name)		end	end	self.widget.buttoninfo = {		text = "Trade",		position = Vector3(0, -195, 0),		fn = buttonfn,	}endlocal function fn()	local inst = CreateEntity()	inst.entity:AddTransform()	inst.entity:AddAnimState()	inst.entity:AddSoundEmitter()	inst.entity:AddMiniMapEntity()	inst.entity:AddNetwork()	MakeObstaclePhysics(inst, 1)	inst.AnimState:SetBank("shop")	inst.AnimState:SetBuild("shop")	inst.AnimState:PlayAnimation("idle", true)	inst.MiniMapEntity:SetIcon("shop.png")	inst:AddTag("structure")	inst.entity:SetPristine()	if not TheWorld.ismastersim then		local _OnEntityReplicated = inst.OnEntityReplicated		inst.OnEntityReplicated = function(inst)			if _OnEntityReplicated then				_OnEntityReplicated(inst)			end			EditContainer(inst)		end		return inst	end	inst:AddComponent("container")	inst.components.container.onopenfn = onopen	inst.components.container.onclosefn = onclose	EditContainer(inst)	inst:AddComponent("inspectable")	inst:AddComponent("lootdropper")	inst:AddComponent("workable")	inst.components.workable:SetWorkAction(ACTIONS.HAMMER)	inst.components.workable:SetWorkLeft(4)	inst.components.workable:SetOnFinishCallback(onhammered)	inst.components.workable:SetOnWorkCallback(onhit)	inst:ListenForEvent("onbuilt", onbuilt)	MakeSnowCovered(inst)	inst.OnSave = onsave 	inst.OnLoad = onload	return instendreturn Prefab("common/objects/shop", fn, assets), MakePlacer("common/shop_placer", "shop", "shop", "idle")

Notice:

1) inst.entity:AddNetwork(), essential for the prefab to exist for hosts and clients.

2) if not TheWorld.ismastersim then, separating host code (master simulation) from client code.

 

3) EditContainer(inst)

This function is seen twice.

One, to setup the widget for the host, after the container component is added.

Two, to setup the widget for the client, after the container replica is added client side.

 

A replica is a component stub that the client interacts with.

 

This difference makes the function have two different functions for the button, one that sends an RPC for the server to do an action, and one that does the action directly. Both cause the same thing in the server, the execution of the trade action we added in modmain.

Link to comment
Share on other sites

@DarkXero : It works. How amazing! I can't thank you enough.

May I ask you one more question?

I want the "shop" mod to be a bank, too. That means I want it can store gold nuggets. Number of the gold nuggets is one value( x_gold ).

How do I make the value x_gold still the same when I start the game next time?

Link to comment
Share on other sites

How do I make the value x_gold still the same when I start the game next time?

 

That's the use of

local function onsave(inst, data)	data.x_gold = inst.x_goldend local function onload(inst, data)	if data then		inst.x_gold = data.x_gold or 0	endend

And you initialize inst.x_gold = 0 in the prefab fn.

 

Oh, by the way, how do add other buttons? Use "self.widget.buttoninfo", "self.widget.buttoninfo2" and so on?

 

Unfortunately, no.

The widget is hard coded to use only one button.

This means that we have to override the widget and copy paste a lot of code that handles that one button, for all other buttons.

 

I propose a simpler alternative, easier to understand, instead of messing with widgets.

 

When your shop prefab spawns, it spawns two mini shop prefabs that depend on it.

(Those mini shops can be scaled down versions of the shop.)

The main shop won't be a container, it will just be examined.

These mini shops can be interacted with, but can't be destroyed, they will only be destroyed if the main shop is destroyed.

 

One mini shop will act like the trader, with the trader button.

The other mini shop, like the bank, with the store button.

Link to comment
Share on other sites

@DarkXero : OMG. I've asked you so many question. What about another one?

Here is the code in modmain.lua for the button:

 

local function TradeFn(act)

    if act.doer and act.doer.components.inventory then
        local give_bluegem = GLOBAL.SpawnPrefab("bluegem")
        local inst = GLOBAL.SpawnPrefab("statuemaxwell")
        local num_found = 0
        for k,v in pairs(inst.components.container.slots) do
            if v and v.prefab == "bluegem" then
               num_found = num_found + v.components.stackable:StackSize()
            end
        end
        if num_found == 3 then
            if act.doer.components.inventory:ConsumeByName("goldnugget", 5) then
                act.doer.components.inventory:ConsumeByName("goldnugget", 5)
           act.doer.components.inventory:GiveItem(give_bluegem)
            end
        end
        return true
    end
end
AddAction("TRADE", "Trade", TradeFn)
 
I want it possible that when I have 5 goldnuggets and there are 3 red gems in the container and if I click the button, it will give me a redgem and take away 5 goldnugget. Unfortunately, it did not work. But if I replace " if num_found == 3 then " with " if num_found == 0 then ", it did give a redgem. That means the value num_found is still the same. What's wrong with my code?

 

Link to comment
Share on other sites

How to trigger an event when the day comes, the night comes, or in a period of time that is set
local function TriggerEvent(inst, phase)    if phase == "day" then        -- Event a    elseif phase == "dusk" then        -- Event b    elseif phase == "night" then        -- Event c    endend inst:WatchWorldState("phase", TriggerEvent)TriggerEvent(inst, TheWorld.state.phase)
I want it possible that when I have 5 goldnuggets and there are 3 red gems in the container and if I click the button, it will give me a redgem and take away 5 goldnugget. Unfortunately, it did not work. But if I replace " if num_found == 3 then " with " if num_found == 0 then ", it did give a redgem. That means the value num_found is still the same. What's wrong with my code?

 

Try saying that again.

 

There is not a single mention of a red gem in your code.

There is also no reference to the shop.

I understand none of this.

 

Maybe you meant this?

local function TradeFn(act)	if act.doer and act.target then		local inv_doer = act.doer.components.inventory		local con_target = act.target.components.container		local is_near = act.doer:IsNear(act.target, 20)		if inv_doer and con_target and is_near then			local fivegold = con_target:Has("goldnugget", 5)			if fivegold then				con_target:ConsumeByName("goldnugget", 5)				local redgem = GLOBAL.SpawnPrefab("redgem")				inv_doer:GiveItem(redgem)			end			return true		end		endendAddAction("TRADE", "Trade", TradeFn)
Link to comment
Share on other sites

@DarkXero : Here comes a problem. 

I add a widget and it can change values when I press. (It is like the widget in the mod : "Speed Me Up DST".)

When I click the button of the container(I have made it with your help), it will give me different things according to the value.

Everything seems normal.

But the problem is the client can't change the value. Only the host can change the value.

I want the value seperate for every single player.

What can I do?

I have uploaded the modmain.lua so that you can check it. :love_heart: 

modmain.lua

Edited by Jupiters
Link to comment
Share on other sites

Hello, @DarkXero

I have been working for this mod for a long time. But, I cannot find a way to add multiple buttons for the container. Is there any way to push a screen with a few other buttons, which shows when I click the button "Trade". And the buttons shown have different action after they are click. Will you show me how to do this. I will attach the mod file. Thanks.

Magical Chest.zip

Edited by Jupiters
Link to comment
Share on other sites

Hello, @DarkXero

The mod is nearly done but with a problem.

When I put many things that are not tradable in the chest and then click sell, it crashed.

 

 

Here is the log:

 

[00:01:50]: [string "../mods/Magical Chest/modmain.lua"]:127: attempt to index field 'tradable' (a nil value)
LUA ERROR stack traceback:
    ../mods/kk/modmain.lua:127 in (field) fn (Lua) <120-142>
    scripts/bufferedaction.lua:23 in (method) Do (Lua) <19-33>
    scripts/networkclientrpc.lua:155 in () ? (Lua) <143-159>
    =[C]:-1 in (method) CallRPC © <-1--1>
    scripts/networkclientrpc.lua:480 in (global) HandleRPCQueue (Lua) <471-488>
    scripts/update.lua:47 in () ? (Lua) <39-123>
 
 
The part of the code of modmain.lua is:
 
local function SellFn( act )
if act.doer and act.doer.components.inventory and act.target then
local inv_doer = act.doer.components.inventory
local con_target = act.target.components.container
local is_near = act.doer:IsNear(act.target, 20)
local num_found = 0
        for k,v in pairs(act.target.components.container.slots) do
            if v and v.components.tradable.goldvalue > 0 then
                num_found = num_found + v.components.stackable:StackSize() * v.components.tradable.goldvalue
            end
        end
if is_near and  num_found >0 then
for k = 1, num_found do inv_doer:GiveItem(GLOBAL.SpawnPrefab("goldnugget")) end
--con_target:DestroyContents()
for k,v in pairs(act.target.components.container.slots) do
if v and v.components.tradable.goldvalue > 0 then
con_target:ConsumeByName(v.prefab, v.components.stackable:StackSize())
end
end
return true
end
end
end
AddAction("SELL", "Sell", SellFn)

 

The line in red is line 127. I cannot figure out what was going wrong.

I want it to behave like pigking. But what I need is put things that can trade for gold with pigking in the chest and click Sell.

Link to comment
Share on other sites

I change the code above into this:

 

 

local function SellFn( act )
if act.doer and act.doer.components.inventory and act.target then
local inv_doer = act.doer.components.inventory
local con_target = act.target.components.container
local is_near = act.doer:IsNear(act.target, 20)
local num_found = 0
        for k,v in pairs(act.target.components.container.slots) do
            if v and v.components.tradable then
            if v.components.tradable.goldvalue >0 then
                num_found = num_found + v.components.stackable:StackSize() * v.components.tradable.goldvalue
                end
            end
        end
if is_near and  num_found >0 then
for k = 1, num_found do inv_doer:GiveItem(GLOBAL.SpawnPrefab("goldnugget")) end
--con_target:DestroyContents()
for k,v in pairs(act.target.components.container.slots) do
if v and v.components.tradable then
if v.components.tradable.goldvalue > 0 then
con_target:ConsumeByName(v.prefab, v.components.stackable:StackSize())
end
end
end
return true
end
end
end
AddAction("SELL", "Sell", SellFn)
 
 
And it seems that it works now. Did I do this right?
Link to comment
Share on other sites

Sorry for disturbing you, I think I've figured out where the problen lies.

 

In modmain.lua, there is  the code:

 

local function MineFn( act )
if act.doer and act.doer.components.inventory and act.target then
local inv_doer = act.doer.components.inventory
local is_near = act.doer:IsNear(act.target, 20)
if inv_doer:Has("goldnugget",1) and is_near then
if GLOBAL.TheWorld.state.isday then
inv_doer:ConsumeByName("goldnugget", 1)
for i = 1, 5 do inv_doer:GiveItem(GLOBAL.SpawnPrefab("marble")) end
else
inv_doer:ConsumeByName("goldnugget", 1)
for i = 1, 5 do inv_doer:GiveItem(GLOBAL.SpawnPrefab("nitre")) end
end
return true
end
end
end
AddAction("MINE", "Mine", MineFn)
 
I think it overrides the function of mine action, so I cannot mine. Now I change "mine" into "mines" and it seems okay now. Am I right?
Edited by Jupiters
Link to comment
Share on other sites

I think it overrides the function of mine action, so I cannot mine. Now I change "mine" into "mines" and it seems okay now. Am I right?

 

Correct. AddAction(id, str, fn) takes three arguments.

 

id must be unique, it identifies the action.

str can be "Mine" or "MINE" or whatever, doesn't matter that it's the same.

 

But the id, again, must be unique.

Name it "MAGICAL_MINE" or "MC_MINE" so there is no conflict.

Link to comment
Share on other sites

Correct. AddAction(id, str, fn) takes three arguments.

 

id must be unique, it identifies the action.

str can be "Mine" or "MINE" or whatever, doesn't matter that it's the same.

 

But the id, again, must be unique.

Name it "MAGICAL_MINE" or "MC_MINE" so there is no conflict.

 

Thanks, I get it. :)

Link to comment
Share on other sites

Hello @DarkXero

 

Your another post gave an inspiration. ( http://forums.kleientertainment.com/topic/57411-help-clickable-buttons/ )

Since the content of this mod is too complicated(trade for many things) and client don't know how the mod functions, I want add button for the container that will display the information after clicking and hide it after another clicking. I know how to add a button for a container now but I don't how to display the information.

 

I think this is one way to show clients how the mod functions. How about another way, when the mouse move upon the button ( Let's assume the button's funtion is buy 10 flint for 1 gold nugget. ), a line of words ( the content is " 10 flint for 1 gold nugget ")  will show at the top of the mouse. Does this way work?

 

I am not sure that these two way which is better because I don't know how to do them. Could you please help me out?

 

Thank you!

Link to comment
Share on other sites

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
 Share

×
  • Create New...