feat: flower month & thawing world changes#3991
Conversation
1.- Created the watering plants mechanics. 2.- Added Achievements 3.- Updated Rosemarie 4.- World Board Updated 5.- Added new global storage 6.- Deleted Dryads from spawns 7.- Added items and decay logic 8.- Achievements 9.- Dryad Raids (Flower Month) 10.- Ice Flower Spawns (Thawing) Everything has been debuged and tested at https://tibiatales.com using canary/tfs crystal server distribution.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a thawing world event and June dryad raid, introduces ice-flower items/seed mechanics, watering-can plant growth, NPC seed trade, map monster spawn rebalancing, and item decay chain adjustments. ChangesWorld Changes and Plant Interaction System
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request implements the Flower Month and Thawing world changes, adding the Rosemarie NPC, dryad raids, ice flower harvesting, and plant watering mechanics. The review feedback highlights several critical issues: the scheduleWave function in flower_month.lua recursively schedules itself indefinitely after June ends; the thawing global storage value is not reset to 0 when the event is inactive; and several scripts lack defensive checks for valid items or send magic effects to virtual container positions (x = 0xffff), which can cause Lua runtime errors or console warnings. Additionally, a duplicate "plant" keyword registration in rosemarie.lua should be removed.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| local function scheduleWave(index) | ||
| spawnDryads(WAVES[index]) | ||
| local next = index < #WAVES and index + 1 or 1 | ||
| addEvent(function() | ||
| scheduleWave(next) | ||
| end, WAVE_INTERVAL) | ||
| end |
There was a problem hiding this comment.
The scheduleWave function recursively schedules itself indefinitely via addEvent even after the month of June ends. Checking the current month inside scheduleWave before rescheduling ensures the event gracefully stops when the month changes.
local function scheduleWave(index)
local t = os.date("*t")
if t.month ~= 6 then
return
end
spawnDryads(WAVES[index])
local next = index < #WAVES and index + 1 or 1
addEvent(function()
scheduleWave(next)
end, WAVE_INTERVAL)
end
There was a problem hiding this comment.
Mr. Bot pelase read the full code! LOL
| function thawingMiniWorldChange.onStartup() | ||
| local eventChance = math.random(1, 100) | ||
| if eventChance <= CHANCE_PERCENT then | ||
| for _, pos in ipairs(POSITIONS) do | ||
| createThawingItem(pos) | ||
| end | ||
|
|
||
| Game.setStorageValue(GlobalStorage.WorldBoard.ThawingMiniWorldChange, 1) | ||
|
|
||
| local t = os.date("*t") | ||
| print(string.format("[%04d-%02d-%02d %02d:%02d:%02d.000] [info] [World Change] Thawing World Change event is active.", t.year, t.month, t.day, t.hour, t.min, t.sec)) | ||
| end | ||
|
|
||
| return true | ||
| end | ||
|
|
There was a problem hiding this comment.
The global storage value GlobalStorage.WorldBoard.ThawingMiniWorldChange is persistent in the database. If the thawing world change is active on one server startup, but the 10% chance is not met on a subsequent startup, the storage value will remain 1 from the previous run. It must be explicitly reset to 0 when the event is not active.
function thawingMiniWorldChange.onStartup()
local eventChance = math.random(1, 100)
if eventChance <= CHANCE_PERCENT then
for _, pos in ipairs(POSITIONS) do
createThawingItem(pos)
end
Game.setStorageValue(GlobalStorage.WorldBoard.ThawingMiniWorldChange, 1)
local t = os.date("*t")
print(string.format("[%04d-%02d-%02d %02d:%02d:%02d.000] [info] [World Change] Thawing World Change event is active.",
t.year, t.month, t.day, t.hour, t.min, t.sec))
else
Game.setStorageValue(GlobalStorage.WorldBoard.ThawingMiniWorldChange, 0)
end
return true
end
| function wateringCan.onUse(player, item, fromPosition, target, toPosition, isHotkey) | ||
| local targetId = target.itemid | ||
| local action = ITEM_ACTION[targetId] | ||
| local roll = math.random(100) | ||
| local advanced = roll <= CHANCES | ||
|
|
||
| if targetId == EMPTY_POT then | ||
| player:say("You should plant some seeds first.", TALKTYPE_MONSTER_SAY) | ||
| return true | ||
| elseif targetId == SEED_POT then |
There was a problem hiding this comment.
If a player uses the watering can on themselves or another creature, target will be a Creature object which does not have an itemid property, causing a Lua runtime error. Adding a defensive check to ensure target is a valid item prevents this crash.
function wateringCan.onUse(player, item, fromPosition, target, toPosition, isHotkey)
if not target or not target:isItem() then
return false
end
local targetId = target.itemid
local action = ITEM_ACTION[targetId]
local roll = math.random(100)
local advanced = roll <= CHANCES
if targetId == EMPTY_POT then
player:say("You should plant some seeds first.", TALKTYPE_MONSTER_SAY)
return true
elseif targetId == SEED_POT then
| function iceFlowerSeeds.onUse(player, item, fromPosition, target, toPosition, isHotkey) | ||
| if target.itemid == 14029 then | ||
| item:remove(1) | ||
| toPosition:sendMagicEffect(CONST_ME_POFF) | ||
| if not player:hasAchievement("Preservationist") then | ||
| player:addAchievement("Preservationist") | ||
| end | ||
| elseif target.itemid == 306 then | ||
| item:remove(1) | ||
| target:transform(14031) | ||
| target:decay() | ||
| toPosition:sendMagicEffect(CONST_ME_POFF) | ||
| end | ||
| return true | ||
| end |
There was a problem hiding this comment.
This action script is missing a defensive check to ensure target is a valid item, which will cause a Lua runtime error if used on a creature or empty space. Additionally, it should return false for unhandled items to allow default engine messages, and check toPosition.x ~= 0xffff before sending magic effects to avoid console warnings when used inside containers.
function iceFlowerSeeds.onUse(player, item, fromPosition, target, toPosition, isHotkey)
if not target or not target:isItem() then
return false
end
if target.itemid == 14029 then
item:remove(1)
if toPosition.x ~= 0xffff then
toPosition:sendMagicEffect(CONST_ME_POFF)
end
if not player:hasAchievement("Preservationist") then
player:addAchievement("Preservationist")
end
return true
elseif target.itemid == 306 then
item:remove(1)
target:transform(14031)
target:decay()
if toPosition.x ~= 0xffff then
toPosition:sendMagicEffect(CONST_ME_POFF)
end
return true
end
return false
end
| keywordHandler:addKeyword({ "plant" }, StdModule.say, { | ||
| npcHandler = npcHandler, | ||
| text = { | ||
| "You will have to water it regularly, then it will flourish and grow. There are several stages of growth that your plant has to pass ...", | ||
| "With good care and luck, it will grow into the next stage until it finally becomes a fully blooming flower. ...", | ||
| "Of course plants won't grow in the darkness of a depot box. On the other hand, it won't dry out there either ...", | ||
| "So if you know you can't take care of your plant for a longer time, it might be a good idea to store it into your depot box.", | ||
| }, | ||
| }) |
There was a problem hiding this comment.
| toPosition:sendMagicEffect(CONST_ME_LOSEENERGY) | ||
| return true | ||
| end |
There was a problem hiding this comment.
If a player waters a plant inside their backpack (a very common action), toPosition will be a virtual container position (x = 0xffff). Sending a magic effect to a container position is invalid and will spam warnings in the console. Checking toPosition.x ~= 0xffff prevents this.
if toPosition.x ~= 0xffff then
toPosition:sendMagicEffect(CONST_ME_LOSEENERGY)
end
return true
end
| function seeds.onAddItem(moveitem, tileitem, position, creature) | ||
| if tileitem.itemid == 306 then | ||
| if moveitem.itemid == 647 then | ||
| tileitem:transform(324) | ||
| tileitem:decay() | ||
| moveitem:remove(1) | ||
| position:sendMagicEffect(CONST_ME_POFF) | ||
| elseif moveitem.itemid == 13844 then | ||
| tileitem:transform(14031) | ||
| tileitem:decay() | ||
| moveitem:remove(1) | ||
| position:sendMagicEffect(CONST_ME_POFF) | ||
| end | ||
| end | ||
| return true | ||
| end |
There was a problem hiding this comment.
If a player drags a seed onto a flowerpot inside a container (like a backpack), position will be a virtual container position (x = 0xffff). Sending a magic effect to a container position is invalid and causes console warnings. Checking position.x ~= 0xffff prevents this.
function seeds.onAddItem(moveitem, tileitem, position, creature)
if tileitem.itemid == 306 then
if moveitem.itemid == 647 then
tileitem:transform(324)
tileitem:decay()
moveitem:remove(1)
if position.x ~= 0xffff then
position:sendMagicEffect(CONST_ME_POFF)
end
elseif moveitem.itemid == 13844 then
tileitem:transform(14031)
tileitem:decay()
moveitem:remove(1)
if position.x ~= 0xffff then
position:sendMagicEffect(CONST_ME_POFF)
end
end
end
return true
end
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
data-otservbr-global/scripts/world_changes/thawing.lua (1)
21-27: 💤 Low valueOptional: guard against duplicate flowers on repeated activations.
createThawingItemcreates anICE_FLOWERunconditionally on any tile that exists. If the event re-activates on a later startup without the previous flowers having been harvested/removed, items can stack at the same positions. Consider skipping creation when the position already holds an ICE_FLOWER.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@data-otservbr-global/scripts/world_changes/thawing.lua` around lines 21 - 27, createThawingItem currently always spawns an ICE_FLOWER on any existing Tile, causing duplicate flowers on repeated activations; modify createThawingItem to first inspect the tile for an existing ICE_FLOWER (e.g. use Tile(pos):getItemById(ICE_FLOWER) or iterate Tile(pos):getItems() and compare item:getId() to ICE_FLOWER) and only call Game.createItem(ICE_FLOWER, 1, pos) when no ICE_FLOWER is present, otherwise return false or the found item.data/scripts/actions/tools/watering_can.lua (1)
74-129: ⚖️ Poor tradeoffOptional: collapse the repetitive growth branches.
The
drying1,drying2,wilting,winterblossom_grow, andadvancebranches all follow the same shape (say message → transform to success/fail id →decay()), differing only in target ids and the achievement hook. EncodinggrowMsg/failMsg/successId/failId/awardAchievementin eachITEM_ACTIONentry would let a single block handle all of them and reduce drift risk.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@data/scripts/actions/tools/watering_can.lua` around lines 74 - 129, The repeated branches for action.type values ("drying1","drying2","wilting","winterblossom_grow","advance") all perform the same pattern (say message → transform to success/fail id → target:decay(), sometimes call player:addAchievementProgress or scan PLANTS), so refactor into one unified handler: add per-action metadata fields to the ITEM_ACTION entries (e.g. growMsg, failMsg, successId, failId, awardAchievement/achievementAmount), then replace the separate elseif blocks with a single block that checks action.type, uses action.successId/action.failId (or resolves random picks like STAGE1_POOL when needed) to call target:transform, uses action.growMsg/action.failMsg for player:say, and conditionally calls player:addAchievementProgress or the PLANTS scan when action.awardAchievement is set; ensure existing symbols (player:say, target:transform, target:decay, player:addAchievementProgress, STAGE1_POOL, SEED_POT, PLANTS) are used and preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@data-otservbr-global/npc/rosemarie.lua`:
- Around line 90-91: Player(creature) can be nil; after creating local player =
Player(creature) add a nil guard before calling player:getId() (e.g., if not
player then return false or exit the event handler) so you never invoke
player:getId() on nil; update any downstream logic that assumes playerId to
early-return or skip processing when player is nil to avoid crashes.
- Around line 104-107: The exchange currently assumes removeItem/addItem
succeed; update the logic to validate both operations: call
player:removeItem(647, 5) and check its return value before proceeding, then
call player:addItem(306, 1) and check its return value; only call npcHandler:say
success text when both return true; if removeItem succeeds but addItem fails,
attempt to restore the originals by calling player:addItem(647, 5) (and handle
that result/log failure) and send a failure message instead of the success text.
Ensure you reference the existing functions player:getItemCount,
player:removeItem, player:addItem and npcHandler:say in the updated flow.
- Around line 79-87: There is a duplicate registration of the "plant" keyword
via keywordHandler:addKeyword that re-defines the same response and overrides
the earlier one; remove the redundant second block (the
keywordHandler:addKeyword call that uses npcHandler and the identical text
array) so only the first "plant" registration remains, ensuring npcHandler and
StdModule.say usage from the original definition are preserved.
In `@data-otservbr-global/scripts/raids/world_changes/flower_month.lua`:
- Around line 49-55: scheduleWave currently reschedules itself unconditionally
causing infinite events; modify scheduleWave (which calls spawnDryads,
references WAVES and WAVE_INTERVAL) to check the current month before calling
addEvent—retrieve the current month (e.g. via os.date("*t").month) and only
schedule the next wave if the month equals June (6); if not June, do not call
addEvent (optionally perform any cleanup) so waves stop after June ends.
- Around line 42-43: The spawn counter is incremented unconditionally even if
Game.createMonster fails (it can return nil); update the block around
Game.createMonster("Dryad", pos, false, true) to check the returned value and
only increment spawned when a valid monster object is returned (and optionally
log or warn when creation fails) so spawned correctly reflects actual Dryads
spawned toward DRYADS_PER_WAVE.
- Line 64: Game.createNpc("Rosemarie", ROSEMARIE_POS, true) is called without
checking its return value; update the event code (the call to Game.createNpc) to
capture the return (e.g., local npc = Game.createNpc(...)) and validate it
before proceeding: if nil or false, log an error via the server logger (or game
logger) including the NPC name and ROSEMARIE_POS and abort or cleanly handle the
event (do not continue as if the NPC exists). Ensure you reference the returned
npc variable in subsequent logic (or bail out) so blocked spawns are detected
and reported.
In `@data-otservbr-global/scripts/world_changes/thawing.lua`:
- Around line 31-45: The code in thawingMiniWorldChange.onStartup currently only
sets GlobalStorage.WorldBoard.ThawingMiniWorldChange = 1 when the random roll
succeeds, leaving the storage stuck on 1 across restarts; update onStartup so
that after the roll it explicitly sets
Game.setStorageValue(GlobalStorage.WorldBoard.ThawingMiniWorldChange, 1) when
activating and sets
Game.setStorageValue(GlobalStorage.WorldBoard.ThawingMiniWorldChange, 0) (or
clears it) when the event does not activate, keeping the existing
createThawingItem(POSITIONS) logic and CHANCE_PERCENT check intact; this ensures
towncryer/world_board >0 checks reflect the real active state.
In `@data/items/items.xml`:
- Around line 31578-31581: The harvested ice flower item (id="13843") currently
has an attribute decayTo="13842" which causes it to regenerate into the
unharvested ice flower; remove that regeneration by either deleting the decayTo
attribute or changing it to decayTo="0" so the harvested item disappears after
duration expires; edit the <item id="13843"> element (and its <attribute
key="decayTo">) to implement the chosen behavior while keeping the duration
attribute intact.
---
Nitpick comments:
In `@data-otservbr-global/scripts/world_changes/thawing.lua`:
- Around line 21-27: createThawingItem currently always spawns an ICE_FLOWER on
any existing Tile, causing duplicate flowers on repeated activations; modify
createThawingItem to first inspect the tile for an existing ICE_FLOWER (e.g. use
Tile(pos):getItemById(ICE_FLOWER) or iterate Tile(pos):getItems() and compare
item:getId() to ICE_FLOWER) and only call Game.createItem(ICE_FLOWER, 1, pos)
when no ICE_FLOWER is present, otherwise return false or the found item.
In `@data/scripts/actions/tools/watering_can.lua`:
- Around line 74-129: The repeated branches for action.type values
("drying1","drying2","wilting","winterblossom_grow","advance") all perform the
same pattern (say message → transform to success/fail id → target:decay(),
sometimes call player:addAchievementProgress or scan PLANTS), so refactor into
one unified handler: add per-action metadata fields to the ITEM_ACTION entries
(e.g. growMsg, failMsg, successId, failId, awardAchievement/achievementAmount),
then replace the separate elseif blocks with a single block that checks
action.type, uses action.successId/action.failId (or resolves random picks like
STAGE1_POOL when needed) to call target:transform, uses
action.growMsg/action.failMsg for player:say, and conditionally calls
player:addAchievementProgress or the PLANTS scan when action.awardAchievement is
set; ensure existing symbols (player:say, target:transform, target:decay,
player:addAchievementProgress, STAGE1_POOL, SEED_POT, PLANTS) are used and
preserved.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a060c007-604c-4565-8700-8340da13731f
📒 Files selected for processing (12)
data-otservbr-global/lib/core/storages.luadata-otservbr-global/npc/rosemarie.luadata-otservbr-global/npc/towncryer.luadata-otservbr-global/scripts/actions/other/world_board.luadata-otservbr-global/scripts/raids/world_changes/flower_month.luadata-otservbr-global/scripts/world_changes/thawing.luadata-otservbr-global/world/otservbr-monster.xmldata/items/items.xmldata/scripts/actions/items/ice_flower.luadata/scripts/actions/items/ice_flower_seeds.luadata/scripts/actions/tools/watering_can.luadata/scripts/movements/seeds.lua
💤 Files with no reviewable changes (1)
- data-otservbr-global/world/otservbr-monster.xml
| keywordHandler:addKeyword({ "plant" }, StdModule.say, { | ||
| npcHandler = npcHandler, | ||
| text = { | ||
| "You will have to water it regularly, then it will flourish and grow. There are several stages of growth that your plant has to pass ...", | ||
| "With good care and luck, it will grow into the next stage until it finally becomes a fully blooming {flower}. ...", | ||
| "Of course plants won't grow in the darkness of a depot box. On the other hand, it won't dry out there either ...", | ||
| "So if you know you can't take care of your plant for a longer time, it might be a good idea to store it into your depot box.", | ||
| }, | ||
| }) |
There was a problem hiding this comment.
Remove duplicate "plant" keyword definition.
The "plant" keyword is already defined at lines 56-64. This second definition at lines 79-87 is identical and will override the first registration, causing unnecessary processing.
♻️ Proposed fix to remove duplicate
keywordHandler:addKeyword({ "how are you" }, StdModule.say, { npcHandler = npcHandler, text = "Well, how do I look? Beautiful isn't?" })
-keywordHandler:addKeyword({ "plant" }, StdModule.say, {
- npcHandler = npcHandler,
- text = {
- "You will have to water it regularly, then it will flourish and grow. There are several stages of growth that your plant has to pass ...",
- "With good care and luck, it will grow into the next stage until it finally becomes a fully blooming {flower}. ...",
- "Of course plants won't grow in the darkness of a depot box. On the other hand, it won't dry out there either ...",
- "So if you know you can't take care of your plant for a longer time, it might be a good idea to store it into your depot box.",
- },
-})
local function creatureSayCallback(npc, creature, type, message)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| keywordHandler:addKeyword({ "plant" }, StdModule.say, { | |
| npcHandler = npcHandler, | |
| text = { | |
| "You will have to water it regularly, then it will flourish and grow. There are several stages of growth that your plant has to pass ...", | |
| "With good care and luck, it will grow into the next stage until it finally becomes a fully blooming {flower}. ...", | |
| "Of course plants won't grow in the darkness of a depot box. On the other hand, it won't dry out there either ...", | |
| "So if you know you can't take care of your plant for a longer time, it might be a good idea to store it into your depot box.", | |
| }, | |
| }) | |
| keywordHandler:addKeyword({ "how are you" }, StdModule.say, { npcHandler = npcHandler, text = "Well, how do I look? Beautiful isn't?" }) | |
| local function creatureSayCallback(npc, creature, type, message) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@data-otservbr-global/npc/rosemarie.lua` around lines 79 - 87, There is a
duplicate registration of the "plant" keyword via keywordHandler:addKeyword that
re-defines the same response and overrides the earlier one; remove the redundant
second block (the keywordHandler:addKeyword call that uses npcHandler and the
identical text array) so only the first "plant" registration remains, ensuring
npcHandler and StdModule.say usage from the original definition are preserved.
| local player = Player(creature) | ||
| local playerId = player:getId() |
There was a problem hiding this comment.
Critical: Add nil check for Player constructor.
Player(creature) can return nil if the creature is not a valid player or has disconnected. Calling getId() on nil will crash the script.
🐛 Proposed fix to add nil guard
local function creatureSayCallback(npc, creature, type, message)
local player = Player(creature)
+ if not player then
+ return false
+ end
local playerId = player:getId()
if not npcHandler:checkInteraction(npc, creature) then🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@data-otservbr-global/npc/rosemarie.lua` around lines 90 - 91,
Player(creature) can be nil; after creating local player = Player(creature) add
a nil guard before calling player:getId() (e.g., if not player then return false
or exit the event handler) so you never invoke player:getId() on nil; update any
downstream logic that assumes playerId to early-return or skip processing when
player is nil to avoid crashes.
| if player:getItemCount(647) >= 5 then | ||
| player:removeItem(647, 5) | ||
| player:addItem(306, 1) | ||
| npcHandler:say("Here, I planted this little baby in a flowerpot for you. Don't forget to water it regularly - buy a watering can somewhere!", npc, creature) |
There was a problem hiding this comment.
Validate item transaction success.
Both removeItem and addItem can fail (e.g., if inventory state changes between the count check and removal). The NPC should verify the transaction succeeded before confirming the exchange.
🛡️ Proposed fix to check transaction results
elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 1 then
if player:getItemCount(647) >= 5 then
- player:removeItem(647, 5)
- player:addItem(306, 1)
- npcHandler:say("Here, I planted this little baby in a flowerpot for you. Don't forget to water it regularly - buy a watering can somewhere!", npc, creature)
+ if player:removeItem(647, 5) then
+ if player:addItem(306, 1) then
+ npcHandler:say("Here, I planted this little baby in a flowerpot for you. Don't forget to water it regularly - buy a watering can somewhere!", npc, creature)
+ else
+ player:addItem(647, 5) -- Refund seeds if flowerpot can't be added
+ npcHandler:say("I couldn't give you the flowerpot. Your inventory might be full.", npc, creature)
+ end
+ else
+ npcHandler:say("You don't have enough seeds.", npc, creature)
+ end
else
npcHandler:say("You don't have enough seeds.", npc, creature)
end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@data-otservbr-global/npc/rosemarie.lua` around lines 104 - 107, The exchange
currently assumes removeItem/addItem succeed; update the logic to validate both
operations: call player:removeItem(647, 5) and check its return value before
proceeding, then call player:addItem(306, 1) and check its return value; only
call npcHandler:say success text when both return true; if removeItem succeeds
but addItem fails, attempt to restore the originals by calling
player:addItem(647, 5) (and handle that result/log failure) and send a failure
message instead of the success text. Ensure you reference the existing functions
player:getItemCount, player:removeItem, player:addItem and npcHandler:say in the
updated flow.
| Game.createMonster("Dryad", pos, false, true) | ||
| spawned = spawned + 1 |
There was a problem hiding this comment.
Verify monster creation succeeded before incrementing spawn count.
Game.createMonster can return nil if the monster type doesn't exist or spawn fails. Currently, the counter increments regardless, potentially causing fewer than DRYADS_PER_WAVE monsters to spawn while reporting success.
🛡️ Proposed fix to validate monster creation
local tile = Tile(pos)
if tile and tile:isWalkable() then
- Game.createMonster("Dryad", pos, false, true)
- spawned = spawned + 1
+ if Game.createMonster("Dryad", pos, false, true) then
+ spawned = spawned + 1
+ end
end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@data-otservbr-global/scripts/raids/world_changes/flower_month.lua` around
lines 42 - 43, The spawn counter is incremented unconditionally even if
Game.createMonster fails (it can return nil); update the block around
Game.createMonster("Dryad", pos, false, true) to check the returned value and
only increment spawned when a valid monster object is returned (and optionally
log or warn when creation fails) so spawned correctly reflects actual Dryads
spawned toward DRYADS_PER_WAVE.
| local function scheduleWave(index) | ||
| spawnDryads(WAVES[index]) | ||
| local next = index < #WAVES and index + 1 or 1 | ||
| addEvent(function() | ||
| scheduleWave(next) | ||
| end, WAVE_INTERVAL) | ||
| end |
There was a problem hiding this comment.
Critical: Infinite event scheduling beyond June.
The scheduleWave function recursively schedules itself indefinitely without checking if the current month is still June. Once started, waves will continue spawning every 15 minutes forever, even after June ends, causing a memory leak and unintended behavior.
🐛 Proposed fix to add month check before rescheduling
local function scheduleWave(index)
+ local t = os.date("*t")
+ if t.month ~= 6 then
+ return
+ end
+
spawnDryads(WAVES[index])
local next = index < `#WAVES` and index + 1 or 1
addEvent(function()
scheduleWave(next)
end, WAVE_INTERVAL)
end📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| local function scheduleWave(index) | |
| spawnDryads(WAVES[index]) | |
| local next = index < #WAVES and index + 1 or 1 | |
| addEvent(function() | |
| scheduleWave(next) | |
| end, WAVE_INTERVAL) | |
| end | |
| local function scheduleWave(index) | |
| local t = os.date("*t") | |
| if t.month ~= 6 then | |
| return | |
| end | |
| spawnDryads(WAVES[index]) | |
| local next = index < `#WAVES` and index + 1 or 1 | |
| addEvent(function() | |
| scheduleWave(next) | |
| end, WAVE_INTERVAL) | |
| end |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@data-otservbr-global/scripts/raids/world_changes/flower_month.lua` around
lines 49 - 55, scheduleWave currently reschedules itself unconditionally causing
infinite events; modify scheduleWave (which calls spawnDryads, references WAVES
and WAVE_INTERVAL) to check the current month before calling addEvent—retrieve
the current month (e.g. via os.date("*t").month) and only schedule the next wave
if the month equals June (6); if not June, do not call addEvent (optionally
perform any cleanup) so waves stop after June ends.
| local month = t.month | ||
|
|
||
| if month == 6 then | ||
| Game.createNpc("Rosemarie", ROSEMARIE_POS, true) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Verify Rosemarie NPC is defined and matches the spawn expectation
# Search for NPC definition (excluding this raid file)
rg -n --type=lua 'createNpcType.*[Rr]osemarie' -g '!**/flower_month.lua'Repository: opentibiabr/canary
Length of output: 44
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Show relevant lines around the createNpc call in the target file
target="data-otservbr-global/scripts/raids/world_changes/flower_month.lua"
echo "---- $target (around lines 50-90) ----"
nl -ba "$target" | sed -n '40,90p'
# 2) Find all occurrences of Rosemarie across Lua files
echo "---- rg Rosemarie across Lua ----"
rg -n --type=lua --hidden --no-ignore -S 'Rosemarie' .
# 3) Check how Game.createNpc is used elsewhere (any return-value handling)
echo "---- rg Game.createNpc patterns (handling nil) ----"
rg -n --type=lua --hidden --no-ignore -S 'local\s+npc\s*=\s*Game\.createNpc|Game\.createNpc\([^)]*\)\s*or|if\s+not\s+.*Game\.createNpc' .
echo "---- rg Game.createNpc simple usage (context) ----"
rg -n --type=lua --hidden --no-ignore -S 'Game\.createNpc\s*\(' data-otservbr-global/scripts | head -n 50Repository: opentibiabr/canary
Length of output: 220
🏁 Script executed:
#!/bin/bash
set -euo pipefail
target="data-otservbr-global/scripts/raids/world_changes/flower_month.lua"
echo "---- $target (around lines 50-90) ----"
nl -ba "$target" | sed -n '50,80p'
echo "---- rg Rosemarie across Lua ----"
rg -n --type=lua --hidden --no-ignore -S 'Rosemarie' .
echo "---- rg Game.createNpc patterns (handling nil) ----"
rg -n --type=lua --hidden --no-ignore -S 'local\s+npc\s*=\s*Game\.createNpc|if\s+not\s+.*Game\.createNpc|local\s+\w+\s*=\s*Game\.createNpc\(|Game\.createNpc\([^)]*\)\s*or' data-otservbr-global/scripts || true
echo "---- rg Game.createNpc simple usage (context) ----"
rg -n --type=lua --hidden --no-ignore -S 'Game\.createNpc\s*\(' data-otservbr-global/scripts | head -n 50Repository: opentibiabr/canary
Length of output: 220
🏁 Script executed:
#!/bin/bash
set -euo pipefail
target="data-otservbr-global/scripts/raids/world_changes/flower_month.lua"
echo "---- $target (around lines 50-90) ----"
nl -ba "$target" | sed -n '50,90p'
echo "---- rg Rosemarie across Lua ----"
rg -n --type=lua --hidden --no-ignore -S 'Rosemarie' .
echo "---- rg Game.createNpc patterns (handling nil) ----"
rg -n --type=lua --hidden --no-ignore -S 'local\s+\w+\s*=\s*Game\.createNpc|if\s+not\s+.*Game\.createNpc|Game\.createNpc\([^)]*\)\s*or' data-otservbr-global/scripts || true
echo "---- rg Game.createNpc simple usage (context) ----"
rg -n --type=lua --hidden --no-ignore -S 'Game\.createNpc\s*\(' data-otservbr-global/scripts | head -n 50Repository: opentibiabr/canary
Length of output: 220
🏁 Script executed:
#!/bin/bash
set -euo pipefail
target="data-otservbr-global/scripts/raids/world_changes/flower_month.lua"
echo "---- $target (around lines 50-90) ----"
nl -ba "$target" | sed -n '50,90p'
echo "---- rg Rosemarie across Lua ----"
rg -n --type=lua --hidden --no-ignore -S 'Rosemarie' .
echo "---- rg Game.createNpc patterns (handling nil) ----"
rg -n --type=lua --hidden --no-ignore -S 'local\s+\w+\s*=\s*Game\.createNpc|if\s+not\s+.*Game\.createNpc|Game\.createNpc\([^)]*\)\s*or' data-otservbr-global/scripts || true
echo "---- rg Game.createNpc simple usage (context) ----"
rg -n --type=lua --hidden --no-ignore -S 'Game\.createNpc\s*\(' data-otservbr-global/scripts | head -n 50Repository: opentibiabr/canary
Length of output: 220
🏁 Script executed:
#!/bin/bash
set -euo pipefail
target="data-otservbr-global/scripts/raids/world_changes/flower_month.lua"
echo "---- $target (lines 55-75 with numbers) ----"
python3 - <<PY
path="$target"
start=55
end=75
with open(path,'r',encoding='utf-8',errors='ignore') as f:
for i,line in enumerate(f, start=1):
if start <= i <= end:
print(f"{i:4d}\t{line.rstrip()}")
if i > end: break
PY
echo "---- occurrences of Rosemarie in repo (Lua) ----"
rg -n --type=lua --hidden --no-ignore 'Rosemarie' .
echo "---- occurrences of Game.createNpc('Rosemarie' ... ) ----"
rg -n --type=lua --hidden --no-ignore 'Game\.createNpc\s*\(\s*["'\'']Rosemarie["'\'']' .
echo "---- sample of Game.createNpc nil-handling patterns in world_changes scripts ----"
rg -n --type=lua --hidden --no-ignore \
'local\s+\w+\s*=\s*Game\.createNpc|if\s+not\s+\w+\s*then|if\s+not\s+Game\.createNpc|or\s+nil|~= *nil' \
data-otservbr-global/scripts/world_changes data-otservbr-global/scripts/raids 2>/dev/null || true
echo "---- search: Game.createNpc( usage with surrounding context ----"
rg -n --type=lua --hidden --no-ignore 'Game\.createNpc\s*\(' data-otservbr-global/scripts/raids | head -n 50Repository: opentibiabr/canary
Length of output: 2551
Check NPC creation return value.
Game.createNpc("Rosemarie", ROSEMARIE_POS, true) (flower_month.lua:64) isn’t checked. If the spawn fails (e.g., blocked position), the event continues without logging—though the NPC’s definition exists at data-otservbr-global/npc/rosemarie.lua.
🛡️ Proposed fix to validate NPC creation
- Game.createNpc("Rosemarie", ROSEMARIE_POS, true)
+ local npc = Game.createNpc("Rosemarie", ROSEMARIE_POS, true)
+ if not npc then
+ print(string.format("[%04d-%02d-%02d %02d:%02d:%02d.000] [warning] [World Change] Failed to spawn Rosemarie NPC.", t.year, t.month, t.day, t.hour, t.min, t.sec))
+ end📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Game.createNpc("Rosemarie", ROSEMARIE_POS, true) | |
| local npc = Game.createNpc("Rosemarie", ROSEMARIE_POS, true) | |
| if not npc then | |
| print(string.format("[%04d-%02d-%02d %02d:%02d:%02d.000] [warning] [World Change] Failed to spawn Rosemarie NPC.", t.year, t.month, t.day, t.hour, t.min, t.sec)) | |
| end |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@data-otservbr-global/scripts/raids/world_changes/flower_month.lua` at line
64, Game.createNpc("Rosemarie", ROSEMARIE_POS, true) is called without checking
its return value; update the event code (the call to Game.createNpc) to capture
the return (e.g., local npc = Game.createNpc(...)) and validate it before
proceeding: if nil or false, log an error via the server logger (or game logger)
including the NPC name and ROSEMARIE_POS and abort or cleanly handle the event
(do not continue as if the NPC exists). Ensure you reference the returned npc
variable in subsequent logic (or bail out) so blocked spawns are detected and
reported.
| function thawingMiniWorldChange.onStartup() | ||
| local eventChance = math.random(1, 100) | ||
| if eventChance <= CHANCE_PERCENT then | ||
| for _, pos in ipairs(POSITIONS) do | ||
| createThawingItem(pos) | ||
| end | ||
|
|
||
| Game.setStorageValue(GlobalStorage.WorldBoard.ThawingMiniWorldChange, 1) | ||
|
|
||
| local t = os.date("*t") | ||
| print(string.format("[%04d-%02d-%02d %02d:%02d:%02d.000] [info] [World Change] Thawing World Change event is active.", t.year, t.month, t.day, t.hour, t.min, t.sec)) | ||
| end | ||
|
|
||
| return true | ||
| end |
There was a problem hiding this comment.
Reset the storage when the event does not activate, otherwise the thaw announcements persist forever.
Global storage values are persisted across server restarts. onStartup only ever sets ThawingMiniWorldChange to 1 when the 10% roll succeeds, and the key is not in startupGlobalStorages (storages.lua L3081). So once the event activates, the value stays 1 on every subsequent restart, and both the towncryer voice line and the world board message keep broadcasting the thaw even when the event is no longer active. The downstream > 0 checks in towncryer.lua and world_board.lua are correct; the root cause is the missing reset here.
🐛 Proposed fix: reset on the inactive path
function thawingMiniWorldChange.onStartup()
local eventChance = math.random(1, 100)
if eventChance <= CHANCE_PERCENT then
for _, pos in ipairs(POSITIONS) do
createThawingItem(pos)
end
Game.setStorageValue(GlobalStorage.WorldBoard.ThawingMiniWorldChange, 1)
local t = os.date("*t")
print(string.format("[%04d-%02d-%02d %02d:%02d:%02d.000] [info] [World Change] Thawing World Change event is active.", t.year, t.month, t.day, t.hour, t.min, t.sec))
+ else
+ Game.setStorageValue(GlobalStorage.WorldBoard.ThawingMiniWorldChange, 0)
end
return true
end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@data-otservbr-global/scripts/world_changes/thawing.lua` around lines 31 - 45,
The code in thawingMiniWorldChange.onStartup currently only sets
GlobalStorage.WorldBoard.ThawingMiniWorldChange = 1 when the random roll
succeeds, leaving the storage stuck on 1 across restarts; update onStartup so
that after the roll it explicitly sets
Game.setStorageValue(GlobalStorage.WorldBoard.ThawingMiniWorldChange, 1) when
activating and sets
Game.setStorageValue(GlobalStorage.WorldBoard.ThawingMiniWorldChange, 0) (or
clears it) when the event does not activate, keeping the existing
createThawingItem(POSITIONS) logic and CHANCE_PERCENT check intact; this ensures
towncryer/world_board >0 checks reflect the real active state.
| <item id="13843" article="a" name="harvested ice flower"> | ||
| <attribute key="duration" value="14400" /> | ||
| <attribute key="decayTo" value="13842" /> | ||
| </item> |
There was a problem hiding this comment.
Logic error: Harvested ice flower decays back to unharvested state.
The harvested ice flower (13843) decays back to ice flower (13842), creating a regeneration loop. Once harvested, the flower should not regrow. This allows a player to harvest the same flower repeatedly.
The decay should either:
- Be removed (no decay for harvested state), or
- Decay to 0 (item disappears), or
- Decay to a "wilted harvested flower" state
Based on the plant mechanics, harvested flowers should likely disappear after the duration expires, not regenerate.
🔄 Proposed fix to prevent regeneration
<item id="13843" article="a" name="harvested ice flower">
<attribute key="duration" value="14400" />
- <attribute key="decayTo" value="13842" />
+ <attribute key="decayTo" value="0" />
</item>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <item id="13843" article="a" name="harvested ice flower"> | |
| <attribute key="duration" value="14400" /> | |
| <attribute key="decayTo" value="13842" /> | |
| </item> | |
| <item id="13843" article="a" name="harvested ice flower"> | |
| <attribute key="duration" value="14400" /> | |
| <attribute key="decayTo" value="0" /> | |
| </item> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@data/items/items.xml` around lines 31578 - 31581, The harvested ice flower
item (id="13843") currently has an attribute decayTo="13842" which causes it to
regenerate into the unharvested ice flower; remove that regeneration by either
deleting the decayTo attribute or changing it to decayTo="0" so the harvested
item disappears after duration expires; edit the <item id="13843"> element (and
its <attribute key="decayTo">) to implement the chosen behavior while keeping
the duration attribute intact.
|
Very Nice 👯 |
…ithub.com/Paco161315/canary into feat-flower-month-&-thawing-world-changes
|



1.- Created the watering plants mechanics.
2.- Added Achievements
3.- Updated Rosemarie
4.- World Board Updated
5.- Added new global storage
6.- Deleted Dryads from spawns
7.- Added items and decay logic
8.- Achievements
9.- Dryad Raids (Flower Month)
10.- Ice Flower Spawns (Thawing)
Everything has been debuged and tested at https://tibiatales.com
Summary by CodeRabbit
Release Notes