Skip to Content
TutorialsEvent Tutorial

Event Tutorial

This tutorial shows how to create a custom server event and expose a sidebar button players can interact with.

1. Full Example (Reference)

-- test event script print("(Loaded) test event script for GrowSoft") local lunarFestivalEventData = { id = 21, -- Must be from the range of 21 to 255 and must be unique! title = "`3Lunar Festival``", description = "During Lunar Festival nothing happens!", message = "`8It's time for the `4Lunar Festival!````" } local lunarFestivalSidebarButton = { active = true, buttonAction = "lunarfestivalmenu", buttonTemplate = "BaseEventButton", counter = 0, counterMax = 0, itemIdIcon = 13054, name = "LunarFestival", order = lunarFestivalEventData.id, rcssClass = "daily_challenge", text = "`#Lunar``" } registerLuaEvent(lunarFestivalEventData) addSidebarButton(json.encode(lunarFestivalSidebarButton)) onEventChangedCallback(function(newEventID, oldEventID) if lunarFestivalEventData.id == newEventID then print(lunarFestivalEventData.title .. " Has started!") elseif lunarFestivalEventData.id == oldEventID then print(lunarFestivalEventData.title .. " Has ended!") end end) onPlayerLoginCallback(function(player) player:sendVariant({"OnEventButtonDataSet", lunarFestivalSidebarButton.name, (getCurrentServerEvent() == lunarFestivalEventData.id) and 1 or 0, json.encode(lunarFestivalSidebarButton)}) end) onPlayerEnterWorldCallback(function(world, player) player:sendVariant({"OnEventButtonDataSet", lunarFestivalSidebarButton.name, (getCurrentServerEvent() == lunarFestivalEventData.id) and 1 or 0, json.encode(lunarFestivalSidebarButton)}) end) onPlayerActionCallback(function(world, player, data) local action = data["action"] if action == lunarFestivalSidebarButton.buttonAction then if getCurrentServerEvent() == lunarFestivalEventData.id then player:sendVariant({"OnTextOverlay", lunarFestivalEventData.title}) end return true end return false end)

2. Step-by-Step Breakdown

2.1 Event Data Object

local lunarFestivalEventData = { id = 21, title = "`3Lunar Festival``", description = "During Lunar Festival nothing happens!", message = "`8It's time for the `4Lunar Festival!````" }

Field notes:

  • id: Unique numeric identifier (range 21–255). Avoid collisions with other scripts.
  • title: Display title; backticks and color codes are formatting tokens.
  • description: Used for internal UI or future display panels (depends on client).
  • message: Broadcast or contextual message text (optional usage depends on engine).

2.2 Sidebar Button Definition

local lunarFestivalSidebarButton = { active = true, buttonAction = "lunarfestivalmenu", buttonTemplate = "BaseEventButton", counter = 0, counterMax = 0, itemIdIcon = 13054, name = "LunarFestival", order = lunarFestivalEventData.id, rcssClass = "daily_challenge", text = "`#Lunar``" }

Meaning of key fields:

KeyPurpose
activeControls whether button is interactable/displayed.
buttonActionString sent when player clicks (matched in onPlayerActionCallback).
buttonTemplateTemplate skin / layout (engine-defined).
counter / counterMaxProgress display (e.g., 3/10). Use both >0 to show a bar/counter.
itemIdIconItem/icon ID used in UI (styling).
nameUnique internal name for referencing button in update packets.
orderSorting priority (lower vs higher depends on implementation; here it mirrors event ID).
rcssClassVisual style class/category.
textShort label (color-coded).

2.3 Registration

registerLuaEvent(lunarFestivalEventData) addSidebarButton(json.encode(lunarFestivalSidebarButton))
  • registerLuaEvent(table) expects a Lua table describing your event.
  • addSidebarButton(jsonString) requires JSON-encoded button data (so we call json.encode).

2.4 Lifecycle Callback

onEventChangedCallback(function(newEventID, oldEventID) if lunarFestivalEventData.id == newEventID then print(lunarFestivalEventData.title .. " Has started!") elseif lunarFestivalEventData.id == oldEventID then print(lunarFestivalEventData.title .. " Has ended!") end end)

Use cases:

  • Announce starts/ends.
  • Initialize timers or spawn resources.
  • Cleanup ephemeral state on end.

2.5 Player Synchronization

Both login and world entry ensure the player UI matches server state:

player:sendVariant({"OnEventButtonDataSet", lunarFestivalSidebarButton.name, (getCurrentServerEvent() == lunarFestivalEventData.id) and 1 or 0, json.encode(lunarFestivalSidebarButton) })

Arguments explanation (positional):

  1. Packet type: "OnEventButtonDataSet"
  2. Button internal name
  3. Active flag (1 if this event is currently active, else 0)
  4. JSON string describing the button

Call this:

  • When player logs in
  • When player changes worlds
  • When button state changes (e.g. counter increment)

2.6 Handling Player Actions

onPlayerActionCallback(function(world, player, data) local action = data["action"] if action == lunarFestivalSidebarButton.buttonAction then if getCurrentServerEvent() == lunarFestivalEventData.id then player:sendVariant({"OnTextOverlay", lunarFestivalEventData.title}) end return true end return false end)

Flow:

  1. Extract action from incoming data.
  2. Match versus your buttonAction string.
  3. Optionally ensure your event is currently active (getCurrentServerEvent()).
  4. Respond (here: show overlay).

Return:

  • true if you consumed the action (prevents other handlers or default processing).
  • false otherwise.

3. Creating Your Own Event

Below is a reusable skeleton. Replace markers.

print("(Loaded) Custom Event Script") local EVENT_ID = 42 -- Choose unique number between 21 and 255 local EVENT_KEY = "HarvestFestival" -- Short internal key local eventData = { id = EVENT_ID, title = "`2Harvest Festival``", description = "Collect harvest tokens during the festival!", message = "`6The Harvest Festival has begun! Gather resources!``" } local sidebarButton = { active = true, buttonAction = "harvestmenu", buttonTemplate = "BaseEventButton", counter = 0, counterMax = 100, -- show progress bar if desired itemIdIcon = 1234, name = EVENT_KEY, order = EVENT_ID, rcssClass = "seasonal_event", text = "`#Harvest``" } registerLuaEvent(eventData) addSidebarButton(json.encode(sidebarButton)) local function syncButton(player) local isActive = (getCurrentServerEvent() == EVENT_ID) and 1 or 0 player:sendVariant({"OnEventButtonDataSet", sidebarButton.name, isActive, json.encode(sidebarButton)}) end onEventChangedCallback(function(newID, oldID) if newID == EVENT_ID then print(eventData.title .. " started") -- Optionally broadcast message or set counters elseif oldID == EVENT_ID then print(eventData.title .. " ended") -- Cleanup / finalize / rewards end end) onPlayerLoginCallback(function(player) syncButton(player) end) onPlayerEnterWorldCallback(function(world, player) syncButton(player) end) onPlayerActionCallback(function(world, player, data) if data.action == sidebarButton.buttonAction then if getCurrentServerEvent() == EVENT_ID then player:sendVariant({"OnTextOverlay", eventData.title}) -- Open custom menu, show progress, etc. else player:sendVariant({"OnTextOverlay", "`4Event is not active.``"}) end return true end return false end)

4. Counters & Progress

To update counter dynamically:

sidebarButton.counter = sidebarButton.counter + 1 player:sendVariant({"OnEventButtonDataSet", sidebarButton.name, (getCurrentServerEvent() == EVENT_ID) and 1 or 0, json.encode(sidebarButton) })

If counterMax > 0, the client may display a progress bar or numeric fraction (implementation-dependent).

Last updated on