Jump to content

[help] how to make code runs on server instead of client ?


Recommended Posts

I am working on a mod called event clock, basically, it's a gui version of "advanced monster warning", so players could check monster timer with a hotkey. But the code I wrote seems to have some bugs in it, I had debuged the mod for the whole day, still no clue how to fix it. Really hope someone so kind could give me some hint, thanks a lot !!!!

20210614023537_1.thumb.png.fc35fa0a9018722917b004d3629eb6db.png

I post my code here, you can also download the whole project in the attachment. And the last part is some logs the mod outputs and my analysis.

Here is what I have found. The method `EventClockScreen:Open()` should run on the server, but it runs on the client side instead, which makes `TheWorld.components.hounded` a nil value, so I can't get attack time of hound, as shown in the screenshot above.

Any kind of help is appreciated !!!

```modmain.lua

GLOBAL.setmetatable(env,{__index=function(t,k) return GLOBAL.rawget(GLOBAL,k) end})

ENABLE_GUI_TIMER = GetModConfigData("enable_gui_timer")
ENABLE_INCOMING_WARNING = GetModConfigData("enable_incoming_warning")
LANGUAGE = GetModConfigData("language")

Assets =
{
    Asset("ATLAS", "images/hound.xml"),
    Asset("ATLAS", "images/worm.xml"),
    Asset("ATLAS", "images/bearger.xml"),
    Asset("ATLAS", "images/deerclops.xml"),
    Asset("ATLAS", "images/sinkhole.xml"),
}

EventClockScreen = GLOBAL.require("screens/eventclock")

print("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", TheWorld)
AddPrefabPostInit("world", function(inst)
    print("$$$$$$$$$$$$$$$$$$$$$$$$$$$", TheWorld, TheWorld.ismastersim, TheWorld.components, TheWorld.components.hounded)
    function displayEventClockScreen()
        if EventClockScreen:Open() then
            TheFrontEnd:PushScreen(EventClockScreen)
        end
    end
    
    function closeEventClockScreen()
        EventClockScreen:Close()
    end
    if ENABLE_GUI_TIMER then
        --open gui timer window with hotkey `C`
        GLOBAL.TheInput:AddKeyDownHandler(KEY_C, displayEventClockScreen)
        GLOBAL.TheInput:AddKeyUpHandler(KEY_C, closeEventClockScreen)
    end
end)

```

 

```scripts/screens/eventclock.lua

local Screen = require "widgets/screen"
local Widget = require "widgets/widget"
local Image = require "widgets/image"
local Text = require "widgets/text"
local TEMPLATES = require "widgets/redux/templates"

TIMER_UPDATE_SEG = 4

LANGUAGE = "english"
local function localization()
    if LANGUAGE == "english" then 
        return {
            DAYS = " days",
            HOUND = "hound attacks in ",
            WORM = "worm attacks in ",
            BEARGER = "bearger attacks in ",
            DEERCLOPS = "deerclops attacks in ",
            SINKHOLE = "sinkhole falls in "
        }
    end
end

WARN_MESSAGE = localization()

local function second_to_day(sec)
    if sec then
        local time_in_days = string.format("%.2f", sec / TUNING.TOTAL_DAY_TIME)
        return time_in_days..WARN_MESSAGE.DAYS
    end
    return "- - -"
end

EventClockScreen = Class(Screen, function(self)
    if self.updateTask ~= nil then
        print("######################update_timer", self.UpdateTimer)
        --update monster timer periodically
        self.updateTask = ThePlayer:DoPeriodicTask(TIMER_UPDATE_FREQUENCY, self.UpdateTimer)
    end
end)

function EventClockScreen:HoundTimeLeft()
    local time_left
    print("########## in HoundTimeLeft", TheWorld, TheWorld.ismastersim, TheWorld.components.hounded)
    if TheWorld.ismastersim and TheWorld.components.hounded then
        time_left =  TheWorld.components.hounded:GetTimeToAttack()
    end
    print("######################HoundTimeLeft", time_left)
    return time_left
end

local DEERCLOPS_TIMERNAME = "deerclops_timetoattack"
function EventClockScreen:DeerclopsAttackTimeLeft()
    local time_left
    print("########## in DeerclopsAttackTimeLeft", TheWorld, TheWorld.ismastersim, TheWorld.components.deerclopsspawner, TUNING.DEERCLOPS_ATTACKS_OFF_SEASON or TheWorld.state.iswinter)
    if TheWorld.ismastersim and TheWorld.components.deerclopsspawner and (TUNING.DEERCLOPS_ATTACKS_OFF_SEASON or TheWorld.state.iswinter) then
        time_left = TheWorld.components.worldsettingstimer:GetTimeLeft(DEERCLOPS_TIMERNAME)
    end
    print("######################DeerclopsAttackTimeLeft", time_left)
    return time_left
end

local BEARGER_TIMERNAME = "bearger_timetospawn"
function EventClockScreen:BeargerSpawnTimeLeft()
    local time_left
    print("########## in BeargerSpawnTimeLeft", TheWorld, TheWorld.ismastersim, TheWorld.components.beargerspawner, TheWorld.state.isautumn)
    if TheWorld.ismastersim and TheWorld.components.beargerspawner and TheWorld.state.isautumn then
        time_left = TheWorld.components.worldsettingstimer:GetTimeLeft(BEARGER_TIMERNAME)
    end
    print("######################BeargerSpawnTimeLeft", time_left)
    return time_left
end

local ANTLION_RAGE_TIMER = "rage"
function EventClockScreen:SinkHoleTimeLeft()
    local time_left
    print("########## in SinkHoleTimeLeft", TheWorld, TheWorld.ismastersim, TheWorld.components.sinkholespawner, TheWorld.state.issummer)
    if TheWorld.ismastersim and TheWorld.components.sinkholespawner and TheWorld.state.issummer then
        time_left = TheWorld.components.worldsettingstimer:GetTimeLeft(ANTLION_RAGE_TIMER)
    end
    print("######################SinkHoleTimeLeft", time_left)
    return time_left
end

function EventClockScreen:Open()
    if self.active then
        return
    end

    Screen._ctor(self, "EventClockScreen")

    self.active = true
    
    --darken everything behind the dialog
    if self.black then
        self.black:Kill()
    end
    self.black = self:AddChild(Image("images/global.xml", "square.tex"))
    self.black:SetVRegPoint(ANCHOR_MIDDLE)
    self.black:SetHRegPoint(ANCHOR_MIDDLE)
    self.black:SetVAnchor(ANCHOR_MIDDLE)
    self.black:SetHAnchor(ANCHOR_MIDDLE)
    self.black:SetScaleMode(SCALEMODE_FILLSCREEN)
    self.black:SetTint(0,0,0,0)

    if self.proot then
        self.proot:Kill()
    end
    self.proot = self:AddChild(Widget("ROOT"))
    self.proot:SetVAnchor(ANCHOR_MIDDLE)
    self.proot:SetHAnchor(ANCHOR_MIDDLE)
    self.proot:SetPosition(0,0,0)
    self.proot:SetScaleMode(SCALEMODE_PROPORTIONAL)
    
    --throw up the background
    if self.bg then
        self.bg:Kill()
    end
    self.bg = self.proot:AddChild(TEMPLATES.RectangleWindow(200, 360, "Event Clock"))
    
    --add boss icon
    if TheWorld:HasTag("cave") then
        --worm
        self.worm_icon = self.proot:AddChild(Image("images/worm.xml", "worm.tex"))
        self.worm_icon:SetScale(0.15)
        self.worm_icon:SetPosition(-40, 70)

        self.worm_timer = self.proot:AddChild(Text(NEWFONT_SMALL, 16))
        self.worm_timer:SetPosition(40, 70)
        self.worm_timer:SetString(second_to_day(self:HoundTimeLeft()))
    else
        --hound
        self.hound_icon = self.proot:AddChild(Image("images/hound.xml", "hound.tex"))
        self.hound_icon:SetScale(0.15)
        self.hound_icon:SetPosition(-40, 70)

        self.hound_timer = self.proot:AddChild(Text(NEWFONT_SMALL, 16))
        self.hound_timer:SetPosition(40, 70)
        self.hound_timer:SetString(second_to_day(self:HoundTimeLeft()))
    end
    
    --bearger
    self.bearger_icon = self.proot:AddChild(Image("images/bearger.xml", "bearger.tex"))
    self.bearger_icon:SetScale(0.15)
    self.bearger_icon:SetPosition(-40, 10)

    self.bearger_timer = self.proot:AddChild(Text(NEWFONT_SMALL, 16))
    self.bearger_timer:SetPosition(40, 10)
    self.bearger_timer:SetString(second_to_day(self:BeargerSpawnTimeLeft()))
    
    --deerclops
    self.deerclops_icon = self.proot:AddChild(Image("images/deerclops.xml", "deerclops.tex"))
    self.deerclops_icon:SetScale(0.15)
    self.deerclops_icon:SetPosition(-40, -50)

    self.deerclops_timer = self.proot:AddChild(Text(NEWFONT_SMALL, 16))
    self.deerclops_timer:SetPosition(40, -50)
    self.deerclops_timer:SetString(second_to_day(self:DeerclopsAttackTimeLeft()))
    
    --sinkhole
    self.sinkhole_icon = self.proot:AddChild(Image("images/sinkhole.xml", "sinkhole.tex"))
    self.sinkhole_icon:SetScale(0.15)
    self.sinkhole_icon:SetPosition(-40, -110)

    self.sinkhole_timer = self.proot:AddChild(Text(NEWFONT_SMALL, 16))
    self.sinkhole_timer:SetPosition(40, -110)
    self.sinkhole_timer:SetString(second_to_day(self:SinkHoleTimeLeft()))
    
    return self.active
end

function EventClockScreen:Close()
    if self.active then
        self.active = false
        TheFrontEnd:PopScreen()
    end
end

function EventClockScreen:AnnounceIncoming(time_left, action) --announce monster attacks one day ahead
    if ENABLE_INCOMING_WARNING and time_left and time_left >= TUNING.TOTAL_DAY_TIME and time_left < TUNING.TOTAL_DAY_TIME + TIMER_UPDATE_FREQUENCY then
        local message = action..second_to_day(time_left)..WARN_MESSAGE.DAYS
        TheNet:Say(STRINGS.LMB .. " " .. message, TheInput:IsKeyDown(KEY_CTRL))
    end
end

function EventClockScreen:UpdateTimer() --update monster timer
    local hound_time_left = self:HoundTimeLeft()
    local bearger_spawn_time_left = self:BeargerSpawnTimeLeft()
    local deerclops_attack_time_left = self:DeerclopsAttackTimeLeft()
    local sinkhole_time_left = self:SinkHoleTimeLeft()
    
    if TheWorld:HasTag("cave") then
        self:AnnounceIncoming(hound_time_left, WARN_MESSAGE.WORM)
    else
        self:AnnounceIncoming(hound_time_left, WARN_MESSAGE.HOUND)
    end
    self:AnnounceIncoming(bearger_spawn_time_left, WARN_MESSAGE.BEARGER)
    self:AnnounceIncoming(deerclops_attack_time_left, WARN_MESSAGE.DEERCLOPS)
    self:AnnounceIncoming(sinkhole_time_left, WARN_MESSAGE.SINKHOLE)
    
    if self.active then
        if TheWorld:HasTag("cave") then
            self.worm_timer:SetString(second_to_day(hound_time_left))
        else
            self.hound_timer:SetString(second_to_day(hound_time_left))
        end

        self.bearger_timer:SetString(second_to_day(bearger_spawn_time_left))
        self.deerclops_timer:SetString(second_to_day(deerclops_attack_time_left))
        self.sinkhole_timer:SetString(second_to_day(sinkhole_time_left))
    end
end

print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
print(TheWorld)
print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")

return EventClockScreen

```

 

I added some debug log, on the server, I got logs like this, which means `TheWorld.components.hounded` does exist on server :

```

[00:00:08]: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@    
[00:00:08]: nil    
[00:00:08]: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@    
[00:00:08]: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%    nil   

[00:00:15]: $$$$$$$$$$$$$$$$$$$$$$$$$$$    100026 - world    true    table: 0EB43518    table: 1376FCF0    

```

 

but on client, I got these logs, meaning `displayEventClockScreen` method runs on client side, that's why it's not working :

```

[00:01:11]: ########## in HoundTimeLeft    100028 - world    false    nil    
[00:01:11]: ######################HoundTimeLeft    nil    
[00:01:11]: ########## in BeargerSpawnTimeLeft    100028 - world    false    nil    true    
[00:01:11]: ######################BeargerSpawnTimeLeft    nil    
[00:01:11]: ########## in DeerclopsAttackTimeLeft    100028 - world    false    nil    false    
[00:01:11]: ######################DeerclopsAttackTimeLeft    nil    
[00:01:11]: ########## in SinkHoleTimeLeft    100028 - world    false    nil    false    
[00:01:11]: ######################SinkHoleTimeLeft    nil    
[00:01:16]: ########## in HoundTimeLeft    100028 - world    false    nil    
[00:01:16]: ######################HoundTimeLeft    nil    
[00:01:16]: ########## in BeargerSpawnTimeLeft    100028 - world    false    nil    true    
[00:01:16]: ######################BeargerSpawnTimeLeft    nil    
[00:01:16]: ########## in DeerclopsAttackTimeLeft    100028 - world    false    nil    false    
[00:01:16]: ######################DeerclopsAttackTimeLeft    nil    
[00:01:16]: ########## in SinkHoleTimeLeft    100028 - world    false    nil    false    
[00:01:16]: ######################SinkHoleTimeLeft    nil    
[00:01:25]: ########## in HoundTimeLeft    100028 - world    false    nil    
[00:01:25]: ######################HoundTimeLeft    nil    
[00:01:25]: ########## in BeargerSpawnTimeLeft    100028 - world    false    nil    true    
[00:01:25]: ######################BeargerSpawnTimeLeft    nil    
[00:01:25]: ########## in DeerclopsAttackTimeLeft    100028 - world    false    nil    false    
[00:01:25]: ######################DeerclopsAttackTimeLeft    nil    
[00:01:25]: ########## in SinkHoleTimeLeft    100028 - world    false    nil    false    
[00:01:25]: ######################SinkHoleTimeLeft    nil    
[00:01:27]: ########## in HoundTimeLeft    100028 - world    false    nil    
[00:01:27]: ######################HoundTimeLeft    nil    
[00:01:27]: ########## in BeargerSpawnTimeLeft    100028 - world    false    nil    true    
[00:01:27]: ######################BeargerSpawnTimeLeft    nil    
[00:01:27]: ########## in DeerclopsAttackTimeLeft    100028 - world    false    nil    false    
[00:01:27]: ######################DeerclopsAttackTimeLeft    nil    
[00:01:27]: ########## in SinkHoleTimeLeft    100028 - world    false    nil    false    
[00:01:27]: ######################SinkHoleTimeLeft    nil    

```

my_mod.zip

Link to comment
Share on other sites

You need to use GLOBAL.TheWorld in the modmain, otherwise the world is always nil.

Screens are always running on the client, you will need to add netvars to send information from the server to the client, which you can then use with your screen.
 

  • Like 1
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...